ai-remote 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/index.ts → dist/index.d.ts} +0 -1
- package/dist/index.js +0 -1
- package/dist/protocols/index.d.ts +31 -0
- package/dist/protocols/index.js +0 -1
- package/dist/protocols/rdp/buffer.d.ts +38 -0
- package/dist/protocols/rdp/buffer.js +0 -1
- package/dist/protocols/rdp/caps.d.ts +49 -0
- package/dist/protocols/rdp/caps.js +0 -1
- package/dist/protocols/rdp/cert.d.ts +18 -0
- package/dist/protocols/rdp/cert.js +0 -1
- package/dist/protocols/rdp/client.d.ts +56 -0
- package/dist/protocols/rdp/client.js +0 -1
- package/dist/protocols/rdp/cliprdr.d.ts +27 -0
- package/dist/protocols/rdp/cliprdr.js +0 -1
- package/dist/protocols/rdp/credssp.d.ts +34 -0
- package/dist/protocols/rdp/credssp.js +0 -1
- package/dist/protocols/rdp/crypto.d.ts +63 -0
- package/dist/protocols/rdp/crypto.js +0 -1
- package/dist/protocols/rdp/display.d.ts +38 -0
- package/dist/protocols/rdp/display.js +0 -1
- package/dist/protocols/rdp/gcc.d.ts +23 -0
- package/dist/protocols/rdp/gcc.js +0 -1
- package/dist/protocols/rdp/keymap.d.ts +9 -0
- package/dist/protocols/rdp/keymap.js +0 -1
- package/dist/protocols/rdp/mcs.d.ts +40 -0
- package/dist/protocols/rdp/mcs.js +0 -1
- package/dist/protocols/rdp/ntlm.d.ts +61 -0
- package/dist/protocols/rdp/ntlm.js +0 -1
- package/dist/protocols/rdp/pdu.d.ts +155 -0
- package/dist/protocols/rdp/pdu.js +0 -1
- package/dist/protocols/rdp/rail.d.ts +119 -0
- package/dist/protocols/rdp/rail.js +0 -1
- package/dist/protocols/rdp/rle.d.ts +13 -0
- package/dist/protocols/rdp/rle.js +0 -1
- package/dist/protocols/rdp/sec.d.ts +59 -0
- package/dist/protocols/rdp/sec.js +0 -1
- package/dist/protocols/rdp/session.d.ts +62 -0
- package/dist/protocols/rdp/session.js +0 -1
- package/dist/protocols/rdp/tls.d.ts +18 -0
- package/dist/protocols/rdp/tls.js +0 -1
- package/dist/protocols/rdp/vchannel.d.ts +28 -0
- package/dist/protocols/rdp/vchannel.js +0 -1
- package/dist/protocols/rdp/x224.d.ts +40 -0
- package/dist/protocols/rdp/x224.js +0 -1
- package/dist/protocols/ssh/kex.d.ts +97 -0
- package/dist/protocols/ssh/kex.js +0 -1
- package/dist/protocols/ssh/messages.d.ts +50 -0
- package/dist/protocols/ssh/messages.js +0 -1
- package/dist/protocols/ssh/session.d.ts +66 -0
- package/dist/protocols/ssh/session.js +0 -1
- package/dist/protocols/ssh/terminal.d.ts +46 -0
- package/dist/protocols/ssh/terminal.js +0 -1
- package/dist/protocols/ssh/transport.d.ts +62 -0
- package/dist/protocols/ssh/transport.js +0 -1
- package/dist/protocols/ssh/wire.d.ts +52 -0
- package/dist/protocols/ssh/wire.js +0 -1
- package/dist/protocols/types.d.ts +127 -0
- package/dist/protocols/types.js +0 -1
- package/dist/protocols/vnc/input.d.ts +5 -0
- package/dist/protocols/vnc/input.js +0 -1
- package/dist/protocols/vnc/session.d.ts +12 -0
- package/dist/protocols/vnc/session.js +0 -1
- package/dist/shared/connection.d.ts +81 -0
- package/dist/shared/connection.js +0 -1
- package/dist/shared/device.d.ts +26 -0
- package/dist/shared/device.js +0 -1
- package/dist/shared/hosts.d.ts +63 -0
- package/dist/shared/hosts.js +0 -1
- package/dist/shared/icons.d.ts +46 -0
- package/dist/shared/icons.js +0 -1
- package/dist/shared/protocol.d.ts +28 -0
- package/dist/shared/protocol.js +0 -1
- package/dist/shared/signals.d.ts +37 -0
- package/dist/shared/signals.js +0 -1
- package/package.json +10 -11
- package/dist/index.js.map +0 -7
- package/dist/protocols/index.js.map +0 -7
- package/dist/protocols/rdp/buffer.js.map +0 -7
- package/dist/protocols/rdp/caps.js.map +0 -7
- package/dist/protocols/rdp/cert.js.map +0 -7
- package/dist/protocols/rdp/client.js.map +0 -7
- package/dist/protocols/rdp/cliprdr.js.map +0 -7
- package/dist/protocols/rdp/credssp.js.map +0 -7
- package/dist/protocols/rdp/crypto.js.map +0 -7
- package/dist/protocols/rdp/display.js.map +0 -7
- package/dist/protocols/rdp/gcc.js.map +0 -7
- package/dist/protocols/rdp/keymap.js.map +0 -7
- package/dist/protocols/rdp/mcs.js.map +0 -7
- package/dist/protocols/rdp/ntlm.js.map +0 -7
- package/dist/protocols/rdp/pdu.js.map +0 -7
- package/dist/protocols/rdp/rail.js.map +0 -7
- package/dist/protocols/rdp/rle.js.map +0 -7
- package/dist/protocols/rdp/sec.js.map +0 -7
- package/dist/protocols/rdp/session.js.map +0 -7
- package/dist/protocols/rdp/tls.js.map +0 -7
- package/dist/protocols/rdp/vchannel.js.map +0 -7
- package/dist/protocols/rdp/x224.js.map +0 -7
- package/dist/protocols/ssh/kex.js.map +0 -7
- package/dist/protocols/ssh/messages.js.map +0 -7
- package/dist/protocols/ssh/session.js.map +0 -7
- package/dist/protocols/ssh/terminal.js.map +0 -7
- package/dist/protocols/ssh/transport.js.map +0 -7
- package/dist/protocols/ssh/wire.js.map +0 -7
- package/dist/protocols/types.js.map +0 -7
- package/dist/protocols/vnc/input.js.map +0 -7
- package/dist/protocols/vnc/session.js.map +0 -7
- package/dist/shared/connection.js.map +0 -7
- package/dist/shared/device.js.map +0 -7
- package/dist/shared/hosts.js.map +0 -7
- package/dist/shared/icons.js.map +0 -7
- package/dist/shared/protocol.js.map +0 -7
- package/dist/shared/signals.js.map +0 -7
- package/src/cli/cli.ts +0 -462
- package/src/cli/daemon.ts +0 -306
- package/src/cli/framebuffer.ts +0 -115
- package/src/cli/generated/viewer-bundle.ts +0 -5
- package/src/cli/ipc.ts +0 -78
- package/src/cli/native/window.swift +0 -151
- package/src/cli/paths.ts +0 -28
- package/src/cli/png.ts +0 -106
- package/src/cli/session.ts +0 -263
- package/src/cli/shell.ts +0 -184
- package/src/cli/transport.ts +0 -86
- package/src/cli/viewer-client/assets.d.ts +0 -4
- package/src/cli/viewer-client/main.ts +0 -323
- package/src/cli/viewer-page.ts +0 -138
- package/src/cli/viewer.ts +0 -296
- package/src/cli/window.ts +0 -140
- package/src/cli/wsserver.ts +0 -144
- package/src/protocols/index.ts +0 -72
- package/src/protocols/rdp/buffer.ts +0 -204
- package/src/protocols/rdp/caps.ts +0 -304
- package/src/protocols/rdp/cert.ts +0 -190
- package/src/protocols/rdp/client.ts +0 -1371
- package/src/protocols/rdp/cliprdr.ts +0 -344
- package/src/protocols/rdp/credssp.ts +0 -319
- package/src/protocols/rdp/crypto.ts +0 -416
- package/src/protocols/rdp/display.ts +0 -750
- package/src/protocols/rdp/gcc.ts +0 -162
- package/src/protocols/rdp/keymap.ts +0 -68
- package/src/protocols/rdp/mcs.ts +0 -283
- package/src/protocols/rdp/ntlm.ts +0 -380
- package/src/protocols/rdp/pdu.ts +0 -497
- package/src/protocols/rdp/rail.ts +0 -480
- package/src/protocols/rdp/rle.ts +0 -343
- package/src/protocols/rdp/sec.ts +0 -184
- package/src/protocols/rdp/session.ts +0 -393
- package/src/protocols/rdp/tls.ts +0 -452
- package/src/protocols/rdp/vchannel.ts +0 -89
- package/src/protocols/rdp/x224.ts +0 -139
- package/src/protocols/ssh/kex.ts +0 -227
- package/src/protocols/ssh/messages.ts +0 -54
- package/src/protocols/ssh/session.ts +0 -879
- package/src/protocols/ssh/terminal.ts +0 -242
- package/src/protocols/ssh/transport.ts +0 -745
- package/src/protocols/ssh/wire.ts +0 -220
- package/src/protocols/types.ts +0 -146
- package/src/protocols/vnc/input.ts +0 -13
- package/src/protocols/vnc/novnc.d.ts +0 -75
- package/src/protocols/vnc/session.ts +0 -367
- package/src/shared/connection.ts +0 -206
- package/src/shared/device.ts +0 -28
- package/src/shared/hosts.ts +0 -240
- package/src/shared/icons.ts +0 -49
- package/src/shared/protocol.ts +0 -55
- package/src/shared/signals.ts +0 -67
package/src/cli/wsserver.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Just enough WebSocket to talk to a browser tab.
|
|
3
|
-
*
|
|
4
|
-
* Node has no WebSocket server, and pulling one in would cost `npx remotectl`
|
|
5
|
-
* its single-download start. What the viewer needs is small: an upgrade
|
|
6
|
-
* handshake, binary frames out, text frames in.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { createHash } from 'node:crypto';
|
|
10
|
-
import type { IncomingMessage } from 'node:http';
|
|
11
|
-
import type net from 'node:net';
|
|
12
|
-
|
|
13
|
-
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
|
14
|
-
|
|
15
|
-
export class WebSocketPeer extends EventTarget {
|
|
16
|
-
#socket: net.Socket;
|
|
17
|
-
#buffer = Buffer.alloc(0);
|
|
18
|
-
/** Stop writing. Set as soon as closing begins, from either end. */
|
|
19
|
-
#closed = false;
|
|
20
|
-
/**
|
|
21
|
-
* Whether the close event has gone out.
|
|
22
|
-
*
|
|
23
|
-
* Kept apart from #closed on purpose: one flag doing both jobs meant a peer
|
|
24
|
-
* that closed itself -- which is what a browser tab does when it goes away --
|
|
25
|
-
* swallowed its own close event, and the session went on sending frames to a
|
|
26
|
-
* viewer that had left.
|
|
27
|
-
*/
|
|
28
|
-
#reported = false;
|
|
29
|
-
|
|
30
|
-
constructor(socket: net.Socket) {
|
|
31
|
-
super();
|
|
32
|
-
this.#socket = socket;
|
|
33
|
-
socket.on('data', (chunk: Buffer) => this.#onData(chunk));
|
|
34
|
-
socket.on('close', () => this.#onClose());
|
|
35
|
-
socket.on('error', () => this.#onClose());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get closed(): boolean { return this.#closed; }
|
|
39
|
-
|
|
40
|
-
send(data: string | Uint8Array): void {
|
|
41
|
-
if (this.#closed) return;
|
|
42
|
-
const isText = typeof data === 'string';
|
|
43
|
-
const payload = isText ? Buffer.from(data, 'utf8') : Buffer.from(data);
|
|
44
|
-
this.#socket.write(frame(isText ? 0x1 : 0x2, payload));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
close(): void {
|
|
48
|
-
if (!this.#closed) {
|
|
49
|
-
this.#closed = true;
|
|
50
|
-
try { this.#socket.end(frame(0x8, Buffer.alloc(0))); } catch { /* already gone */ }
|
|
51
|
-
}
|
|
52
|
-
this.#onClose();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
#onClose(): void {
|
|
56
|
-
this.#closed = true;
|
|
57
|
-
if (this.#reported) return;
|
|
58
|
-
this.#reported = true;
|
|
59
|
-
this.dispatchEvent(new Event('close'));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
#onData(chunk: Buffer): void {
|
|
63
|
-
if (process.env.REMOTECTL_DEBUG) console.log('[ws] data', chunk.length, 'bytes');
|
|
64
|
-
this.#buffer = Buffer.concat([this.#buffer, chunk]);
|
|
65
|
-
|
|
66
|
-
for (;;) {
|
|
67
|
-
const parsed = readFrame(this.#buffer);
|
|
68
|
-
if (!parsed) return;
|
|
69
|
-
this.#buffer = parsed.rest;
|
|
70
|
-
|
|
71
|
-
if (parsed.opcode === 0x8) { this.close(); return; }
|
|
72
|
-
if (parsed.opcode === 0x9) { this.#socket.write(frame(0xa, parsed.payload)); continue; }
|
|
73
|
-
if (parsed.opcode === 0x1) {
|
|
74
|
-
this.dispatchEvent(new MessageEvent('message', { data: parsed.payload.toString('utf8') }));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Server frames are never masked, and the viewer sends nothing large. */
|
|
81
|
-
function frame(opcode: number, payload: Buffer): Buffer {
|
|
82
|
-
const length = payload.length;
|
|
83
|
-
let header: Buffer;
|
|
84
|
-
|
|
85
|
-
if (length < 126) {
|
|
86
|
-
header = Buffer.from([0x80 | opcode, length]);
|
|
87
|
-
} else if (length < 65536) {
|
|
88
|
-
header = Buffer.alloc(4);
|
|
89
|
-
header[0] = 0x80 | opcode;
|
|
90
|
-
header[1] = 126;
|
|
91
|
-
header.writeUInt16BE(length, 2);
|
|
92
|
-
} else {
|
|
93
|
-
header = Buffer.alloc(10);
|
|
94
|
-
header[0] = 0x80 | opcode;
|
|
95
|
-
header[1] = 127;
|
|
96
|
-
header.writeBigUInt64BE(BigInt(length), 2);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return Buffer.concat([header, payload]);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function readFrame(buffer: Buffer) {
|
|
103
|
-
if (buffer.length < 2) return null;
|
|
104
|
-
|
|
105
|
-
const opcode = buffer[0] & 0x0f;
|
|
106
|
-
const masked = (buffer[1] & 0x80) !== 0;
|
|
107
|
-
let length = buffer[1] & 0x7f;
|
|
108
|
-
let offset = 2;
|
|
109
|
-
|
|
110
|
-
if (length === 126) {
|
|
111
|
-
if (buffer.length < 4) return null;
|
|
112
|
-
length = buffer.readUInt16BE(2);
|
|
113
|
-
offset = 4;
|
|
114
|
-
} else if (length === 127) {
|
|
115
|
-
if (buffer.length < 10) return null;
|
|
116
|
-
length = Number(buffer.readBigUInt64BE(2));
|
|
117
|
-
offset = 10;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const maskKey = masked ? buffer.subarray(offset, offset + 4) : null;
|
|
121
|
-
if (masked) offset += 4;
|
|
122
|
-
if (buffer.length < offset + length) return null;
|
|
123
|
-
|
|
124
|
-
const payload = Buffer.from(buffer.subarray(offset, offset + length));
|
|
125
|
-
if (maskKey) for (let i = 0; i < payload.length; i++) payload[i] ^= maskKey[i % 4];
|
|
126
|
-
|
|
127
|
-
return { opcode, payload, rest: buffer.subarray(offset + length) };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Complete the HTTP upgrade a browser sends, and hand back the peer. */
|
|
131
|
-
export function accept(request: IncomingMessage, socket: net.Socket): WebSocketPeer | null {
|
|
132
|
-
const key = request.headers['sec-websocket-key'];
|
|
133
|
-
if (typeof key !== 'string') { socket.destroy(); return null; }
|
|
134
|
-
|
|
135
|
-
const acceptKey = createHash('sha1').update(key + GUID).digest('base64');
|
|
136
|
-
socket.write(
|
|
137
|
-
'HTTP/1.1 101 Switching Protocols\r\n'
|
|
138
|
-
+ 'Upgrade: websocket\r\n'
|
|
139
|
-
+ 'Connection: Upgrade\r\n'
|
|
140
|
-
+ `Sec-WebSocket-Accept: ${acceptKey}\r\n\r\n`
|
|
141
|
-
);
|
|
142
|
-
socket.setNoDelay(true);
|
|
143
|
-
return new WebSocketPeer(socket);
|
|
144
|
-
}
|
package/src/protocols/index.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The protocol registry the workspace talks to.
|
|
3
|
-
*
|
|
4
|
-
* The UI owns the toolbar, the dialogs, the stats rail and the on-screen
|
|
5
|
-
* keyboard, none of which care whether a session is VNC or RDP. Everything that
|
|
6
|
-
* does differ -- the endpoint, how a client is constructed, the wording of a
|
|
7
|
-
* failure, the labels in the connect dialog -- is looked up here instead of
|
|
8
|
-
* branching at each site.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { rdpDriver } from './rdp/session';
|
|
12
|
-
import { vncDriver } from './vnc/session';
|
|
13
|
-
import type { DisconnectContext, ProtocolDriver } from './types';
|
|
14
|
-
import type { RemoteProtocol } from '../shared/protocol';
|
|
15
|
-
|
|
16
|
-
export type { ProtocolDriver, RemoteSession, SessionOptions, ScreenGeometry } from './types';
|
|
17
|
-
|
|
18
|
-
export const DRIVERS: Record<RemoteProtocol, ProtocolDriver> = {
|
|
19
|
-
vnc: vncDriver,
|
|
20
|
-
rdp: rdpDriver,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/** Unknown names fall back to VNC, which is what the dialog opens on. */
|
|
24
|
-
export function driverFor(protocol: string | undefined): ProtocolDriver {
|
|
25
|
-
return DRIVERS[protocol as RemoteProtocol] ?? DRIVERS.vnc;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** What the workspace knows about a session that has just ended. */
|
|
29
|
-
export interface DisconnectDiagnostic {
|
|
30
|
-
protocol: RemoteProtocol;
|
|
31
|
-
/** The transport close, when one arrived. A 1006 means it did not. */
|
|
32
|
-
close: { code: number; reason: string } | null;
|
|
33
|
-
/** True once the session had reached the desktop. */
|
|
34
|
-
established: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Explain a disconnect in terms someone can act on. The protocol gets first
|
|
39
|
-
* say, then the close codes that mean the same thing either way.
|
|
40
|
-
*/
|
|
41
|
-
export function explainDisconnect(
|
|
42
|
-
diagnostic: DisconnectDiagnostic,
|
|
43
|
-
clean: boolean,
|
|
44
|
-
protocolMessage: string
|
|
45
|
-
): string {
|
|
46
|
-
// A client that reports its own failure states it in the protocol's terms,
|
|
47
|
-
// which is always more specific than anything the transport can say.
|
|
48
|
-
if (protocolMessage) return protocolMessage;
|
|
49
|
-
|
|
50
|
-
const code = diagnostic.close?.code;
|
|
51
|
-
const reason = diagnostic.close?.reason.trim() ?? '';
|
|
52
|
-
const suffix = code ? ` (WebSocket ${code}${reason ? `: ${reason}` : ''})` : '';
|
|
53
|
-
const context: DisconnectContext = { ...(code === undefined ? {} : { code }), reason, suffix };
|
|
54
|
-
|
|
55
|
-
const driver = driverFor(diagnostic.protocol);
|
|
56
|
-
const specific = driver.describeDisconnect(context);
|
|
57
|
-
if (specific) return specific;
|
|
58
|
-
|
|
59
|
-
const name = driver.protocol.toUpperCase();
|
|
60
|
-
if (/tcp (reached )?eof/i.test(reason)) {
|
|
61
|
-
return `The ${name} host accepted the connection and then closed it${suffix}. `
|
|
62
|
-
+ 'Check its remote-access permissions and the account name.';
|
|
63
|
-
}
|
|
64
|
-
if (code === 1006) {
|
|
65
|
-
return 'The connection was interrupted before a close reason arrived (WebSocket 1006). '
|
|
66
|
-
+ 'This can happen during a deployment or a brief network change. Automatic reconnect '
|
|
67
|
-
+ 'did not restore the session; reconnect once, then check Worker live logs if it repeats.';
|
|
68
|
-
}
|
|
69
|
-
if (reason) return `The remote session ended${suffix}.`;
|
|
70
|
-
if (clean) return `The remote server closed the session cleanly${suffix || ' (no reason supplied)'}.`;
|
|
71
|
-
return `The ${name} negotiation failed${suffix || ' before the server supplied a close reason'}.`;
|
|
72
|
-
}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
// Binary reader/writer used by every RDP layer. RDP mixes endianness freely --
|
|
2
|
-
// TPKT and MCS are big endian, everything above them is little endian -- so the
|
|
3
|
-
// byte order is part of each call rather than a mode on the object.
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Plain `Uint8Array` throughout. Pinning the buffer type parameter would be
|
|
7
|
-
* more precise, but every layer above this one passes byte slices around and
|
|
8
|
-
* the variance between `ArrayBuffer` and `ArrayBufferLike` then has to be
|
|
9
|
-
* restated at each boundary for no benefit the protocol code can use.
|
|
10
|
-
*/
|
|
11
|
-
function allocate(length: number): Uint8Array {
|
|
12
|
-
return new Uint8Array(length);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class Writer {
|
|
16
|
-
private bytes: Uint8Array;
|
|
17
|
-
private view: DataView;
|
|
18
|
-
private offset = 0;
|
|
19
|
-
|
|
20
|
-
constructor(capacity = 512) {
|
|
21
|
-
this.bytes = allocate(capacity);
|
|
22
|
-
this.view = new DataView(this.bytes.buffer);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
#ensure(extra: number): void {
|
|
26
|
-
if (this.offset + extra <= this.bytes.length) return;
|
|
27
|
-
let capacity = this.bytes.length * 2;
|
|
28
|
-
while (capacity < this.offset + extra) capacity *= 2;
|
|
29
|
-
const grown = allocate(capacity);
|
|
30
|
-
grown.set(this.bytes.subarray(0, this.offset));
|
|
31
|
-
this.bytes = grown;
|
|
32
|
-
this.view = new DataView(grown.buffer);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
u8(value: number): this {
|
|
36
|
-
this.#ensure(1);
|
|
37
|
-
this.view.setUint8(this.offset, value);
|
|
38
|
-
this.offset += 1;
|
|
39
|
-
return this;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
u16le(value: number): this {
|
|
43
|
-
this.#ensure(2);
|
|
44
|
-
this.view.setUint16(this.offset, value, true);
|
|
45
|
-
this.offset += 2;
|
|
46
|
-
return this;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
u16be(value: number): this {
|
|
50
|
-
this.#ensure(2);
|
|
51
|
-
this.view.setUint16(this.offset, value, false);
|
|
52
|
-
this.offset += 2;
|
|
53
|
-
return this;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
u32le(value: number): this {
|
|
57
|
-
this.#ensure(4);
|
|
58
|
-
this.view.setUint32(this.offset, value >>> 0, true);
|
|
59
|
-
this.offset += 4;
|
|
60
|
-
return this;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
u32be(value: number): this {
|
|
64
|
-
this.#ensure(4);
|
|
65
|
-
this.view.setUint32(this.offset, value >>> 0, false);
|
|
66
|
-
this.offset += 4;
|
|
67
|
-
return this;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
raw(bytes: Uint8Array): this {
|
|
71
|
-
this.#ensure(bytes.length);
|
|
72
|
-
this.bytes.set(bytes, this.offset);
|
|
73
|
-
this.offset += bytes.length;
|
|
74
|
-
return this;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
zeros(count: number): this {
|
|
78
|
-
this.#ensure(count);
|
|
79
|
-
this.offset += count;
|
|
80
|
-
return this;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** UTF-16LE, optionally padded or truncated to a fixed byte width. */
|
|
84
|
-
utf16le(text: string, fixedBytes?: number): this {
|
|
85
|
-
const start = this.offset;
|
|
86
|
-
for (const unit of text) {
|
|
87
|
-
const code = unit.codePointAt(0) ?? 0;
|
|
88
|
-
// Fixed-width fields must keep room for the terminating NUL.
|
|
89
|
-
if (fixedBytes !== undefined && this.offset - start >= fixedBytes - 2) break;
|
|
90
|
-
if (code > 0xffff) {
|
|
91
|
-
// Surrogate pair: both halves are already what UTF-16 needs.
|
|
92
|
-
for (let i = 0; i < unit.length; i++) this.u16le(unit.charCodeAt(i));
|
|
93
|
-
} else {
|
|
94
|
-
this.u16le(code);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (fixedBytes !== undefined) {
|
|
98
|
-
this.zeros(fixedBytes - (this.offset - start));
|
|
99
|
-
}
|
|
100
|
-
return this;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Overwrite an earlier 16-bit slot, for length fields known only later. */
|
|
104
|
-
patchU16le(position: number, value: number): this {
|
|
105
|
-
this.view.setUint16(position, value, true);
|
|
106
|
-
return this;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
patchU16be(position: number, value: number): this {
|
|
110
|
-
this.view.setUint16(position, value, false);
|
|
111
|
-
return this;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
get length(): number {
|
|
115
|
-
return this.offset;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
take(): Uint8Array {
|
|
119
|
-
return this.bytes.slice(0, this.offset);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export class Reader {
|
|
124
|
-
private readonly bytes: Uint8Array;
|
|
125
|
-
private readonly view: DataView;
|
|
126
|
-
offset: number;
|
|
127
|
-
|
|
128
|
-
constructor(bytes: Uint8Array, offset = 0) {
|
|
129
|
-
this.bytes = bytes;
|
|
130
|
-
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
131
|
-
this.offset = offset;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
get remaining(): number {
|
|
135
|
-
return this.bytes.length - this.offset;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
#need(count: number): void {
|
|
139
|
-
if (this.remaining < count) {
|
|
140
|
-
throw new Error(`RDP: truncated frame (needed ${count} bytes, ${this.remaining} left)`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
u8(): number {
|
|
145
|
-
this.#need(1);
|
|
146
|
-
return this.view.getUint8(this.offset++);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
u16le(): number {
|
|
150
|
-
this.#need(2);
|
|
151
|
-
const value = this.view.getUint16(this.offset, true);
|
|
152
|
-
this.offset += 2;
|
|
153
|
-
return value;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
u16be(): number {
|
|
157
|
-
this.#need(2);
|
|
158
|
-
const value = this.view.getUint16(this.offset, false);
|
|
159
|
-
this.offset += 2;
|
|
160
|
-
return value;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
u32le(): number {
|
|
164
|
-
this.#need(4);
|
|
165
|
-
const value = this.view.getUint32(this.offset, true);
|
|
166
|
-
this.offset += 4;
|
|
167
|
-
return value;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
u32be(): number {
|
|
171
|
-
this.#need(4);
|
|
172
|
-
const value = this.view.getUint32(this.offset, false);
|
|
173
|
-
this.offset += 4;
|
|
174
|
-
return value;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
raw(count: number): Uint8Array {
|
|
178
|
-
this.#need(count);
|
|
179
|
-
const slice = this.bytes.subarray(this.offset, this.offset + count);
|
|
180
|
-
this.offset += count;
|
|
181
|
-
return slice;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
skip(count: number): this {
|
|
185
|
-
this.#need(count);
|
|
186
|
-
this.offset += count;
|
|
187
|
-
return this;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
rest(): Uint8Array {
|
|
191
|
-
return this.bytes.subarray(this.offset);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function concatBytes(...parts: readonly Uint8Array[]): Uint8Array {
|
|
196
|
-
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
197
|
-
const out = allocate(total);
|
|
198
|
-
let offset = 0;
|
|
199
|
-
for (const part of parts) {
|
|
200
|
-
out.set(part, offset);
|
|
201
|
-
offset += part.length;
|
|
202
|
-
}
|
|
203
|
-
return out;
|
|
204
|
-
}
|