@airterm/protocol 0.2.0 → 0.3.0
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/README.md +23 -11
- package/dist/direct-negotiation.d.ts +40 -0
- package/dist/direct-negotiation.d.ts.map +1 -0
- package/dist/direct-negotiation.js +169 -0
- package/dist/direct-negotiation.js.map +1 -0
- package/dist/framing.d.ts +4 -1
- package/dist/framing.d.ts.map +1 -1
- package/dist/framing.js +3 -0
- package/dist/framing.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +11 -6
- package/dist/messages.js.map +1 -1
- package/dist/outbound-queue.d.ts +16 -0
- package/dist/outbound-queue.d.ts.map +1 -0
- package/dist/outbound-queue.js +139 -0
- package/dist/outbound-queue.js.map +1 -0
- package/dist/p2p.d.ts +37 -0
- package/dist/p2p.d.ts.map +1 -0
- package/dist/p2p.js +91 -0
- package/dist/p2p.js.map +1 -0
- package/dist/peer-transport.d.ts +31 -0
- package/dist/peer-transport.d.ts.map +1 -0
- package/dist/peer-transport.js +155 -0
- package/dist/peer-transport.js.map +1 -0
- package/dist/transport-handoff.d.ts +25 -0
- package/dist/transport-handoff.d.ts.map +1 -0
- package/dist/transport-handoff.js +207 -0
- package/dist/transport-handoff.js.map +1 -0
- package/dist/transport.d.ts +38 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +85 -0
- package/dist/transport.js.map +1 -0
- package/dist/webrtc-transport.d.ts +14 -0
- package/dist/webrtc-transport.d.ts.map +1 -0
- package/dist/webrtc-transport.js +106 -0
- package/dist/webrtc-transport.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { decodeInnerFrame, encodeInnerFrame, } from './framing.js';
|
|
2
|
+
import { NoiseTransport } from './noise.js';
|
|
3
|
+
import { OutboundQueue } from './outbound-queue.js';
|
|
4
|
+
export class WebSocketTransport {
|
|
5
|
+
buffered;
|
|
6
|
+
send;
|
|
7
|
+
stop;
|
|
8
|
+
constructor(buffered, send, stop) {
|
|
9
|
+
this.buffered = buffered;
|
|
10
|
+
this.send = send;
|
|
11
|
+
this.stop = stop;
|
|
12
|
+
}
|
|
13
|
+
get bufferedAmount() {
|
|
14
|
+
return this.buffered();
|
|
15
|
+
}
|
|
16
|
+
write(bytes, done) {
|
|
17
|
+
this.send(bytes, done);
|
|
18
|
+
}
|
|
19
|
+
close() {
|
|
20
|
+
this.stop();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Owns ciphers and encrypts only when the bounded scheduler actually writes. */
|
|
24
|
+
export class EncryptedConnection {
|
|
25
|
+
transport;
|
|
26
|
+
options;
|
|
27
|
+
events;
|
|
28
|
+
#noise;
|
|
29
|
+
#queue;
|
|
30
|
+
#closed = false;
|
|
31
|
+
constructor(transport, options, events) {
|
|
32
|
+
this.transport = transport;
|
|
33
|
+
this.options = options;
|
|
34
|
+
this.events = events;
|
|
35
|
+
this.#noise = new NoiseTransport(options);
|
|
36
|
+
this.#queue = new OutboundQueue(() => transport.bufferedAmount, (frame, done) => transport.write(this.#noise.encrypt(encodeInnerFrame(frame)), done), (paused) => events.onBackpressure?.(paused), (error) => {
|
|
37
|
+
this.close();
|
|
38
|
+
events.onFailure(error);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
get complete() {
|
|
42
|
+
return !this.#closed && this.#noise.complete;
|
|
43
|
+
}
|
|
44
|
+
get pendingBytes() {
|
|
45
|
+
return this.#queue.pendingBytes;
|
|
46
|
+
}
|
|
47
|
+
start() {
|
|
48
|
+
if (this.options.initiator && !this.#closed)
|
|
49
|
+
this.#writeHandshake();
|
|
50
|
+
}
|
|
51
|
+
receive(bytes) {
|
|
52
|
+
if (this.#closed)
|
|
53
|
+
return;
|
|
54
|
+
if (!this.#noise.complete) {
|
|
55
|
+
this.#noise.readHandshake(bytes);
|
|
56
|
+
if (!this.options.initiator)
|
|
57
|
+
this.#writeHandshake();
|
|
58
|
+
if (this.complete)
|
|
59
|
+
this.events.onSecure?.();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
this.events.onFrame?.(decodeInnerFrame(this.#noise.decrypt(bytes)));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
send(frame) {
|
|
66
|
+
if (!this.complete)
|
|
67
|
+
return Promise.reject(new Error('Encrypted transport is not ready.'));
|
|
68
|
+
return this.#queue.send(frame);
|
|
69
|
+
}
|
|
70
|
+
close() {
|
|
71
|
+
if (this.#closed)
|
|
72
|
+
return;
|
|
73
|
+
this.#closed = true;
|
|
74
|
+
this.#queue.close();
|
|
75
|
+
}
|
|
76
|
+
#writeHandshake() {
|
|
77
|
+
this.transport.write(this.#noise.writeHandshake(), (error) => {
|
|
78
|
+
if (error && !this.#closed) {
|
|
79
|
+
this.close();
|
|
80
|
+
this.events.onFailure(error);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BinaryTransport } from './transport.js';
|
|
2
|
+
/** Ordered fragmentation is below encryption; only complete bounded frames escape. */
|
|
3
|
+
export declare class WebRTCTransport implements BinaryTransport {
|
|
4
|
+
#private;
|
|
5
|
+
private readonly channel;
|
|
6
|
+
private readonly receiveFrame;
|
|
7
|
+
private readonly failure;
|
|
8
|
+
constructor(channel: BinaryTransport, receiveFrame: (bytes: Uint8Array) => void, failure: (error: Error) => void);
|
|
9
|
+
get bufferedAmount(): number;
|
|
10
|
+
write(bytes: Uint8Array, done: (error?: Error | null) => void): void;
|
|
11
|
+
receive(fragment: Uint8Array): void;
|
|
12
|
+
close(): void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=webrtc-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webrtc-transport.d.ts","sourceRoot":"","sources":["../src/webrtc-transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMtD,sFAAsF;AACtF,qBAAa,eAAgB,YAAW,eAAe;;IAO/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAFP,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EACzC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI;IAEpD,IAAW,cAAc,IAAI,MAAM,CAElC;IACM,KAAK,CACR,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GACrC,IAAI;IAqCA,OAAO,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI;IAkDnC,KAAK,IAAI,IAAI;CAavB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { MAX_CIPHERTEXT_BYTES } from './framing.js';
|
|
2
|
+
const MESSAGE_BYTES = 16 * 1024;
|
|
3
|
+
const HEADER_BYTES = 8;
|
|
4
|
+
const FRAME_BYTES = MAX_CIPHERTEXT_BYTES + 2;
|
|
5
|
+
/** Ordered fragmentation is below encryption; only complete bounded frames escape. */
|
|
6
|
+
export class WebRTCTransport {
|
|
7
|
+
channel;
|
|
8
|
+
receiveFrame;
|
|
9
|
+
failure;
|
|
10
|
+
#assembly;
|
|
11
|
+
#offset = 0;
|
|
12
|
+
#timer;
|
|
13
|
+
#closed = false;
|
|
14
|
+
#writing = false;
|
|
15
|
+
constructor(channel, receiveFrame, failure) {
|
|
16
|
+
this.channel = channel;
|
|
17
|
+
this.receiveFrame = receiveFrame;
|
|
18
|
+
this.failure = failure;
|
|
19
|
+
}
|
|
20
|
+
get bufferedAmount() {
|
|
21
|
+
return this.channel.bufferedAmount;
|
|
22
|
+
}
|
|
23
|
+
write(bytes, done) {
|
|
24
|
+
if (this.#closed ||
|
|
25
|
+
this.#writing ||
|
|
26
|
+
bytes.length < 2 ||
|
|
27
|
+
bytes.length > FRAME_BYTES) {
|
|
28
|
+
done(new Error('Invalid direct transport write.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.#writing = true;
|
|
32
|
+
let offset = 0;
|
|
33
|
+
const next = (error) => {
|
|
34
|
+
if (error || this.#closed) {
|
|
35
|
+
this.#writing = false;
|
|
36
|
+
done(error ?? new Error('Direct transport closed.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (offset === bytes.length) {
|
|
40
|
+
this.#writing = false;
|
|
41
|
+
done();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const end = Math.min(bytes.length, offset + MESSAGE_BYTES - HEADER_BYTES);
|
|
45
|
+
const fragment = new Uint8Array(HEADER_BYTES + end - offset);
|
|
46
|
+
const header = new DataView(fragment.buffer);
|
|
47
|
+
header.setUint32(0, bytes.length);
|
|
48
|
+
header.setUint32(4, offset);
|
|
49
|
+
fragment.set(bytes.subarray(offset, end), HEADER_BYTES);
|
|
50
|
+
offset = end;
|
|
51
|
+
this.channel.write(fragment, next);
|
|
52
|
+
};
|
|
53
|
+
next();
|
|
54
|
+
}
|
|
55
|
+
receive(fragment) {
|
|
56
|
+
if (this.#closed)
|
|
57
|
+
return;
|
|
58
|
+
try {
|
|
59
|
+
if (fragment.length <= HEADER_BYTES ||
|
|
60
|
+
fragment.length > MESSAGE_BYTES)
|
|
61
|
+
throw new Error('Invalid direct fragment size.');
|
|
62
|
+
const view = new DataView(fragment.buffer, fragment.byteOffset, fragment.byteLength);
|
|
63
|
+
const length = view.getUint32(0);
|
|
64
|
+
const offset = view.getUint32(4);
|
|
65
|
+
if (length < 2 || length > FRAME_BYTES || offset !== this.#offset)
|
|
66
|
+
throw new Error('Invalid direct fragment offset.');
|
|
67
|
+
if (this.#assembly === undefined) {
|
|
68
|
+
this.#assembly = new Uint8Array(length);
|
|
69
|
+
this.#timer = setTimeout(() => this.#fail(new Error('Direct frame assembly timed out.')), 10_000);
|
|
70
|
+
}
|
|
71
|
+
if (length !== this.#assembly.length ||
|
|
72
|
+
offset + fragment.length - HEADER_BYTES > length)
|
|
73
|
+
throw new Error('Invalid direct fragment length.');
|
|
74
|
+
this.#assembly.set(fragment.subarray(HEADER_BYTES), offset);
|
|
75
|
+
this.#offset += fragment.length - HEADER_BYTES;
|
|
76
|
+
if (this.#offset === length) {
|
|
77
|
+
const frame = this.#assembly;
|
|
78
|
+
this.#assembly = undefined;
|
|
79
|
+
this.#offset = 0;
|
|
80
|
+
clearTimeout(this.#timer);
|
|
81
|
+
this.#timer = undefined;
|
|
82
|
+
this.receiveFrame(frame);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.#fail(error instanceof Error
|
|
87
|
+
? error
|
|
88
|
+
: new Error('Direct transport failed.'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
close() {
|
|
92
|
+
if (this.#closed)
|
|
93
|
+
return;
|
|
94
|
+
this.#closed = true;
|
|
95
|
+
clearTimeout(this.#timer);
|
|
96
|
+
this.#timer = undefined;
|
|
97
|
+
this.#assembly = undefined;
|
|
98
|
+
this.#offset = 0;
|
|
99
|
+
this.channel.close();
|
|
100
|
+
}
|
|
101
|
+
#fail(error) {
|
|
102
|
+
this.close();
|
|
103
|
+
this.failure(error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=webrtc-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webrtc-transport.js","sourceRoot":"","sources":["../src/webrtc-transport.ts"],"names":[],"mappings":"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"}
|