-
Notifications
You must be signed in to change notification settings - Fork 0
/
mumblePing.ts
133 lines (122 loc) · 3.42 KB
/
mumblePing.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import Gio from 'gi://Gio';
const MUMBLE_PING_BODY = [0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8];
const MUMBLE_PING_RESPONSE_LEN = 24;
Gio._promisify(
Gio.SocketClient.prototype,
'connect_to_host_async',
'connect_to_host_finish'
);
Gio._promisify(Gio.OutputStream.prototype, 'write_async', 'write_finish');
Gio._promisify(
Gio.InputStream.prototype,
'read_bytes_async',
'read_bytes_finish'
);
export class EmptyPingResultError extends Error {
constructor(msg: string) {
super(msg);
// Set the prototype explicitly.
Object.setPrototypeOf(this, EmptyPingResultError.prototype);
}
}
function _readUInt32BE(bytes: Uint8Array, startPosition: number) {
let result = 0;
let shift = 24;
for (let i = startPosition; i < startPosition + 4; i++) {
result += bytes[i] << shift;
shift -= 8;
}
return result;
}
function _parseResponseBytes(responseBytes: Uint8Array) {
const version = [];
for (let i = 1; i < 4; i++)
version.push(Number(responseBytes[i]));
const versionStr = version.join('.');
const numUsersConnected = _readUInt32BE(responseBytes, 12);
const numMaxUsers = _readUInt32BE(responseBytes, 16);
const bandwidth = _readUInt32BE(responseBytes, 20);
const result = {
version: versionStr,
users: numUsersConnected,
maxUsers: numMaxUsers,
bandwidth,
};
return result;
}
async function _writeByteString(
connection: Gio.SocketConnection,
byteString: Iterable<number>,
cancellable: Gio.Cancellable
) {
const bytesWritten = await connection.outputStream.write_async(
Uint8Array.from(byteString),
0,
cancellable
);
return bytesWritten;
}
/**
* Read n bytes from Socket Connection (cancellable)
*
* @param connection
* @param numBytesToRead
* @param cancellable
*/
function _readBytesFromConnection(
connection: Gio.SocketConnection,
numBytesToRead: number,
cancellable: Gio.Cancellable
) {
return connection.inputStream.read_bytes_async(
numBytesToRead,
0,
cancellable
);
}
export interface MumblePingResult {
version?: string;
users: number;
maxUsers: number;
bandwidth?: number;
}
/**
* Creates an UDP socket to ping the Mumble server
*
* @param host Hostname of the Mumble server
* @param port Port of the Mumble server
* @param cancellable Gio.Cancellable to be able to cancel ongoing operations
* @returns Promise resolving with UDP socket
*/
export function createClient(
host: string,
port: number,
cancellable: Gio.Cancellable | null = null
) {
const udpSocket = new Gio.SocketClient();
udpSocket.protocol = Gio.SocketProtocol.UDP;
udpSocket.type = Gio.SocketType.DATAGRAM;
return udpSocket.connect_to_host_async(host, port, cancellable);
}
/**
*
* @param connection
* @param cancellable
*/
export async function pingMumble(
connection: Gio.SocketConnection,
cancellable: Gio.Cancellable
): Promise<MumblePingResult> {
await _writeByteString(connection, MUMBLE_PING_BODY, cancellable);
const responseBytes = await _readBytesFromConnection(
connection,
MUMBLE_PING_RESPONSE_LEN,
cancellable
);
if (responseBytes.get_size() > 0) {
const responseData = responseBytes.get_data();
return _parseResponseBytes(responseData!);
} else {
throw new EmptyPingResultError('Response to UDP Ping was empty');
}
}