@agentdance/node-webrtc-dtls 1.0.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/src/record.ts ADDED
@@ -0,0 +1,127 @@
1
+ // DTLS Record Layer encode/decode (RFC 6347 Section 4.1)
2
+ //
3
+ // Record header format (13 bytes):
4
+ // 1 byte content type
5
+ // 2 bytes version (major, minor)
6
+ // 2 bytes epoch
7
+ // 6 bytes sequence number (48-bit)
8
+ // 2 bytes length
9
+
10
+ import { ContentType, type DtlsRecord, DTLS_VERSION_1_2 } from './types.js';
11
+
12
+ const RECORD_HEADER_SIZE = 13;
13
+
14
+ /**
15
+ * Encode a single DTLS record into a Buffer.
16
+ */
17
+ export function encodeRecord(record: DtlsRecord): Buffer {
18
+ const buf = Buffer.allocUnsafe(RECORD_HEADER_SIZE + record.fragment.length);
19
+ let offset = 0;
20
+
21
+ buf.writeUInt8(record.contentType, offset);
22
+ offset += 1;
23
+ buf.writeUInt8(record.version.major, offset);
24
+ offset += 1;
25
+ buf.writeUInt8(record.version.minor, offset);
26
+ offset += 1;
27
+ buf.writeUInt16BE(record.epoch, offset);
28
+ offset += 2;
29
+
30
+ // 48-bit sequence number (big-endian)
31
+ const seq = record.sequenceNumber;
32
+ buf.writeUInt16BE(Number((seq >> 32n) & 0xffffn), offset);
33
+ offset += 2;
34
+ buf.writeUInt32BE(Number(seq & 0xffffffffn), offset);
35
+ offset += 4;
36
+
37
+ buf.writeUInt16BE(record.fragment.length, offset);
38
+ offset += 2;
39
+
40
+ record.fragment.copy(buf, offset);
41
+ return buf;
42
+ }
43
+
44
+ /**
45
+ * Decode one or more DTLS records from a UDP datagram.
46
+ * A single datagram may contain multiple records.
47
+ */
48
+ export function decodeRecords(buf: Buffer): DtlsRecord[] {
49
+ const records: DtlsRecord[] = [];
50
+ let offset = 0;
51
+
52
+ while (offset < buf.length) {
53
+ if (buf.length - offset < RECORD_HEADER_SIZE) {
54
+ break;
55
+ }
56
+
57
+ const contentType = buf.readUInt8(offset) as ContentType;
58
+ offset += 1;
59
+ const major = buf.readUInt8(offset);
60
+ offset += 1;
61
+ const minor = buf.readUInt8(offset);
62
+ offset += 1;
63
+ const epoch = buf.readUInt16BE(offset);
64
+ offset += 2;
65
+
66
+ const seqHigh = BigInt(buf.readUInt16BE(offset));
67
+ offset += 2;
68
+ const seqLow = BigInt(buf.readUInt32BE(offset));
69
+ offset += 4;
70
+ const sequenceNumber = (seqHigh << 32n) | seqLow;
71
+
72
+ const length = buf.readUInt16BE(offset);
73
+ offset += 2;
74
+
75
+ if (buf.length - offset < length) {
76
+ break;
77
+ }
78
+
79
+ const fragment = buf.subarray(offset, offset + length);
80
+ offset += length;
81
+
82
+ records.push({
83
+ contentType,
84
+ version: { major, minor },
85
+ epoch,
86
+ sequenceNumber,
87
+ fragment: Buffer.from(fragment),
88
+ });
89
+ }
90
+
91
+ return records;
92
+ }
93
+
94
+ /**
95
+ * Returns true if the buffer looks like a DTLS packet.
96
+ * Content type must be 20-63 and version bytes must match DTLS 1.x.
97
+ */
98
+ export function isDtlsPacket(buf: Buffer): boolean {
99
+ if (buf.length < RECORD_HEADER_SIZE) return false;
100
+ const ct = buf.readUInt8(0);
101
+ // ContentType: 20 (ChangeCipherSpec), 21 (Alert), 22 (Handshake), 23 (ApplicationData)
102
+ if (ct < 20 || ct > 63) return false;
103
+ const major = buf.readUInt8(1);
104
+ const minor = buf.readUInt8(2);
105
+ // DTLS 1.0 = {254, 255}, DTLS 1.2 = {254, 253}
106
+ if (major !== 254) return false;
107
+ if (minor !== 253 && minor !== 255) return false;
108
+ return true;
109
+ }
110
+
111
+ /** Create a record with DTLS 1.2 version defaults */
112
+ export function makeRecord(
113
+ contentType: ContentType,
114
+ epoch: number,
115
+ sequenceNumber: bigint,
116
+ fragment: Buffer,
117
+ ): DtlsRecord {
118
+ return {
119
+ contentType,
120
+ version: DTLS_VERSION_1_2,
121
+ epoch,
122
+ sequenceNumber,
123
+ fragment,
124
+ };
125
+ }
126
+
127
+ export { ContentType };
package/src/state.ts ADDED
@@ -0,0 +1,59 @@
1
+ // DTLS 1.2 state machine types and security parameters
2
+
3
+ import * as crypto from 'node:crypto';
4
+
5
+ export enum DtlsState {
6
+ New = 'new',
7
+ Connecting = 'connecting',
8
+ Connected = 'connected',
9
+ Closed = 'closed',
10
+ Failed = 'failed',
11
+ }
12
+
13
+ export interface SecurityParameters {
14
+ entity: 'client' | 'server';
15
+ cipherSuite: number;
16
+ masterSecret: Buffer;
17
+ clientRandom: Buffer;
18
+ serverRandom: Buffer;
19
+ clientWriteKey: Buffer;
20
+ serverWriteKey: Buffer;
21
+ clientWriteIv: Buffer;
22
+ serverWriteIv: Buffer;
23
+ }
24
+
25
+ export interface CipherState {
26
+ writeKey: Buffer;
27
+ writeIv: Buffer; // 4-byte implicit part for GCM
28
+ readKey: Buffer;
29
+ readIv: Buffer;
30
+ writeEpoch: number;
31
+ writeSeq: bigint;
32
+ readEpoch: number;
33
+ readSeq: bigint;
34
+ }
35
+
36
+ export interface HandshakeContext {
37
+ // Accumulated handshake messages for Finished verification
38
+ messages: Buffer[];
39
+ // Cookie (server side)
40
+ cookie?: Buffer;
41
+ // Our ephemeral ECDH key pair
42
+ ecdhPrivateKey?: crypto.KeyObject;
43
+ ecdhPublicKey?: crypto.KeyObject;
44
+ // Peer's EC public key (from ServerKeyExchange or ClientKeyExchange)
45
+ peerEcPublicKeyBytes?: Buffer;
46
+ // Pre-master secret derived from ECDH
47
+ preMasterSecret?: Buffer;
48
+ // Peer certificate DER
49
+ peerCertDer?: Buffer;
50
+ // Selected cipher suite
51
+ selectedCipherSuite?: number;
52
+ // Message sequence counters
53
+ sendMessageSeq: number;
54
+ recvMessageSeq: number;
55
+ // Server random (stored during ServerHello processing)
56
+ serverRandom?: Buffer;
57
+ // Client random (generated by client, sent to server in ClientHello)
58
+ clientRandom?: Buffer;
59
+ }