node-rtc-connection 1.0.19 → 2.0.4

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.
Files changed (64) hide show
  1. package/README.md +94 -85
  2. package/dist/index.cjs +20 -5606
  3. package/dist/index.mjs +25 -5598
  4. package/dist/types/crypto/der.d.ts +107 -0
  5. package/dist/types/crypto/x509.d.ts +56 -0
  6. package/dist/types/datachannel/RTCDataChannel.d.ts +179 -0
  7. package/dist/types/dtls/RTCCertificate.d.ts +163 -0
  8. package/dist/types/dtls/cipher.d.ts +81 -0
  9. package/dist/types/dtls/connection.d.ts +81 -0
  10. package/dist/types/dtls/prf.d.ts +29 -0
  11. package/dist/types/dtls/protocol.d.ts +127 -0
  12. package/dist/types/foundation/ByteBufferQueue.d.ts +71 -0
  13. package/dist/types/foundation/RTCError.d.ts +152 -0
  14. package/dist/types/ice/RTCIceCandidate.d.ts +161 -0
  15. package/dist/types/ice/ice-agent.d.ts +154 -0
  16. package/dist/types/ice/stun-message.d.ts +92 -0
  17. package/dist/types/index.d.ts +29 -0
  18. package/dist/types/peerconnection/RTCPeerConnection.d.ts +74 -0
  19. package/dist/types/sctp/association.d.ts +77 -0
  20. package/dist/types/sctp/chunks.d.ts +200 -0
  21. package/dist/types/sctp/crc32c.d.ts +24 -0
  22. package/dist/types/sctp/datachannel-manager.d.ts +51 -0
  23. package/dist/types/sctp/dcep.d.ts +56 -0
  24. package/dist/types/sdp/RTCSessionDescription.d.ts +73 -0
  25. package/dist/types/sdp/sdp-utils.d.ts +103 -0
  26. package/dist/types/stun/stun-client.d.ts +119 -0
  27. package/dist/types/transport-stack.d.ts +68 -0
  28. package/package.json +26 -21
  29. package/src/crypto/der.ts +205 -0
  30. package/src/crypto/x509.ts +146 -0
  31. package/src/datachannel/RTCDataChannel.ts +388 -0
  32. package/src/dtls/RTCCertificate.ts +396 -0
  33. package/src/dtls/cipher.ts +198 -0
  34. package/src/dtls/connection.ts +974 -0
  35. package/src/dtls/prf.ts +62 -0
  36. package/src/dtls/protocol.ts +204 -0
  37. package/src/foundation/{ByteBufferQueue.js → ByteBufferQueue.ts} +74 -72
  38. package/src/foundation/{RTCError.js → RTCError.ts} +110 -60
  39. package/src/ice/{RTCIceCandidate.js → RTCIceCandidate.ts} +140 -92
  40. package/src/ice/ice-agent.ts +609 -0
  41. package/src/ice/stun-message.ts +260 -0
  42. package/src/index.ts +72 -0
  43. package/src/peerconnection/RTCPeerConnection.ts +430 -0
  44. package/src/sctp/association.ts +523 -0
  45. package/src/sctp/chunks.ts +350 -0
  46. package/src/sctp/crc32c.ts +57 -0
  47. package/src/sctp/datachannel-manager.ts +187 -0
  48. package/src/sctp/dcep.ts +94 -0
  49. package/src/sdp/{RTCSessionDescription.js → RTCSessionDescription.ts} +42 -29
  50. package/src/sdp/sdp-utils.ts +229 -0
  51. package/src/stun/{stun-client.js → stun-client.ts} +346 -187
  52. package/src/transport-stack.ts +165 -0
  53. package/dist/index.cjs.map +0 -1
  54. package/dist/index.mjs.map +0 -1
  55. package/src/datachannel/RTCDataChannel.js +0 -354
  56. package/src/dtls/RTCCertificate.js +0 -310
  57. package/src/dtls/RTCDtlsTransport.js +0 -247
  58. package/src/ice/RTCIceTransport.js +0 -1018
  59. package/src/index.d.ts +0 -400
  60. package/src/index.js +0 -92
  61. package/src/network/network-transport.js +0 -478
  62. package/src/peerconnection/RTCPeerConnection.js +0 -875
  63. package/src/sctp/RTCSctpTransport.js +0 -253
  64. package/src/sdp/sdp-utils.js +0 -224
