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.
- package/README.md +94 -85
- package/dist/index.cjs +20 -5606
- package/dist/index.mjs +25 -5598
- package/dist/types/crypto/der.d.ts +107 -0
- package/dist/types/crypto/x509.d.ts +56 -0
- package/dist/types/datachannel/RTCDataChannel.d.ts +179 -0
- package/dist/types/dtls/RTCCertificate.d.ts +163 -0
- package/dist/types/dtls/cipher.d.ts +81 -0
- package/dist/types/dtls/connection.d.ts +81 -0
- package/dist/types/dtls/prf.d.ts +29 -0
- package/dist/types/dtls/protocol.d.ts +127 -0
- package/dist/types/foundation/ByteBufferQueue.d.ts +71 -0
- package/dist/types/foundation/RTCError.d.ts +152 -0
- package/dist/types/ice/RTCIceCandidate.d.ts +161 -0
- package/dist/types/ice/ice-agent.d.ts +154 -0
- package/dist/types/ice/stun-message.d.ts +92 -0
- package/dist/types/index.d.ts +29 -0
- package/dist/types/peerconnection/RTCPeerConnection.d.ts +74 -0
- package/dist/types/sctp/association.d.ts +77 -0
- package/dist/types/sctp/chunks.d.ts +200 -0
- package/dist/types/sctp/crc32c.d.ts +24 -0
- package/dist/types/sctp/datachannel-manager.d.ts +51 -0
- package/dist/types/sctp/dcep.d.ts +56 -0
- package/dist/types/sdp/RTCSessionDescription.d.ts +73 -0
- package/dist/types/sdp/sdp-utils.d.ts +103 -0
- package/dist/types/stun/stun-client.d.ts +119 -0
- package/dist/types/transport-stack.d.ts +68 -0
- package/package.json +26 -21
- package/src/crypto/der.ts +205 -0
- package/src/crypto/x509.ts +146 -0
- package/src/datachannel/RTCDataChannel.ts +388 -0
- package/src/dtls/RTCCertificate.ts +396 -0
- package/src/dtls/cipher.ts +198 -0
- package/src/dtls/connection.ts +974 -0
- package/src/dtls/prf.ts +62 -0
- package/src/dtls/protocol.ts +204 -0
- package/src/foundation/{ByteBufferQueue.js → ByteBufferQueue.ts} +74 -72
- package/src/foundation/{RTCError.js → RTCError.ts} +110 -60
- package/src/ice/{RTCIceCandidate.js → RTCIceCandidate.ts} +140 -92
- package/src/ice/ice-agent.ts +609 -0
- package/src/ice/stun-message.ts +260 -0
- package/src/index.ts +72 -0
- package/src/peerconnection/RTCPeerConnection.ts +430 -0
- package/src/sctp/association.ts +523 -0
- package/src/sctp/chunks.ts +350 -0
- package/src/sctp/crc32c.ts +57 -0
- package/src/sctp/datachannel-manager.ts +187 -0
- package/src/sctp/dcep.ts +94 -0
- package/src/sdp/{RTCSessionDescription.js → RTCSessionDescription.ts} +42 -29
- package/src/sdp/sdp-utils.ts +229 -0
- package/src/stun/{stun-client.js → stun-client.ts} +346 -187
- package/src/transport-stack.ts +165 -0
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/src/datachannel/RTCDataChannel.js +0 -354
- package/src/dtls/RTCCertificate.js +0 -310
- package/src/dtls/RTCDtlsTransport.js +0 -247
- package/src/ice/RTCIceTransport.js +0 -1018
- package/src/index.d.ts +0 -400
- package/src/index.js +0 -92
- package/src/network/network-transport.js +0 -478
- package/src/peerconnection/RTCPeerConnection.js +0 -875
- package/src/sctp/RTCSctpTransport.js +0 -253
- package/src/sdp/sdp-utils.js +0 -224
package/src/dtls/prf.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file prf.ts
|
|
3
|
+
* @description TLS 1.2 pseudo-random function (RFC 5246 §5) with SHA-256.
|
|
4
|
+
* @module dtls/prf
|
|
5
|
+
*
|
|
6
|
+
* The cipher suite TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uses P_SHA256 for
|
|
7
|
+
* the PRF. This module also exposes the underlying HMAC-based P_hash.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
import * as crypto from 'crypto';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* P_hash(secret, seed) expanded to `length` bytes (RFC 5246 §5).
|
|
16
|
+
* A(0) = seed
|
|
17
|
+
* A(i) = HMAC(secret, A(i-1))
|
|
18
|
+
* P_hash = HMAC(secret, A(1)+seed) | HMAC(secret, A(2)+seed) | ...
|
|
19
|
+
*
|
|
20
|
+
* @param hashAlg - Node hash name, e.g. 'sha256'.
|
|
21
|
+
* @param secret
|
|
22
|
+
* @param seed
|
|
23
|
+
* @param length
|
|
24
|
+
*/
|
|
25
|
+
export function pHash(
|
|
26
|
+
hashAlg: string,
|
|
27
|
+
secret: Buffer,
|
|
28
|
+
seed: Buffer,
|
|
29
|
+
length: number
|
|
30
|
+
): Buffer {
|
|
31
|
+
const out: Buffer[] = [];
|
|
32
|
+
let total = 0;
|
|
33
|
+
let a = seed; // A(0)
|
|
34
|
+
while (total < length) {
|
|
35
|
+
a = crypto.createHmac(hashAlg, secret).update(a).digest(); // A(i)
|
|
36
|
+
const chunk = crypto
|
|
37
|
+
.createHmac(hashAlg, secret)
|
|
38
|
+
.update(Buffer.concat([a, seed]))
|
|
39
|
+
.digest();
|
|
40
|
+
out.push(chunk);
|
|
41
|
+
total += chunk.length;
|
|
42
|
+
}
|
|
43
|
+
return Buffer.concat(out).slice(0, length);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* TLS 1.2 PRF = P_SHA256(secret, label + seed).
|
|
48
|
+
*
|
|
49
|
+
* @param secret
|
|
50
|
+
* @param label - ASCII label, e.g. "master secret".
|
|
51
|
+
* @param seed
|
|
52
|
+
* @param length
|
|
53
|
+
*/
|
|
54
|
+
export function prf(
|
|
55
|
+
secret: Buffer,
|
|
56
|
+
label: string,
|
|
57
|
+
seed: Buffer,
|
|
58
|
+
length: number
|
|
59
|
+
): Buffer {
|
|
60
|
+
const labelAndSeed = Buffer.concat([Buffer.from(label, 'ascii'), seed]);
|
|
61
|
+
return pHash('sha256', secret, labelAndSeed, length);
|
|
62
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file protocol.ts
|
|
3
|
+
* @description DTLS 1.2 wire-format constants and TLV/vector encoders.
|
|
4
|
+
* @module dtls/protocol
|
|
5
|
+
*
|
|
6
|
+
* Covers exactly what WebRTC's data channel needs:
|
|
7
|
+
* cipher suite TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xC02B)
|
|
8
|
+
* curve secp256r1, signature scheme ecdsa_secp256r1_sha256.
|
|
9
|
+
*
|
|
10
|
+
* References: RFC 6347 (DTLS 1.2), RFC 5246 (TLS 1.2), RFC 8422 (ECC).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
// DTLS 1.2 on the wire is version 0xFEFD (i.e. ~1.2).
|
|
16
|
+
export const DTLS_1_2 = 0xfefd;
|
|
17
|
+
|
|
18
|
+
export const CONTENT_TYPE = Object.freeze({
|
|
19
|
+
CHANGE_CIPHER_SPEC: 20,
|
|
20
|
+
ALERT: 21,
|
|
21
|
+
HANDSHAKE: 22,
|
|
22
|
+
APPLICATION_DATA: 23,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const HANDSHAKE_TYPE = Object.freeze({
|
|
26
|
+
HELLO_REQUEST: 0,
|
|
27
|
+
CLIENT_HELLO: 1,
|
|
28
|
+
SERVER_HELLO: 2,
|
|
29
|
+
HELLO_VERIFY_REQUEST: 3,
|
|
30
|
+
CERTIFICATE: 11,
|
|
31
|
+
SERVER_KEY_EXCHANGE: 12,
|
|
32
|
+
CERTIFICATE_REQUEST: 13,
|
|
33
|
+
SERVER_HELLO_DONE: 14,
|
|
34
|
+
CERTIFICATE_VERIFY: 15,
|
|
35
|
+
CLIENT_KEY_EXCHANGE: 16,
|
|
36
|
+
FINISHED: 20,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const ALERT_LEVEL = Object.freeze({ WARNING: 1, FATAL: 2 });
|
|
40
|
+
export const ALERT_DESC = Object.freeze({
|
|
41
|
+
CLOSE_NOTIFY: 0,
|
|
42
|
+
HANDSHAKE_FAILURE: 40,
|
|
43
|
+
BAD_CERTIFICATE: 42,
|
|
44
|
+
DECRYPT_ERROR: 51,
|
|
45
|
+
INTERNAL_ERROR: 80,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
|
49
|
+
export const CIPHER_SUITE = 0xc02b;
|
|
50
|
+
|
|
51
|
+
export const NAMED_GROUP = Object.freeze({ secp256r1: 0x0017 });
|
|
52
|
+
export const EC_POINT_FORMAT = Object.freeze({ uncompressed: 0 });
|
|
53
|
+
|
|
54
|
+
// SignatureAndHashAlgorithm
|
|
55
|
+
export const HASH_ALG = Object.freeze({ sha256: 4, sha384: 5, sha512: 6 });
|
|
56
|
+
export const SIG_ALG = Object.freeze({ rsa: 1, ecdsa: 3 });
|
|
57
|
+
|
|
58
|
+
// ClientCertificateType
|
|
59
|
+
export const CERT_TYPE = Object.freeze({ ecdsa_sign: 64, rsa_sign: 1 });
|
|
60
|
+
|
|
61
|
+
export const EXTENSION = Object.freeze({
|
|
62
|
+
SUPPORTED_GROUPS: 10,
|
|
63
|
+
EC_POINT_FORMATS: 11,
|
|
64
|
+
SIGNATURE_ALGORITHMS: 13,
|
|
65
|
+
EXTENDED_MASTER_SECRET: 23,
|
|
66
|
+
RENEGOTIATION_INFO: 0xff01,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const FINISHED_LABEL = Object.freeze({
|
|
70
|
+
CLIENT: 'client finished',
|
|
71
|
+
SERVER: 'server finished',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/** A parsed DTLS record from {@link parseRecords}. */
|
|
75
|
+
export interface Record {
|
|
76
|
+
type: number;
|
|
77
|
+
version: number;
|
|
78
|
+
epoch: number;
|
|
79
|
+
seq: number;
|
|
80
|
+
fragment: Buffer;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A parsed handshake message header from {@link parseHandshake}. */
|
|
84
|
+
export interface Handshake {
|
|
85
|
+
msgType: number;
|
|
86
|
+
length: number;
|
|
87
|
+
messageSeq: number;
|
|
88
|
+
fragmentOffset: number;
|
|
89
|
+
fragmentLength: number;
|
|
90
|
+
body: Buffer;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---- Vector / integer encoders -------------------------------------------
|
|
94
|
+
|
|
95
|
+
/** Encode a uint24 (3 bytes, big-endian). */
|
|
96
|
+
export function uint24(n: number): Buffer {
|
|
97
|
+
return Buffer.from([(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Read a uint24 at offset. */
|
|
101
|
+
export function readUint24(buf: Buffer, off: number): number {
|
|
102
|
+
return (buf[off]! << 16) | (buf[off + 1]! << 8) | buf[off + 2]!;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Length-prefixed vector with a 1-byte length. */
|
|
106
|
+
export function vec8(body: Buffer): Buffer {
|
|
107
|
+
return Buffer.concat([Buffer.from([body.length]), body]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Length-prefixed vector with a 2-byte length. */
|
|
111
|
+
export function vec16(body: Buffer): Buffer {
|
|
112
|
+
const len = Buffer.alloc(2);
|
|
113
|
+
len.writeUInt16BE(body.length, 0);
|
|
114
|
+
return Buffer.concat([len, body]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Length-prefixed vector with a 3-byte length. */
|
|
118
|
+
export function vec24(body: Buffer): Buffer {
|
|
119
|
+
return Buffer.concat([uint24(body.length), body]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---- Record layer ---------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Encode a DTLS record (13-byte header + fragment).
|
|
126
|
+
* @param type - CONTENT_TYPE
|
|
127
|
+
* @param epoch
|
|
128
|
+
* @param seq - 48-bit sequence number
|
|
129
|
+
* @param fragment
|
|
130
|
+
* @param version
|
|
131
|
+
*/
|
|
132
|
+
export function encodeRecord(
|
|
133
|
+
type: number,
|
|
134
|
+
epoch: number,
|
|
135
|
+
seq: number,
|
|
136
|
+
fragment: Buffer,
|
|
137
|
+
version: number = DTLS_1_2
|
|
138
|
+
): Buffer {
|
|
139
|
+
const header = Buffer.alloc(13);
|
|
140
|
+
header.writeUInt8(type, 0);
|
|
141
|
+
header.writeUInt16BE(version, 1);
|
|
142
|
+
header.writeUInt16BE(epoch, 3);
|
|
143
|
+
header.writeUIntBE(seq, 5, 6);
|
|
144
|
+
header.writeUInt16BE(fragment.length, 11);
|
|
145
|
+
return Buffer.concat([header, fragment]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Parse one or more DTLS records from a datagram. Multiple records may be
|
|
150
|
+
* packed into a single UDP packet.
|
|
151
|
+
* @param packet
|
|
152
|
+
*/
|
|
153
|
+
export function parseRecords(packet: Buffer): Record[] {
|
|
154
|
+
const records: Record[] = [];
|
|
155
|
+
let off = 0;
|
|
156
|
+
while (off + 13 <= packet.length) {
|
|
157
|
+
const type = packet.readUInt8(off);
|
|
158
|
+
const version = packet.readUInt16BE(off + 1);
|
|
159
|
+
const epoch = packet.readUInt16BE(off + 3);
|
|
160
|
+
const seq = packet.readUIntBE(off + 5, 6);
|
|
161
|
+
const length = packet.readUInt16BE(off + 11);
|
|
162
|
+
const start = off + 13;
|
|
163
|
+
if (start + length > packet.length) break;
|
|
164
|
+
records.push({ type, version, epoch, seq, fragment: packet.slice(start, start + length) });
|
|
165
|
+
off = start + length;
|
|
166
|
+
}
|
|
167
|
+
return records;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---- Handshake layer -------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Encode a DTLS handshake message header + body (unfragmented).
|
|
174
|
+
* @param msgType - HANDSHAKE_TYPE
|
|
175
|
+
* @param messageSeq
|
|
176
|
+
* @param body
|
|
177
|
+
*/
|
|
178
|
+
export function encodeHandshake(
|
|
179
|
+
msgType: number,
|
|
180
|
+
messageSeq: number,
|
|
181
|
+
body: Buffer
|
|
182
|
+
): Buffer {
|
|
183
|
+
const header = Buffer.alloc(12);
|
|
184
|
+
header.writeUInt8(msgType, 0);
|
|
185
|
+
uint24(body.length).copy(header, 1); // length
|
|
186
|
+
header.writeUInt16BE(messageSeq, 4); // message_seq
|
|
187
|
+
uint24(0).copy(header, 6); // fragment_offset
|
|
188
|
+
uint24(body.length).copy(header, 9); // fragment_length
|
|
189
|
+
return Buffer.concat([header, body]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Parse a handshake message header.
|
|
194
|
+
* @param buf - starts at the handshake header
|
|
195
|
+
*/
|
|
196
|
+
export function parseHandshake(buf: Buffer): Handshake {
|
|
197
|
+
const msgType = buf.readUInt8(0);
|
|
198
|
+
const length = readUint24(buf, 1);
|
|
199
|
+
const messageSeq = buf.readUInt16BE(4);
|
|
200
|
+
const fragmentOffset = readUint24(buf, 6);
|
|
201
|
+
const fragmentLength = readUint24(buf, 9);
|
|
202
|
+
const body = buf.slice(12, 12 + fragmentLength);
|
|
203
|
+
return { msgType, length, messageSeq, fragmentOffset, fragmentLength, body };
|
|
204
|
+
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
*
|
|
4
|
-
* Ported from Chromium's WebRTC implementation:
|
|
5
|
-
* chromium/src/third_party/blink/renderer/modules/peerconnection/byte_buffer_queue.{h,cc}
|
|
6
|
-
*
|
|
2
|
+
* @file ByteBufferQueue - Efficient byte buffer with O(1) append and O(n) read.
|
|
3
|
+
*
|
|
7
4
|
* This class provides efficient management of byte buffers with O(1) append operations
|
|
8
5
|
* and O(n) read operations. Clients can append entire buffers then copy data out across
|
|
9
6
|
* buffer boundaries.
|
|
10
|
-
*
|
|
11
|
-
* @license
|
|
7
|
+
*
|
|
8
|
+
* @license MIT
|
|
12
9
|
* @author nmhung1210
|
|
13
10
|
*/
|
|
14
11
|
|
|
@@ -16,7 +13,7 @@
|
|
|
16
13
|
|
|
17
14
|
/**
|
|
18
15
|
* A ByteBufferQueue manages a queue of byte buffers with efficient operations.
|
|
19
|
-
*
|
|
16
|
+
*
|
|
20
17
|
* Invariants maintained:
|
|
21
18
|
* - size_ = sum of all buffer sizes - frontBufferOffset_
|
|
22
19
|
* - No buffer in the queue is empty
|
|
@@ -24,52 +21,56 @@
|
|
|
24
21
|
* - Otherwise, frontBufferOffset_ < front buffer size
|
|
25
22
|
*/
|
|
26
23
|
class ByteBufferQueue {
|
|
24
|
+
/**
|
|
25
|
+
* Total number of bytes available to read.
|
|
26
|
+
* @private {number}
|
|
27
|
+
*/
|
|
28
|
+
#size: number;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Double-ended queue of byte buffers.
|
|
32
|
+
* Append() pushes to the back, ReadInto() consumes from the front.
|
|
33
|
+
* @private {Buffer[]}
|
|
34
|
+
*/
|
|
35
|
+
#buffers: Buffer[];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Offset from which to start reading the front buffer.
|
|
39
|
+
* @private {number}
|
|
40
|
+
*/
|
|
41
|
+
#frontBufferOffset: number;
|
|
42
|
+
|
|
27
43
|
constructor() {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*/
|
|
32
|
-
this._size = 0;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Double-ended queue of byte buffers.
|
|
36
|
-
* Append() pushes to the back, ReadInto() consumes from the front.
|
|
37
|
-
* @private {Buffer[]}
|
|
38
|
-
*/
|
|
39
|
-
this._buffers = [];
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Offset from which to start reading the front buffer.
|
|
43
|
-
* @private {number}
|
|
44
|
-
*/
|
|
45
|
-
this._frontBufferOffset = 0;
|
|
44
|
+
this.#size = 0;
|
|
45
|
+
this.#buffers = [];
|
|
46
|
+
this.#frontBufferOffset = 0;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Number of bytes that can be read.
|
|
50
51
|
* @returns {number}
|
|
51
52
|
*/
|
|
52
|
-
get size() {
|
|
53
|
-
return this
|
|
53
|
+
get size(): number {
|
|
54
|
+
return this.#size;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
58
|
* Returns true if no bytes are available to read.
|
|
58
59
|
* @returns {boolean}
|
|
59
60
|
*/
|
|
60
|
-
get empty() {
|
|
61
|
-
return this
|
|
61
|
+
get empty(): boolean {
|
|
62
|
+
return this.#size === 0;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* Copies data into the given buffer. Consumes bytes from the queue.
|
|
66
67
|
* Returns the number of bytes written to bufferOut.
|
|
67
|
-
*
|
|
68
|
+
*
|
|
68
69
|
* @param {Buffer} bufferOut - Destination buffer to read into
|
|
69
70
|
* @returns {number} Number of bytes actually read
|
|
70
71
|
* @throws {TypeError} If bufferOut is not a Buffer
|
|
71
72
|
*/
|
|
72
|
-
readInto(bufferOut) {
|
|
73
|
+
readInto(bufferOut: Buffer): number {
|
|
73
74
|
if (!Buffer.isBuffer(bufferOut)) {
|
|
74
75
|
throw new TypeError('bufferOut must be a Buffer');
|
|
75
76
|
}
|
|
@@ -77,9 +78,9 @@ class ByteBufferQueue {
|
|
|
77
78
|
let readAmount = 0;
|
|
78
79
|
let outputOffset = 0;
|
|
79
80
|
|
|
80
|
-
while (outputOffset < bufferOut.length && this.
|
|
81
|
-
const frontBuffer = this
|
|
82
|
-
const availableInFront = frontBuffer.length - this
|
|
81
|
+
while (outputOffset < bufferOut.length && this.#buffers.length > 0) {
|
|
82
|
+
const frontBuffer = this.#buffers[0]!;
|
|
83
|
+
const availableInFront = frontBuffer.length - this.#frontBufferOffset;
|
|
83
84
|
const remainingOutput = bufferOut.length - outputOffset;
|
|
84
85
|
const toCopy = Math.min(availableInFront, remainingOutput);
|
|
85
86
|
|
|
@@ -87,8 +88,8 @@ class ByteBufferQueue {
|
|
|
87
88
|
frontBuffer.copy(
|
|
88
89
|
bufferOut,
|
|
89
90
|
outputOffset,
|
|
90
|
-
this
|
|
91
|
-
this
|
|
91
|
+
this.#frontBufferOffset,
|
|
92
|
+
this.#frontBufferOffset + toCopy
|
|
92
93
|
);
|
|
93
94
|
|
|
94
95
|
readAmount += toCopy;
|
|
@@ -96,27 +97,27 @@ class ByteBufferQueue {
|
|
|
96
97
|
|
|
97
98
|
if (toCopy < availableInFront) {
|
|
98
99
|
// Partial read, update offset
|
|
99
|
-
this
|
|
100
|
+
this.#frontBufferOffset += toCopy;
|
|
100
101
|
} else {
|
|
101
102
|
// Consumed entire front buffer, remove it
|
|
102
|
-
this.
|
|
103
|
-
this
|
|
103
|
+
this.#buffers.shift();
|
|
104
|
+
this.#frontBufferOffset = 0;
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
this
|
|
108
|
-
this
|
|
108
|
+
this.#size -= readAmount;
|
|
109
|
+
this.#checkInvariants();
|
|
109
110
|
return readAmount;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
/**
|
|
113
114
|
* Appends a buffer to the queue. Takes ownership of the buffer.
|
|
114
115
|
* Empty buffers are ignored.
|
|
115
|
-
*
|
|
116
|
+
*
|
|
116
117
|
* @param {Buffer} buffer - Buffer to append
|
|
117
118
|
* @throws {TypeError} If buffer is not a Buffer
|
|
118
119
|
*/
|
|
119
|
-
append(buffer) {
|
|
120
|
+
append(buffer: Buffer): void {
|
|
120
121
|
if (!Buffer.isBuffer(buffer)) {
|
|
121
122
|
throw new TypeError('buffer must be a Buffer');
|
|
122
123
|
}
|
|
@@ -125,31 +126,31 @@ class ByteBufferQueue {
|
|
|
125
126
|
return; // Ignore empty buffers
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
this
|
|
129
|
-
this.
|
|
130
|
-
this
|
|
129
|
+
this.#size += buffer.length;
|
|
130
|
+
this.#buffers.push(buffer);
|
|
131
|
+
this.#checkInvariants();
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
/**
|
|
134
135
|
* Clears all stored buffers.
|
|
135
136
|
*/
|
|
136
|
-
clear() {
|
|
137
|
-
this
|
|
138
|
-
this
|
|
139
|
-
this
|
|
140
|
-
this
|
|
137
|
+
clear(): void {
|
|
138
|
+
this.#buffers = [];
|
|
139
|
+
this.#frontBufferOffset = 0;
|
|
140
|
+
this.#size = 0;
|
|
141
|
+
this.#checkInvariants();
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
/**
|
|
144
145
|
* Reads and consumes exactly n bytes.
|
|
145
|
-
*
|
|
146
|
+
*
|
|
146
147
|
* @param {number} n - Number of bytes to read
|
|
147
148
|
* @returns {Buffer} Buffer containing exactly n bytes
|
|
148
149
|
* @throws {RangeError} If fewer than n bytes are available
|
|
149
150
|
*/
|
|
150
|
-
read(n) {
|
|
151
|
-
if (n > this
|
|
152
|
-
throw new RangeError(`Cannot read ${n} bytes, only ${this
|
|
151
|
+
read(n: number): Buffer {
|
|
152
|
+
if (n > this.#size) {
|
|
153
|
+
throw new RangeError(`Cannot read ${n} bytes, only ${this.#size} available`);
|
|
153
154
|
}
|
|
154
155
|
if (n === 0) {
|
|
155
156
|
return Buffer.allocUnsafe(0);
|
|
@@ -167,12 +168,12 @@ class ByteBufferQueue {
|
|
|
167
168
|
|
|
168
169
|
/**
|
|
169
170
|
* Peeks at data without consuming it.
|
|
170
|
-
*
|
|
171
|
-
* @param {number} [n=this
|
|
171
|
+
*
|
|
172
|
+
* @param {number} [n=this.#size] - Number of bytes to peek
|
|
172
173
|
* @returns {Buffer} Buffer containing up to n bytes (not consumed)
|
|
173
174
|
*/
|
|
174
|
-
peek(n = this
|
|
175
|
-
const peekAmount = Math.min(n, this
|
|
175
|
+
peek(n: number = this.#size): Buffer {
|
|
176
|
+
const peekAmount = Math.min(n, this.#size);
|
|
176
177
|
if (peekAmount === 0) {
|
|
177
178
|
return Buffer.allocUnsafe(0);
|
|
178
179
|
}
|
|
@@ -180,10 +181,10 @@ class ByteBufferQueue {
|
|
|
180
181
|
const result = Buffer.allocUnsafe(peekAmount);
|
|
181
182
|
let written = 0;
|
|
182
183
|
let bufferIndex = 0;
|
|
183
|
-
let offset = this
|
|
184
|
+
let offset = this.#frontBufferOffset;
|
|
184
185
|
|
|
185
|
-
while (written < peekAmount && bufferIndex < this.
|
|
186
|
-
const buffer = this
|
|
186
|
+
while (written < peekAmount && bufferIndex < this.#buffers.length) {
|
|
187
|
+
const buffer = this.#buffers[bufferIndex]!;
|
|
187
188
|
const available = buffer.length - offset;
|
|
188
189
|
const toCopy = Math.min(available, peekAmount - written);
|
|
189
190
|
|
|
@@ -202,29 +203,29 @@ class ByteBufferQueue {
|
|
|
202
203
|
* @private
|
|
203
204
|
* @throws {Error} If invariants are violated
|
|
204
205
|
*/
|
|
205
|
-
|
|
206
|
+
#checkInvariants(): void {
|
|
206
207
|
if (process.env.NODE_ENV !== 'production') {
|
|
207
208
|
let bufferSizeSum = 0;
|
|
208
|
-
for (const buffer of this
|
|
209
|
+
for (const buffer of this.#buffers) {
|
|
209
210
|
if (buffer.length === 0) {
|
|
210
211
|
throw new Error('Invariant violation: empty buffer in queue');
|
|
211
212
|
}
|
|
212
213
|
bufferSizeSum += buffer.length;
|
|
213
214
|
}
|
|
214
215
|
|
|
215
|
-
const expectedSize = bufferSizeSum - this
|
|
216
|
-
if (this
|
|
216
|
+
const expectedSize = bufferSizeSum - this.#frontBufferOffset;
|
|
217
|
+
if (this.#size !== expectedSize) {
|
|
217
218
|
throw new Error(
|
|
218
|
-
`Invariant violation: size=${this
|
|
219
|
+
`Invariant violation: size=${this.#size}, expected=${expectedSize}`
|
|
219
220
|
);
|
|
220
221
|
}
|
|
221
222
|
|
|
222
|
-
if (this.
|
|
223
|
-
if (this
|
|
223
|
+
if (this.#buffers.length === 0) {
|
|
224
|
+
if (this.#frontBufferOffset !== 0) {
|
|
224
225
|
throw new Error('Invariant violation: offset non-zero with empty queue');
|
|
225
226
|
}
|
|
226
227
|
} else {
|
|
227
|
-
if (this
|
|
228
|
+
if (this.#frontBufferOffset >= this.#buffers[0]!.length) {
|
|
228
229
|
throw new Error('Invariant violation: offset >= front buffer size');
|
|
229
230
|
}
|
|
230
231
|
}
|
|
@@ -232,4 +233,5 @@ class ByteBufferQueue {
|
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
|
|
236
|
+
export default ByteBufferQueue;
|
|
237
|
+
export { ByteBufferQueue };
|