node-rtc-connection 2.0.4 → 2.0.6
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/index.cjs +1 -0
- package/index.mjs +1 -0
- package/package.json +10 -47
- package/{dist/types → types}/stun/stun-client.d.ts +2 -0
- package/dist/index.cjs +0 -32
- package/dist/index.mjs +0 -43
- package/src/crypto/der.ts +0 -205
- package/src/crypto/x509.ts +0 -146
- package/src/datachannel/RTCDataChannel.ts +0 -388
- package/src/dtls/RTCCertificate.ts +0 -396
- package/src/dtls/cipher.ts +0 -198
- package/src/dtls/connection.ts +0 -974
- package/src/dtls/prf.ts +0 -62
- package/src/dtls/protocol.ts +0 -204
- package/src/foundation/ByteBufferQueue.ts +0 -237
- package/src/foundation/RTCError.ts +0 -276
- package/src/ice/RTCIceCandidate.ts +0 -349
- package/src/ice/ice-agent.ts +0 -609
- package/src/ice/stun-message.ts +0 -260
- package/src/index.ts +0 -72
- package/src/peerconnection/RTCPeerConnection.ts +0 -430
- package/src/sctp/association.ts +0 -523
- package/src/sctp/chunks.ts +0 -350
- package/src/sctp/crc32c.ts +0 -57
- package/src/sctp/datachannel-manager.ts +0 -187
- package/src/sctp/dcep.ts +0 -94
- package/src/sdp/RTCSessionDescription.ts +0 -115
- package/src/sdp/sdp-utils.ts +0 -229
- package/src/stun/stun-client.ts +0 -936
- package/src/transport-stack.ts +0 -165
- /package/{dist/types → types}/crypto/der.d.ts +0 -0
- /package/{dist/types → types}/crypto/x509.d.ts +0 -0
- /package/{dist/types → types}/datachannel/RTCDataChannel.d.ts +0 -0
- /package/{dist/types → types}/dtls/RTCCertificate.d.ts +0 -0
- /package/{dist/types → types}/dtls/cipher.d.ts +0 -0
- /package/{dist/types → types}/dtls/connection.d.ts +0 -0
- /package/{dist/types → types}/dtls/prf.d.ts +0 -0
- /package/{dist/types → types}/dtls/protocol.d.ts +0 -0
- /package/{dist/types → types}/foundation/ByteBufferQueue.d.ts +0 -0
- /package/{dist/types → types}/foundation/RTCError.d.ts +0 -0
- /package/{dist/types → types}/ice/RTCIceCandidate.d.ts +0 -0
- /package/{dist/types → types}/ice/ice-agent.d.ts +0 -0
- /package/{dist/types → types}/ice/stun-message.d.ts +0 -0
- /package/{dist/types → types}/index.d.ts +0 -0
- /package/{dist/types → types}/peerconnection/RTCPeerConnection.d.ts +0 -0
- /package/{dist/types → types}/sctp/association.d.ts +0 -0
- /package/{dist/types → types}/sctp/chunks.d.ts +0 -0
- /package/{dist/types → types}/sctp/crc32c.d.ts +0 -0
- /package/{dist/types → types}/sctp/datachannel-manager.d.ts +0 -0
- /package/{dist/types → types}/sctp/dcep.d.ts +0 -0
- /package/{dist/types → types}/sdp/RTCSessionDescription.d.ts +0 -0
- /package/{dist/types → types}/sdp/sdp-utils.d.ts +0 -0
- /package/{dist/types → types}/transport-stack.d.ts +0 -0
package/src/sctp/chunks.ts
DELETED
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file chunks.ts
|
|
3
|
-
* @description SCTP common header and chunk encode/parse (RFC 4960 + RFC 8260
|
|
4
|
-
* for I-DATA is NOT used; classic DATA only). Scoped to the WebRTC profile.
|
|
5
|
-
* @module sctp/chunks
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
export const CHUNK_TYPE = Object.freeze({
|
|
11
|
-
DATA: 0,
|
|
12
|
-
INIT: 1,
|
|
13
|
-
INIT_ACK: 2,
|
|
14
|
-
SACK: 3,
|
|
15
|
-
HEARTBEAT: 4,
|
|
16
|
-
HEARTBEAT_ACK: 5,
|
|
17
|
-
ABORT: 6,
|
|
18
|
-
SHUTDOWN: 7,
|
|
19
|
-
SHUTDOWN_ACK: 8,
|
|
20
|
-
ERROR: 9,
|
|
21
|
-
COOKIE_ECHO: 10,
|
|
22
|
-
COOKIE_ACK: 11,
|
|
23
|
-
SHUTDOWN_COMPLETE: 14,
|
|
24
|
-
FORWARD_TSN: 192, // 0xC0
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
export const PARAM_TYPE = Object.freeze({
|
|
28
|
-
HEARTBEAT_INFO: 1,
|
|
29
|
-
STATE_COOKIE: 7,
|
|
30
|
-
UNRECOGNIZED_PARAM: 8,
|
|
31
|
-
COOKIE_PRESERVATIVE: 9,
|
|
32
|
-
SUPPORTED_ADDR_TYPES: 11,
|
|
33
|
-
// RFC 8260 / RFC 3758
|
|
34
|
-
FORWARD_TSN_SUPPORTED: 49152, // 0xC000
|
|
35
|
-
SUPPORTED_EXTENSIONS: 32776, // 0x8008
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Payload Protocol Identifiers used by WebRTC data channels (RFC 8831).
|
|
39
|
-
export const PPID = Object.freeze({
|
|
40
|
-
DCEP: 50,
|
|
41
|
-
STRING: 51,
|
|
42
|
-
BINARY: 53,
|
|
43
|
-
STRING_EMPTY: 56,
|
|
44
|
-
BINARY_EMPTY: 57,
|
|
45
|
-
STRING_PARTIAL: 54, // deprecated
|
|
46
|
-
BINARY_PARTIAL: 52, // deprecated
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export interface CommonHeader {
|
|
50
|
-
srcPort: number;
|
|
51
|
-
dstPort: number;
|
|
52
|
-
verificationTag: number;
|
|
53
|
-
checksum: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface ParsedChunk {
|
|
57
|
-
type: number;
|
|
58
|
-
flags: number;
|
|
59
|
-
length: number;
|
|
60
|
-
body: Buffer;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface ParsedParam {
|
|
64
|
-
type: number;
|
|
65
|
-
length: number;
|
|
66
|
-
value: Buffer;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface InitBodyParams {
|
|
70
|
-
initiateTag: number;
|
|
71
|
-
a_rwnd: number;
|
|
72
|
-
outStreams: number;
|
|
73
|
-
inStreams: number;
|
|
74
|
-
initialTSN: number;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface ParsedInitBody {
|
|
78
|
-
initiateTag: number;
|
|
79
|
-
a_rwnd: number;
|
|
80
|
-
outStreams: number;
|
|
81
|
-
inStreams: number;
|
|
82
|
-
initialTSN: number;
|
|
83
|
-
params: ParsedParam[];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface DataBodyParams {
|
|
87
|
-
tsn: number;
|
|
88
|
-
streamId: number;
|
|
89
|
-
streamSeq: number;
|
|
90
|
-
ppid: number;
|
|
91
|
-
userData: Buffer;
|
|
92
|
-
unordered?: boolean;
|
|
93
|
-
beginning?: boolean;
|
|
94
|
-
ending?: boolean;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface EncodedDataBody {
|
|
98
|
-
flags: number;
|
|
99
|
-
body: Buffer;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export interface ParsedDataBody {
|
|
103
|
-
unordered: boolean;
|
|
104
|
-
beginning: boolean;
|
|
105
|
-
ending: boolean;
|
|
106
|
-
tsn: number;
|
|
107
|
-
streamId: number;
|
|
108
|
-
streamSeq: number;
|
|
109
|
-
ppid: number;
|
|
110
|
-
userData: Buffer;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface SackBodyParams {
|
|
114
|
-
cumulativeTSNAck: number;
|
|
115
|
-
a_rwnd: number;
|
|
116
|
-
gapBlocks?: Array<[number, number]>;
|
|
117
|
-
dupTSNs?: number[];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface ParsedSackBody {
|
|
121
|
-
cumulativeTSNAck: number;
|
|
122
|
-
a_rwnd: number;
|
|
123
|
-
gapBlocks: Array<[number, number]>;
|
|
124
|
-
dupTSNs: number[];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** Round a length up to the next 4-byte boundary. */
|
|
128
|
-
export function pad4(n: number): number {
|
|
129
|
-
return (n + 3) & ~3;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Encode the 12-byte SCTP common header (checksum left as 0; filled by crc32c).
|
|
134
|
-
* @param {number} srcPort
|
|
135
|
-
* @param {number} dstPort
|
|
136
|
-
* @param {number} verificationTag
|
|
137
|
-
* @returns {Buffer}
|
|
138
|
-
*/
|
|
139
|
-
export function encodeCommonHeader(srcPort: number, dstPort: number, verificationTag: number): Buffer {
|
|
140
|
-
const h = Buffer.alloc(12);
|
|
141
|
-
h.writeUInt16BE(srcPort, 0);
|
|
142
|
-
h.writeUInt16BE(dstPort, 2);
|
|
143
|
-
h.writeUInt32BE(verificationTag >>> 0, 4);
|
|
144
|
-
h.writeUInt32BE(0, 8); // checksum placeholder
|
|
145
|
-
return h;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Parse the common header.
|
|
150
|
-
* @param {Buffer} packet
|
|
151
|
-
* @returns {{srcPort:number,dstPort:number,verificationTag:number,checksum:number}}
|
|
152
|
-
*/
|
|
153
|
-
export function parseCommonHeader(packet: Buffer): CommonHeader {
|
|
154
|
-
return {
|
|
155
|
-
srcPort: packet.readUInt16BE(0),
|
|
156
|
-
dstPort: packet.readUInt16BE(2),
|
|
157
|
-
verificationTag: packet.readUInt32BE(4),
|
|
158
|
-
checksum: packet.readUInt32LE(8),
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Wrap a chunk body with the 4-byte chunk header, padded to 4 bytes.
|
|
164
|
-
* @param {number} type
|
|
165
|
-
* @param {number} flags
|
|
166
|
-
* @param {Buffer} body
|
|
167
|
-
* @returns {Buffer}
|
|
168
|
-
*/
|
|
169
|
-
export function encodeChunk(type: number, flags: number, body: Buffer): Buffer {
|
|
170
|
-
const len = 4 + body.length;
|
|
171
|
-
const out = Buffer.alloc(pad4(len));
|
|
172
|
-
out.writeUInt8(type, 0);
|
|
173
|
-
out.writeUInt8(flags, 1);
|
|
174
|
-
out.writeUInt16BE(len, 2); // length excludes padding
|
|
175
|
-
body.copy(out, 4);
|
|
176
|
-
return out;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Parse all chunks out of an SCTP packet (after the 12-byte common header).
|
|
181
|
-
* @param {Buffer} packet
|
|
182
|
-
* @returns {Array<{type:number,flags:number,length:number,body:Buffer}>}
|
|
183
|
-
*/
|
|
184
|
-
export function parseChunks(packet: Buffer): ParsedChunk[] {
|
|
185
|
-
const chunks: ParsedChunk[] = [];
|
|
186
|
-
let off = 12;
|
|
187
|
-
while (off + 4 <= packet.length) {
|
|
188
|
-
const type = packet.readUInt8(off);
|
|
189
|
-
const flags = packet.readUInt8(off + 1);
|
|
190
|
-
const length = packet.readUInt16BE(off + 2);
|
|
191
|
-
if (length < 4 || off + length > packet.length) break;
|
|
192
|
-
const body = packet.slice(off + 4, off + length);
|
|
193
|
-
chunks.push({ type, flags, length, body });
|
|
194
|
-
off += pad4(length);
|
|
195
|
-
}
|
|
196
|
-
return chunks;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Encode a TLV parameter, padded to 4 bytes.
|
|
201
|
-
* @param {number} type
|
|
202
|
-
* @param {Buffer} value
|
|
203
|
-
* @returns {Buffer}
|
|
204
|
-
*/
|
|
205
|
-
export function encodeParam(type: number, value: Buffer): Buffer {
|
|
206
|
-
const len = 4 + value.length;
|
|
207
|
-
const out = Buffer.alloc(pad4(len));
|
|
208
|
-
out.writeUInt16BE(type, 0);
|
|
209
|
-
out.writeUInt16BE(len, 2);
|
|
210
|
-
value.copy(out, 4);
|
|
211
|
-
return out;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Parse TLV parameters from a buffer.
|
|
216
|
-
* @param {Buffer} buf
|
|
217
|
-
* @returns {Array<{type:number,length:number,value:Buffer}>}
|
|
218
|
-
*/
|
|
219
|
-
export function parseParams(buf: Buffer): ParsedParam[] {
|
|
220
|
-
const params: ParsedParam[] = [];
|
|
221
|
-
let off = 0;
|
|
222
|
-
while (off + 4 <= buf.length) {
|
|
223
|
-
const type = buf.readUInt16BE(off);
|
|
224
|
-
const length = buf.readUInt16BE(off + 2);
|
|
225
|
-
if (length < 4 || off + length > buf.length) break;
|
|
226
|
-
params.push({ type, length, value: buf.slice(off + 4, off + length) });
|
|
227
|
-
off += pad4(length);
|
|
228
|
-
}
|
|
229
|
-
return params;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Build an INIT or INIT_ACK fixed body (without parameters).
|
|
234
|
-
* @param {Object} p
|
|
235
|
-
* @param {number} p.initiateTag
|
|
236
|
-
* @param {number} p.a_rwnd - advertised receiver window
|
|
237
|
-
* @param {number} p.outStreams
|
|
238
|
-
* @param {number} p.inStreams
|
|
239
|
-
* @param {number} p.initialTSN
|
|
240
|
-
* @returns {Buffer}
|
|
241
|
-
*/
|
|
242
|
-
export function encodeInitBody({ initiateTag, a_rwnd, outStreams, inStreams, initialTSN }: InitBodyParams): Buffer {
|
|
243
|
-
const b = Buffer.alloc(16);
|
|
244
|
-
b.writeUInt32BE(initiateTag >>> 0, 0);
|
|
245
|
-
b.writeUInt32BE(a_rwnd >>> 0, 4);
|
|
246
|
-
b.writeUInt16BE(outStreams, 8);
|
|
247
|
-
b.writeUInt16BE(inStreams, 10);
|
|
248
|
-
b.writeUInt32BE(initialTSN >>> 0, 12);
|
|
249
|
-
return b;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Parse an INIT/INIT_ACK body.
|
|
254
|
-
* @param {Buffer} body
|
|
255
|
-
* @returns {{initiateTag:number,a_rwnd:number,outStreams:number,inStreams:number,initialTSN:number,params:Array}}
|
|
256
|
-
*/
|
|
257
|
-
export function parseInitBody(body: Buffer): ParsedInitBody {
|
|
258
|
-
return {
|
|
259
|
-
initiateTag: body.readUInt32BE(0),
|
|
260
|
-
a_rwnd: body.readUInt32BE(4),
|
|
261
|
-
outStreams: body.readUInt16BE(8),
|
|
262
|
-
inStreams: body.readUInt16BE(10),
|
|
263
|
-
initialTSN: body.readUInt32BE(12),
|
|
264
|
-
params: parseParams(body.slice(16)),
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Encode a DATA chunk body (RFC 4960 §3.3.1).
|
|
270
|
-
* @param {Object} p
|
|
271
|
-
* @param {number} p.tsn
|
|
272
|
-
* @param {number} p.streamId
|
|
273
|
-
* @param {number} p.streamSeq
|
|
274
|
-
* @param {number} p.ppid
|
|
275
|
-
* @param {Buffer} p.userData
|
|
276
|
-
* @returns {{flags:number, body:Buffer}}
|
|
277
|
-
*/
|
|
278
|
-
export function encodeDataBody({ tsn, streamId, streamSeq, ppid, userData, unordered = false, beginning = true, ending = true }: DataBodyParams): EncodedDataBody {
|
|
279
|
-
const head = Buffer.alloc(12);
|
|
280
|
-
head.writeUInt32BE(tsn >>> 0, 0);
|
|
281
|
-
head.writeUInt16BE(streamId, 4);
|
|
282
|
-
head.writeUInt16BE(streamSeq, 6);
|
|
283
|
-
head.writeUInt32BE(ppid >>> 0, 8);
|
|
284
|
-
let flags = 0;
|
|
285
|
-
if (ending) flags |= 0x01; // E
|
|
286
|
-
if (beginning) flags |= 0x02; // B
|
|
287
|
-
if (unordered) flags |= 0x04; // U
|
|
288
|
-
return { flags, body: Buffer.concat([head, userData]) };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Parse a DATA chunk body.
|
|
293
|
-
* @param {number} flags
|
|
294
|
-
* @param {Buffer} body
|
|
295
|
-
*/
|
|
296
|
-
export function parseDataBody(flags: number, body: Buffer): ParsedDataBody {
|
|
297
|
-
return {
|
|
298
|
-
unordered: !!(flags & 0x04),
|
|
299
|
-
beginning: !!(flags & 0x02),
|
|
300
|
-
ending: !!(flags & 0x01),
|
|
301
|
-
tsn: body.readUInt32BE(0),
|
|
302
|
-
streamId: body.readUInt16BE(4),
|
|
303
|
-
streamSeq: body.readUInt16BE(6),
|
|
304
|
-
ppid: body.readUInt32BE(8),
|
|
305
|
-
userData: body.slice(12),
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Encode a SACK chunk body (cumulative ack only, no gap/dup for simplicity but
|
|
311
|
-
* gap blocks supported via params).
|
|
312
|
-
* @param {Object} p
|
|
313
|
-
* @param {number} p.cumulativeTSNAck
|
|
314
|
-
* @param {number} p.a_rwnd
|
|
315
|
-
* @param {Array<[number,number]>} [p.gapBlocks] - [start,end] offsets from cumAck+1
|
|
316
|
-
* @param {Array<number>} [p.dupTSNs]
|
|
317
|
-
* @returns {Buffer}
|
|
318
|
-
*/
|
|
319
|
-
export function encodeSackBody({ cumulativeTSNAck, a_rwnd, gapBlocks = [], dupTSNs = [] }: SackBodyParams): Buffer {
|
|
320
|
-
const b = Buffer.alloc(12 + gapBlocks.length * 4 + dupTSNs.length * 4);
|
|
321
|
-
b.writeUInt32BE(cumulativeTSNAck >>> 0, 0);
|
|
322
|
-
b.writeUInt32BE(a_rwnd >>> 0, 4);
|
|
323
|
-
b.writeUInt16BE(gapBlocks.length, 8);
|
|
324
|
-
b.writeUInt16BE(dupTSNs.length, 10);
|
|
325
|
-
let o = 12;
|
|
326
|
-
for (const [start, end] of gapBlocks) {
|
|
327
|
-
b.writeUInt16BE(start, o); b.writeUInt16BE(end, o + 2); o += 4;
|
|
328
|
-
}
|
|
329
|
-
for (const d of dupTSNs) { b.writeUInt32BE(d >>> 0, o); o += 4; }
|
|
330
|
-
return b;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Parse a SACK chunk body.
|
|
335
|
-
*/
|
|
336
|
-
export function parseSackBody(body: Buffer): ParsedSackBody {
|
|
337
|
-
const cumulativeTSNAck = body.readUInt32BE(0);
|
|
338
|
-
const a_rwnd = body.readUInt32BE(4);
|
|
339
|
-
const numGap = body.readUInt16BE(8);
|
|
340
|
-
const numDup = body.readUInt16BE(10);
|
|
341
|
-
const gapBlocks: Array<[number, number]> = [];
|
|
342
|
-
let o = 12;
|
|
343
|
-
for (let i = 0; i < numGap; i++) {
|
|
344
|
-
gapBlocks.push([body.readUInt16BE(o), body.readUInt16BE(o + 2)]);
|
|
345
|
-
o += 4;
|
|
346
|
-
}
|
|
347
|
-
const dupTSNs: number[] = [];
|
|
348
|
-
for (let i = 0; i < numDup; i++) { dupTSNs.push(body.readUInt32BE(o)); o += 4; }
|
|
349
|
-
return { cumulativeTSNAck, a_rwnd, gapBlocks, dupTSNs };
|
|
350
|
-
}
|
package/src/sctp/crc32c.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file crc32c.ts
|
|
3
|
-
* @description CRC-32C (Castagnoli) checksum for SCTP packets (RFC 4960 App. B,
|
|
4
|
-
* polynomial 0x1EDC6F41), reflected input/output, used with the SCTP-specific
|
|
5
|
-
* byte ordering described in RFC 4960 §6.8 / RFC 3309.
|
|
6
|
-
* @module sctp/crc32c
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// Precomputed reflected table for polynomial 0x1EDC6F41 (reflected 0x82F63B78).
|
|
10
|
-
const TABLE: Uint32Array = (() => {
|
|
11
|
-
const t = new Uint32Array(256);
|
|
12
|
-
for (let n = 0; n < 256; n++) {
|
|
13
|
-
let c = n;
|
|
14
|
-
for (let k = 0; k < 8; k++) {
|
|
15
|
-
c = c & 1 ? 0x82f63b78 ^ (c >>> 1) : c >>> 1;
|
|
16
|
-
}
|
|
17
|
-
t[n] = c >>> 0;
|
|
18
|
-
}
|
|
19
|
-
return t;
|
|
20
|
-
})();
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Compute the raw reflected CRC-32C over a buffer.
|
|
24
|
-
* @returns unsigned 32-bit
|
|
25
|
-
*/
|
|
26
|
-
export function crc32c(buf: Buffer): number {
|
|
27
|
-
let crc = 0xffffffff;
|
|
28
|
-
for (let i = 0; i < buf.length; i++) {
|
|
29
|
-
crc = TABLE[(crc ^ buf[i]!) & 0xff]! ^ (crc >>> 8);
|
|
30
|
-
}
|
|
31
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Insert the SCTP checksum into a packet. The checksum field (bytes 8..11 of
|
|
36
|
-
* the common header) is zeroed, the CRC computed over the whole packet, then
|
|
37
|
-
* written back in little-endian byte order (RFC 4960 §6.8, RFC 3309).
|
|
38
|
-
* @param packet - full SCTP packet (header + chunks)
|
|
39
|
-
* @returns the same packet, checksum filled in
|
|
40
|
-
*/
|
|
41
|
-
export function applyChecksum(packet: Buffer): Buffer {
|
|
42
|
-
packet.writeUInt32LE(0, 8);
|
|
43
|
-
const crc = crc32c(packet);
|
|
44
|
-
packet.writeUInt32LE(crc, 8);
|
|
45
|
-
return packet;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Validate the checksum of a received SCTP packet.
|
|
50
|
-
*/
|
|
51
|
-
export function verifyChecksum(packet: Buffer): boolean {
|
|
52
|
-
const original = packet.readUInt32LE(8);
|
|
53
|
-
packet.writeUInt32LE(0, 8);
|
|
54
|
-
const crc = crc32c(packet);
|
|
55
|
-
packet.writeUInt32LE(original, 8); // restore
|
|
56
|
-
return crc === original;
|
|
57
|
-
}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file datachannel-manager.ts
|
|
3
|
-
* @description Bridges SCTP streams + DCEP to RTCDataChannel instances.
|
|
4
|
-
* @module sctp/datachannel-manager
|
|
5
|
-
*
|
|
6
|
-
* Responsibilities:
|
|
7
|
-
* - Allocate SCTP stream IDs per RFC 8832 §6: the DTLS client (a=setup:active)
|
|
8
|
-
* uses even stream IDs, the DTLS server uses odd.
|
|
9
|
-
* - Send DATA_CHANNEL_OPEN and await DATA_CHANNEL_ACK; respond to inbound OPEN.
|
|
10
|
-
* - Map outgoing string/binary sends to the correct PPID, and incoming PPIDs
|
|
11
|
-
* back to string/binary, including the EMPTY variants for zero-length data.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
'use strict';
|
|
15
|
-
|
|
16
|
-
import { EventEmitter } from 'events';
|
|
17
|
-
import * as dcep from './dcep';
|
|
18
|
-
import { PPID } from './chunks';
|
|
19
|
-
import type { SctpAssociation, SctpMessage } from './association';
|
|
20
|
-
import { RTCDataChannel, RTCDataChannelEvents } from '../datachannel/RTCDataChannel';
|
|
21
|
-
|
|
22
|
-
/** Subset of RTCDataChannelInit used when opening/accepting channels. */
|
|
23
|
-
interface ChannelInit {
|
|
24
|
-
ordered?: boolean;
|
|
25
|
-
maxRetransmits?: number | null;
|
|
26
|
-
maxPacketLifeTime?: number | null;
|
|
27
|
-
protocol?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** A locally tracked channel and whether its DCEP open has been acked. */
|
|
31
|
-
interface ChannelEntry {
|
|
32
|
-
channel: RTCDataChannel;
|
|
33
|
-
acked: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Information surfaced on the 'open-request' event for an inbound channel. */
|
|
37
|
-
export interface OpenRequestInfo {
|
|
38
|
-
streamId: number;
|
|
39
|
-
label: string;
|
|
40
|
-
protocol: string;
|
|
41
|
-
ordered: boolean;
|
|
42
|
-
channelType: number;
|
|
43
|
-
reliabilityParameter: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
class DataChannelManager extends EventEmitter {
|
|
47
|
-
#sctp: SctpAssociation;
|
|
48
|
-
#channels: Map<number, ChannelEntry>; // streamId -> { channel, acked }
|
|
49
|
-
#nextStreamId: number;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @param {import('./association').SctpAssociation} association
|
|
53
|
-
* @param {boolean} isDtlsClient - true if we are the DTLS client (even IDs)
|
|
54
|
-
*/
|
|
55
|
-
constructor(association: SctpAssociation, isDtlsClient: boolean) {
|
|
56
|
-
super();
|
|
57
|
-
this.#sctp = association;
|
|
58
|
-
this.#channels = new Map(); // streamId -> { channel, acked }
|
|
59
|
-
this.#nextStreamId = isDtlsClient ? 0 : 1;
|
|
60
|
-
|
|
61
|
-
this.#sctp.on('message', (m: SctpMessage) => this.#onSctpMessage(m));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Open a channel initiated locally.
|
|
66
|
-
* @param {import('../datachannel/RTCDataChannel').RTCDataChannel} channel
|
|
67
|
-
* @param {Object} init - { ordered, maxRetransmits, maxPacketLifeTime, protocol }
|
|
68
|
-
*/
|
|
69
|
-
openChannel(channel: RTCDataChannel, init: ChannelInit = {}): void {
|
|
70
|
-
let streamId = channel.id;
|
|
71
|
-
if (streamId === null || streamId === undefined) {
|
|
72
|
-
streamId = this.#allocateStreamId();
|
|
73
|
-
channel.emit(RTCDataChannelEvents.SET_ID, streamId);
|
|
74
|
-
}
|
|
75
|
-
this.#channels.set(streamId, { channel, acked: false });
|
|
76
|
-
this.#attachSender(channel, streamId, init);
|
|
77
|
-
|
|
78
|
-
if (!channel.negotiated) {
|
|
79
|
-
const open = dcep.encodeOpen({
|
|
80
|
-
channelType: this.#channelType(init),
|
|
81
|
-
priority: 0,
|
|
82
|
-
reliabilityParameter: this.#reliabilityParam(init),
|
|
83
|
-
label: channel.label,
|
|
84
|
-
protocol: init.protocol || channel.protocol || '',
|
|
85
|
-
});
|
|
86
|
-
this.#sctp.sendData(streamId, PPID.DCEP, open);
|
|
87
|
-
// Negotiated=false channels open after receiving DATA_CHANNEL_ACK.
|
|
88
|
-
} else {
|
|
89
|
-
// Pre-negotiated: considered open immediately.
|
|
90
|
-
(this.#channels.get(streamId) as ChannelEntry).acked = true;
|
|
91
|
-
channel.emit(RTCDataChannelEvents.OPEN);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
#allocateStreamId(): number {
|
|
96
|
-
let id = this.#nextStreamId;
|
|
97
|
-
while (this.#channels.has(id)) id += 2;
|
|
98
|
-
this.#nextStreamId = id + 2;
|
|
99
|
-
return id;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
#channelType(init: ChannelInit): number {
|
|
103
|
-
const unordered = init.ordered === false;
|
|
104
|
-
if (init.maxRetransmits != null) {
|
|
105
|
-
return unordered
|
|
106
|
-
? dcep.CHANNEL_TYPE.PARTIAL_RELIABLE_REXMIT_UNORDERED
|
|
107
|
-
: dcep.CHANNEL_TYPE.PARTIAL_RELIABLE_REXMIT;
|
|
108
|
-
}
|
|
109
|
-
if (init.maxPacketLifeTime != null) {
|
|
110
|
-
return unordered
|
|
111
|
-
? dcep.CHANNEL_TYPE.PARTIAL_RELIABLE_TIMED_UNORDERED
|
|
112
|
-
: dcep.CHANNEL_TYPE.PARTIAL_RELIABLE_TIMED;
|
|
113
|
-
}
|
|
114
|
-
return unordered ? dcep.CHANNEL_TYPE.RELIABLE_UNORDERED : dcep.CHANNEL_TYPE.RELIABLE;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
#reliabilityParam(init: ChannelInit): number {
|
|
118
|
-
if (init.maxRetransmits != null) return init.maxRetransmits >>> 0;
|
|
119
|
-
if (init.maxPacketLifeTime != null) return init.maxPacketLifeTime >>> 0;
|
|
120
|
-
return 0;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Wire the channel's outbound SEND events -> SCTP DATA with the right PPID. */
|
|
124
|
-
#attachSender(channel: RTCDataChannel, streamId: number, init: ChannelInit): void {
|
|
125
|
-
const unordered = init.ordered === false;
|
|
126
|
-
channel.on(RTCDataChannelEvents.SEND, (data: Buffer, isBinary: boolean) => {
|
|
127
|
-
let ppid: number;
|
|
128
|
-
if (isBinary) {
|
|
129
|
-
ppid = data.length === 0 ? PPID.BINARY_EMPTY : PPID.BINARY;
|
|
130
|
-
} else {
|
|
131
|
-
ppid = data.length === 0 ? PPID.STRING_EMPTY : PPID.STRING;
|
|
132
|
-
}
|
|
133
|
-
// EMPTY PPIDs still need one byte on the wire (RFC 8831 §6.6).
|
|
134
|
-
const payload = data.length === 0 ? Buffer.from([0]) : data;
|
|
135
|
-
this.#sctp.sendData(streamId, ppid, payload, { unordered });
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
#onSctpMessage(m: SctpMessage): void {
|
|
140
|
-
if (m.ppid === PPID.DCEP) {
|
|
141
|
-
this.#onDcep(m);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const entry = this.#channels.get(m.streamId);
|
|
145
|
-
if (!entry) return; // data for unknown channel
|
|
146
|
-
const isBinary = m.ppid === PPID.BINARY || m.ppid === PPID.BINARY_EMPTY || m.ppid === PPID.BINARY_PARTIAL;
|
|
147
|
-
const isEmpty = m.ppid === PPID.STRING_EMPTY || m.ppid === PPID.BINARY_EMPTY;
|
|
148
|
-
const data = isEmpty ? Buffer.alloc(0) : m.data;
|
|
149
|
-
entry.channel.emit(RTCDataChannelEvents.RECEIVE, data, isBinary);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
#onDcep(m: SctpMessage): void {
|
|
153
|
-
const type = dcep.messageType(m.data);
|
|
154
|
-
if (type === dcep.MESSAGE_TYPE.DATA_CHANNEL_OPEN) {
|
|
155
|
-
const open = dcep.decodeOpen(m.data);
|
|
156
|
-
// Acknowledge and surface a new inbound channel.
|
|
157
|
-
this.#sctp.sendData(m.streamId, PPID.DCEP, dcep.encodeAck());
|
|
158
|
-
this.emit('open-request', {
|
|
159
|
-
streamId: m.streamId,
|
|
160
|
-
label: open.label,
|
|
161
|
-
protocol: open.protocol,
|
|
162
|
-
ordered: !open.unordered,
|
|
163
|
-
channelType: open.channelType,
|
|
164
|
-
reliabilityParameter: open.reliabilityParameter,
|
|
165
|
-
});
|
|
166
|
-
} else if (type === dcep.MESSAGE_TYPE.DATA_CHANNEL_ACK) {
|
|
167
|
-
const entry = this.#channels.get(m.streamId);
|
|
168
|
-
if (entry && !entry.acked) {
|
|
169
|
-
entry.acked = true;
|
|
170
|
-
entry.channel.emit(RTCDataChannelEvents.OPEN);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Register an inbound channel (created in response to 'open-request') and
|
|
177
|
-
* attach its sender.
|
|
178
|
-
*/
|
|
179
|
-
acceptChannel(channel: RTCDataChannel, info: OpenRequestInfo): void {
|
|
180
|
-
channel.emit(RTCDataChannelEvents.SET_ID, info.streamId);
|
|
181
|
-
this.#channels.set(info.streamId, { channel, acked: true });
|
|
182
|
-
this.#attachSender(channel, info.streamId, { ordered: info.ordered });
|
|
183
|
-
channel.emit(RTCDataChannelEvents.OPEN);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export { DataChannelManager };
|
package/src/sctp/dcep.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file dcep.ts
|
|
3
|
-
* @description Data Channel Establishment Protocol (RFC 8832) message codec.
|
|
4
|
-
* @module sctp/dcep
|
|
5
|
-
*
|
|
6
|
-
* DCEP runs on PPID 50 and negotiates a data channel on an SCTP stream:
|
|
7
|
-
* DATA_CHANNEL_OPEN (0x03) -> DATA_CHANNEL_ACK (0x02)
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
'use strict';
|
|
11
|
-
|
|
12
|
-
export const MESSAGE_TYPE = Object.freeze({
|
|
13
|
-
DATA_CHANNEL_ACK: 0x02,
|
|
14
|
-
DATA_CHANNEL_OPEN: 0x03,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// Channel types (reliability/ordering), RFC 8832 §5.1.
|
|
18
|
-
export const CHANNEL_TYPE = Object.freeze({
|
|
19
|
-
RELIABLE: 0x00,
|
|
20
|
-
RELIABLE_UNORDERED: 0x80,
|
|
21
|
-
PARTIAL_RELIABLE_REXMIT: 0x01,
|
|
22
|
-
PARTIAL_RELIABLE_REXMIT_UNORDERED: 0x81,
|
|
23
|
-
PARTIAL_RELIABLE_TIMED: 0x02,
|
|
24
|
-
PARTIAL_RELIABLE_TIMED_UNORDERED: 0x82,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
export interface OpenParams {
|
|
28
|
-
channelType: number;
|
|
29
|
-
priority?: number;
|
|
30
|
-
reliabilityParameter?: number;
|
|
31
|
-
label?: string;
|
|
32
|
-
protocol?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface DecodedOpen {
|
|
36
|
-
channelType: number;
|
|
37
|
-
priority: number;
|
|
38
|
-
reliabilityParameter: number;
|
|
39
|
-
label: string;
|
|
40
|
-
protocol: string;
|
|
41
|
-
unordered: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Encode a DATA_CHANNEL_OPEN message.
|
|
46
|
-
* @param {Object} p
|
|
47
|
-
* @param {number} p.channelType
|
|
48
|
-
* @param {number} p.priority
|
|
49
|
-
* @param {number} p.reliabilityParameter
|
|
50
|
-
* @param {string} p.label
|
|
51
|
-
* @param {string} p.protocol
|
|
52
|
-
* @returns {Buffer}
|
|
53
|
-
*/
|
|
54
|
-
export function encodeOpen({ channelType, priority = 0, reliabilityParameter = 0, label = '', protocol = '' }: OpenParams): Buffer {
|
|
55
|
-
const labelBuf = Buffer.from(label, 'utf8');
|
|
56
|
-
const protoBuf = Buffer.from(protocol, 'utf8');
|
|
57
|
-
const buf = Buffer.alloc(12 + labelBuf.length + protoBuf.length);
|
|
58
|
-
buf.writeUInt8(MESSAGE_TYPE.DATA_CHANNEL_OPEN, 0);
|
|
59
|
-
buf.writeUInt8(channelType, 1);
|
|
60
|
-
buf.writeUInt16BE(priority, 2);
|
|
61
|
-
buf.writeUInt32BE(reliabilityParameter >>> 0, 4);
|
|
62
|
-
buf.writeUInt16BE(labelBuf.length, 8);
|
|
63
|
-
buf.writeUInt16BE(protoBuf.length, 10);
|
|
64
|
-
labelBuf.copy(buf, 12);
|
|
65
|
-
protoBuf.copy(buf, 12 + labelBuf.length);
|
|
66
|
-
return buf;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Decode a DATA_CHANNEL_OPEN message.
|
|
71
|
-
* @param {Buffer} buf
|
|
72
|
-
* @returns {Object}
|
|
73
|
-
*/
|
|
74
|
-
export function decodeOpen(buf: Buffer): DecodedOpen {
|
|
75
|
-
const channelType = buf.readUInt8(1);
|
|
76
|
-
const priority = buf.readUInt16BE(2);
|
|
77
|
-
const reliabilityParameter = buf.readUInt32BE(4);
|
|
78
|
-
const labelLen = buf.readUInt16BE(8);
|
|
79
|
-
const protoLen = buf.readUInt16BE(10);
|
|
80
|
-
const label = buf.slice(12, 12 + labelLen).toString('utf8');
|
|
81
|
-
const protocol = buf.slice(12 + labelLen, 12 + labelLen + protoLen).toString('utf8');
|
|
82
|
-
const unordered = (channelType & 0x80) !== 0;
|
|
83
|
-
return { channelType, priority, reliabilityParameter, label, protocol, unordered };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Encode a DATA_CHANNEL_ACK message. */
|
|
87
|
-
export function encodeAck(): Buffer {
|
|
88
|
-
return Buffer.from([MESSAGE_TYPE.DATA_CHANNEL_ACK]);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Return the message type of a DCEP buffer. */
|
|
92
|
-
export function messageType(buf: Buffer): number {
|
|
93
|
-
return buf.length > 0 ? buf.readUInt8(0) : -1;
|
|
94
|
-
}
|