@@ -0,0 +1,260 @@
1
+ /**
2
+ * @file stun-message.ts
3
+ * @description STUN message codec for ICE connectivity checks (RFC 5389 / 8445).
4
+ * @module ice/stun-message
5
+ *
6
+ * Unlike the server-oriented stun-client.js (binding/allocate), this builds and
7
+ * validates the connectivity-check messages browsers require: USERNAME,
8
+ * MESSAGE-INTEGRITY (HMAC-SHA1 keyed by the peer's ice-pwd), FINGERPRINT
9
+ * (CRC-32 of the message Xored with 0x5354554e), PRIORITY, ICE-CONTROLLING/
10
+ * ICE-CONTROLLED, and USE-CANDIDATE.
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ import * as crypto from 'crypto';
16
+
17
+ export const MAGIC_COOKIE = 0x2112a442;
18
+
19
+ export const METHOD = Object.freeze({ BINDING: 0x0001 });
20
+ export const CLASS = Object.freeze({
21
+ REQUEST: 0x000,
22
+ INDICATION: 0x010,
23
+ SUCCESS: 0x100,
24
+ ERROR: 0x110,
25
+ });
26
+
27
+ export const MSG_TYPE = Object.freeze({
28
+ BINDING_REQUEST: 0x0001,
29
+ BINDING_SUCCESS: 0x0101,
30
+ BINDING_ERROR: 0x0111,
31
+ });
32
+
33
+ export const ATTR = Object.freeze({
34
+ MAPPED_ADDRESS: 0x0001,
35
+ USERNAME: 0x0006,
36
+ MESSAGE_INTEGRITY: 0x0008,
37
+ ERROR_CODE: 0x0009,
38
+ XOR_MAPPED_ADDRESS: 0x0020,
39
+ PRIORITY: 0x0024,
40
+ USE_CANDIDATE: 0x0025,
41
+ FINGERPRINT: 0x8028,
42
+ ICE_CONTROLLED: 0x8029,
43
+ ICE_CONTROLLING: 0x802a,
44
+ });
45
+
46
+ /** A single STUN attribute pending serialization. */
47
+ interface StunAttribute {
48
+ type: number;
49
+ value: Buffer;
50
+ }
51
+
52
+ /** Shape of a parsed STUN message. */
53
+ export interface ParsedStunMessage {
54
+ type: number;
55
+ transactionId: Buffer;
56
+ attrs: Map<number, Buffer>;
57
+ raw: Buffer;
58
+ }
59
+
60
+ function pad4(n: number): number {
61
+ return (n + 3) & ~3;
62
+ }
63
+
64
+ /** CRC-32 (IEEE) for the FINGERPRINT attribute. */
65
+ const CRC_TABLE: Uint32Array = (() => {
66
+ const t = new Uint32Array(256);
67
+ for (let n = 0; n < 256; n++) {
68
+ let c = n;
69
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
70
+ t[n] = c >>> 0;
71
+ }
72
+ return t;
73
+ })();
74
+ export function crc32(buf: Buffer): number {
75
+ let crc = 0xffffffff;
76
+ for (let i = 0; i < buf.length; i++) crc = CRC_TABLE[(crc ^ buf[i]!) & 0xff]! ^ (crc >>> 8);
77
+ return (crc ^ 0xffffffff) >>> 0;
78
+ }
79
+
80
+ /**
81
+ * @class StunMessageBuilder
82
+ * @description Incrementally builds a STUN message, then appends
83
+ * MESSAGE-INTEGRITY and FINGERPRINT with the correct length pre-computation.
84
+ */
85
+ export class StunMessageBuilder {
86
+ type: number;
87
+ transactionId: Buffer;
88
+ attrs: StunAttribute[];
89
+
90
+ constructor(type: number, transactionId?: Buffer) {
91
+ this.type = type;
92
+ this.transactionId = transactionId || crypto.randomBytes(12);
93
+ this.attrs = []; // {type, value}
94
+ }
95
+
96
+ addAttr(type: number, value: Buffer): this {
97
+ this.attrs.push({ type, value });
98
+ return this;
99
+ }
100
+
101
+ addUsername(username: string): this {
102
+ return this.addAttr(ATTR.USERNAME, Buffer.from(username, 'utf8'));
103
+ }
104
+
105
+ addPriority(priority: number): this {
106
+ const b = Buffer.alloc(4);
107
+ b.writeUInt32BE(priority >>> 0, 0);
108
+ return this.addAttr(ATTR.PRIORITY, b);
109
+ }
110
+
111
+ addIceControlling(tieBreaker: Buffer): this {
112
+ return this.addAttr(ATTR.ICE_CONTROLLING, tieBreaker);
113
+ }
114
+
115
+ addIceControlled(tieBreaker: Buffer): this {
116
+ return this.addAttr(ATTR.ICE_CONTROLLED, tieBreaker);
117
+ }
118
+
119
+ addUseCandidate(): this {
120
+ return this.addAttr(ATTR.USE_CANDIDATE, Buffer.alloc(0));
121
+ }
122
+
123
+ addXorMappedAddress(address: string, port: number): this {
124
+ return this.addAttr(ATTR.XOR_MAPPED_ADDRESS, encodeXorAddress(address, port, this.transactionId));
125
+ }
126
+
127
+ /** Serialize the attributes added so far. */
128
+ #encodeBody(): Buffer {
129
+ const parts: Buffer[] = [];
130
+ for (const a of this.attrs) {
131
+ const head = Buffer.alloc(4);
132
+ head.writeUInt16BE(a.type, 0);
133
+ head.writeUInt16BE(a.value.length, 2);
134
+ const padded = Buffer.alloc(pad4(a.value.length));
135
+ a.value.copy(padded, 0);
136
+ parts.push(head, padded);
137
+ }
138
+ return Buffer.concat(parts);
139
+ }
140
+
141
+ #header(bodyLen: number): Buffer {
142
+ const h = Buffer.alloc(20);
143
+ h.writeUInt16BE(this.type, 0);
144
+ h.writeUInt16BE(bodyLen, 2);
145
+ h.writeUInt32BE(MAGIC_COOKIE, 4);
146
+ this.transactionId.copy(h, 8);
147
+ return h;
148
+ }
149
+
150
+ /**
151
+ * Finalize the message, appending MESSAGE-INTEGRITY (keyed by `password`)
152
+ * and FINGERPRINT. Both require the header length to include the attribute
153
+ * being computed, per RFC 5389 §15.4 / §15.5.
154
+ * @param {string} [password] - ICE password for MESSAGE-INTEGRITY
155
+ * @returns {Buffer}
156
+ */
157
+ build(password?: string): Buffer {
158
+ let body = this.#encodeBody();
159
+
160
+ if (password) {
161
+ // Length for HMAC input = current body + (4 header + 20 HMAC).
162
+ const lenForMI = body.length + 24;
163
+ const header = this.#header(lenForMI);
164
+ const hmac = crypto
165
+ .createHmac('sha1', Buffer.from(password, 'utf8'))
166
+ .update(Buffer.concat([header, body]))
167
+ .digest();
168
+ const miHead = Buffer.alloc(4);
169
+ miHead.writeUInt16BE(ATTR.MESSAGE_INTEGRITY, 0);
170
+ miHead.writeUInt16BE(20, 2);
171
+ body = Buffer.concat([body, miHead, hmac]);
172
+ }
173
+
174
+ // FINGERPRINT: CRC-32 over the message (with length including fingerprint)
175
+ // Xored with 0x5354554e.
176
+ const lenForFp = body.length + 8;
177
+ const headerFp = this.#header(lenForFp);
178
+ const fpVal = (crc32(Buffer.concat([headerFp, body])) ^ 0x5354554e) >>> 0;
179
+ const fpHead = Buffer.alloc(8);
180
+ fpHead.writeUInt16BE(ATTR.FINGERPRINT, 0);
181
+ fpHead.writeUInt16BE(4, 2);
182
+ fpHead.writeUInt32BE(fpVal, 4);
183
+ body = Buffer.concat([body, fpHead]);
184
+
185
+ return Buffer.concat([this.#header(body.length), body]);
186
+ }
187
+ }
188
+
189
+ /** Encode a XOR-MAPPED-ADDRESS attribute value (IPv4). */
190
+ function encodeXorAddress(address: string, port: number, _transactionId: Buffer): Buffer {
191
+ const buf = Buffer.alloc(8);
192
+ buf.writeUInt8(0, 0);
193
+ buf.writeUInt8(0x01, 1); // family IPv4
194
+ buf.writeUInt16BE(port ^ (MAGIC_COOKIE >>> 16), 2);
195
+ const parts = address.split('.').map(Number);
196
+ const addrInt = ((parts[0]! << 24) | (parts[1]! << 16) | (parts[2]! << 8) | parts[3]!) >>> 0;
197
+ buf.writeUInt32BE((addrInt ^ MAGIC_COOKIE) >>> 0, 4);
198
+ return buf;
199
+ }
200
+
201
+ /**
202
+ * Parse a STUN message. Returns null if not a STUN message.
203
+ * @param {Buffer} msg
204
+ * @returns {null|{type:number,transactionId:Buffer,attrs:Map<number,Buffer>,raw:Buffer}}
205
+ */
206
+ export function parse(msg: Buffer): ParsedStunMessage | null {
207
+ if (msg.length < 20) return null;
208
+ if (msg.readUInt32BE(4) !== MAGIC_COOKIE) return null;
209
+ const type = msg.readUInt16BE(0);
210
+ const length = msg.readUInt16BE(2);
211
+ if (20 + length > msg.length) return null;
212
+ const transactionId = msg.slice(8, 20);
213
+ const attrs = new Map<number, Buffer>();
214
+ let off = 20;
215
+ const end = 20 + length;
216
+ while (off + 4 <= end) {
217
+ const atype = msg.readUInt16BE(off);
218
+ const alen = msg.readUInt16BE(off + 2);
219
+ off += 4;
220
+ if (off + alen > end) break;
221
+ attrs.set(atype, msg.slice(off, off + alen));
222
+ off += pad4(alen);
223
+ }
224
+ return { type, transactionId, attrs, raw: msg };
225
+ }
226
+
227
+ /**
228
+ * Verify the MESSAGE-INTEGRITY of a parsed message against a password.
229
+ * @param {Buffer} msg - raw message
230
+ * @param {string} password
231
+ * @returns {boolean}
232
+ */
233
+ export function verifyIntegrity(msg: Buffer, password: string): boolean {
234
+ // Locate the MESSAGE-INTEGRITY attribute.
235
+ const length = msg.readUInt16BE(2);
236
+ let off = 20;
237
+ const end = 20 + length;
238
+ let miOffset = -1;
239
+ while (off + 4 <= end) {
240
+ const atype = msg.readUInt16BE(off);
241
+ const alen = msg.readUInt16BE(off + 2);
242
+ if (atype === ATTR.MESSAGE_INTEGRITY) {
243
+ miOffset = off;
244
+ break;
245
+ }
246
+ off += 4 + pad4(alen);
247
+ }
248
+ if (miOffset < 0) return false;
249
+
250
+ const provided = msg.slice(miOffset + 4, miOffset + 4 + 20);
251
+ // Recompute over header (with length up to & including MI) + body before MI.
252
+ const lenUpToMI = miOffset + 24 - 20;
253
+ const header = Buffer.from(msg.slice(0, 20));
254
+ header.writeUInt16BE(lenUpToMI, 2);
255
+ const hmac = crypto
256
+ .createHmac('sha1', Buffer.from(password, 'utf8'))
257
+ .update(Buffer.concat([header, msg.slice(20, miOffset)]))
258
+ .digest();
259
+ return provided.length === hmac.length && crypto.timingSafeEqual(provided, hmac);
260
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @fileoverview node-rtc-connection - WebRTC DataChannel implementation for Node.js
3
+ *
4
+ * A from-scratch, pure-Node.js implementation of WebRTC peer connections and
5
+ * data channels (no native dependencies). Interoperates with browsers.
6
+ *
7
+ * This implementation focuses on DataChannel functionality without media streams.
8
+ * Features:
9
+ * - ICE (Interactive Connectivity Establishment), RFC 8445
10
+ * - STUN/TURN support for NAT traversal
11
+ * - DTLS 1.2 encryption (RFC 6347)
12
+ * - SCTP over DTLS + DCEP for data channels (RFC 8831 / 8832)
13
+ * - W3C-compatible RTCPeerConnection / RTCDataChannel API
14
+ *
15
+ * @license MIT
16
+ * @author nmhung1210
17
+ */
18
+
19
+ // Foundation
20
+ import ByteBufferQueue from './foundation/ByteBufferQueue';
21
+ import RTCError from './foundation/RTCError';
22
+
23
+ // ICE / certificates
24
+ import RTCIceCandidate from './ice/RTCIceCandidate';
25
+ import RTCCertificate from './dtls/RTCCertificate';
26
+
27
+ // DataChannel
28
+ import { RTCDataChannel, RTCDataChannelState } from './datachannel/RTCDataChannel';
29
+
30
+ // SDP
31
+ import { RTCSessionDescription, RTCSdpType } from './sdp/RTCSessionDescription';
32
+
33
+ // PeerConnection
34
+ import {
35
+ RTCPeerConnection,
36
+ RTCSignalingState,
37
+ RTCIceGatheringState,
38
+ RTCPeerConnectionState,
39
+ } from './peerconnection/RTCPeerConnection';
40
+
41
+ import pkg from '../package.json';
42
+
43
+ export {
44
+ // Foundation
45
+ ByteBufferQueue,
46
+ RTCError,
47
+
48
+ // ICE / certificates
49
+ RTCIceCandidate,
50
+ RTCCertificate,
51
+
52
+ // DataChannel
53
+ RTCDataChannel,
54
+ RTCDataChannelState,
55
+
56
+ // SDP
57
+ RTCSessionDescription,
58
+ RTCSdpType,
59
+
60
+ // PeerConnection
61
+ RTCPeerConnection,
62
+ RTCSignalingState,
63
+ RTCIceGatheringState,
64
+ RTCPeerConnectionState,
65
+ };
66
+
67
+ export const version: string = pkg.version;
68
+
69
+ // Re-export public types for consumers.
70
+ export type { RTCDataChannelInit } from './datachannel/RTCDataChannel';
71
+ export type { RTCSessionDescriptionInit } from './sdp/RTCSessionDescription';
72
+ export type { TransportStackOptions } from './transport-stack';