• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

benrr101 / node-taglib-sharp / 46462135

pending completion
46462135

push

appveyor

Benjamin Russell
Merge branch 'release/v5.1.0'

3096 of 3788 branches covered (81.73%)

Branch coverage included in aggregate %.

2171 of 2171 new or added lines in 47 files covered. (100.0%)

25320 of 26463 relevant lines covered (95.68%)

437.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.23
/src/byteVector.ts
1
import * as IConv from "iconv-lite";
1✔
2
import * as fs from "fs";
1✔
3
import {IFileAbstraction} from "./fileAbstraction";
4
import {IStream} from "./stream";
5
import {Guards, NumberUtils} from "./utils";
1✔
6

7
/**
8
 * @summary Specifies the text encoding used when converting betweenInclusive a string and a
9
 *     {@link ByteVector}.
10
 * @remarks
11
 *     This enumeration is used by {@link ByteVector.fromString} and
12
 *     {@link ByteVector.toString}
13
 */
14
export enum StringType {
1✔
15
    /**
16
     * @summary The string is to be Latin-1 encoded.
17
     */
18
    Latin1 = 0,
1✔
19

20
    /**
21
     * @summary The string is to be UTF-16 encoded.
22
     */
23
    UTF16 = 1,
1✔
24

25
    /**
26
     * @summary The string is to be UTF-16BE encoded.
27
     */
28
    UTF16BE = 2,
1✔
29

30
    /**
31
     * @summary The string is to be UTF-8 encoded.
32
     */
33
    UTF8 = 3,
1✔
34

35
    /**
36
     * @summary The string is to be UTF-16LE encoded.
37
     */
38
    UTF16LE = 4
1✔
39
}
40

41
/**
42
 * Wrapper around the `iconv-lite` library to provide string encoding and decoding functionality.
43
 */
44
export class Encoding {
1✔
45
    private static readonly ENCODINGS = new Map<StringType, Encoding>([
1✔
46
        [StringType.Latin1, new Encoding("latin1")],
47
        [StringType.UTF8, new Encoding("utf8")],
48
        [StringType.UTF16BE, new Encoding("utf16-be")],
49
        [StringType.UTF16LE, new Encoding("utf16-le")]
50
    ]);
51

52
    /**
53
     * Contains the last generic UTF16 encoding read. Defaults to UTF16-LE
54
     */
55
    private static _lastUtf16Encoding: StringType = StringType.UTF16LE;
1✔
56

57
    private readonly _encoding: string;
58

59
    private constructor(encoding: string) {
60
        this._encoding = encoding;
4✔
61
    }
62

63
    /**
64
     * Gets the appropriate encoding instance for encoding and decoding strings, based on the
65
     * provided `type`.
66
     * @param type Type of string to get an {@link Encoding} class instance for
67
     * @param bom Optional, the byte order marker for the string. Used to determine UTF16 endianess
68
     */
69
    public static getEncoding(type: StringType, bom?: ByteVector): Encoding {
70
        // When reading a collection of UTF16 strings, sometimes only the first one will contain
71
        // the BOM. In that case, this field will inform the file what encoding to use for the
72
        // second string.
73
        if (type === StringType.UTF16) {
5,951✔
74
            // If we have a BOM, return the appropriate encoding.  Otherwise, assume we're
75
            // reading from a string that was already identified. In that case, we'll use
76
            // the last used encoding.
77
            if (bom && bom.length >= 2) {
45✔
78
                if (bom.get(0) === 0xFF && bom.get(1) === 0xFE) {
44!
79
                    this._lastUtf16Encoding = StringType.UTF16LE;
44✔
80
                } else {
81
                    this._lastUtf16Encoding = StringType.UTF16BE;
×
82
                }
83
            }
84

85
            type = this._lastUtf16Encoding;
45✔
86
        }
87

88
        return this.ENCODINGS.get(type);
5,951✔
89

90
        // NOTE: The original .NET implementation has the notion of "broken" latin behavior that
91
        //       uses Encoding.Default. I have removed it in this port because 1) this behavior is
92
        //       not used anywhere in the library, 2) Encoding.Default could be anything depending
93
        //       on the machine's region, 3) in .NET Core this is always UTF8.
94
        //       If it turns out we need support for other non unicode encodings, we'll want to
95
        //       revisit this.
96
    }
97

98
    public decode(data: Uint8Array): string {
99
        // @TODO: The next version of iconv-lite will add Uint8Array to the types for decode. Until
100
        //    then, I have word it should work w/an 'unsafe' cast. See
101
        //    https://github.com/ashtuchkin/iconv-lite/issues/293
102
        return IConv.decode(<Buffer> data, this._encoding);
2,327✔
103
    }
104

105
    public encode(text: string): Uint8Array {
106
        return IConv.encode(text, this._encoding);
3,621✔
107
    }
108
}
109

110
/**
111
 * Wrapper around a `Uint8Array` that provides functionality for reading and writing byte arrays.
112
 * @remarks
113
 *     Implementation of this class uses a single `Uint8Array` to store bytes. Due to
114
 *     `Uint8Array`s being fixed length, any operation that inserts or removes values into the
115
 *     instance will result in a copy of the internal array being made. If multiple additions will
116
 *     be made, rather than using multiple inserts/adds, the {@link ByteVector.concatenate} method
117
 *     is provided to group additions/inserts and therefore improve performance.
118
 *
119
 *     The original .NET implementation had an ubiquitous `mid` method that would return a subset
120
 *     of the bytes in the current instance. In versions <5 of the node port, `mid` would make a
121
 *     copy of the subset of the bytes. Since this was frequently done right before reading a
122
 *     number or string, this copy was extremely wasteful. In version 5, the `mid` method was
123
 *     replaced with `subarray` which behaves identically to `Uint8Array.subarray` and returns
124
 *     an instance that is a 'view' of an existing instance - no copying involved. However, all
125
 *     write operations make copies, instances that are backed by 'views' may waste memory by
126
 *     referencing a `Uint8Array` that is much larger than the view.
127
 *
128
 *     With this in mind, best practices for using `ByteVectors`:
129
 *     * Calling {@link ByteVector.subarray} is cheap, use it when possible
130
 *     * If storing a subset of a `ByteVector`, store a copy with {@link ByteVector.toByteVector}
131
 *     * If building a `ByteVector`, use {@link ByteVector.concatenate} when possible
132
 *     * If the instance should be immutable, use {@link ByteVector.makeReadOnly}
133
 */
134
export class ByteVector {
1✔
135

136
    // #region Members
137

138
    private static readonly CRC_TABLE: Uint32Array = new Uint32Array([
1✔
139
        0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
140
        0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
141
        0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
142
        0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
143
        0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
144
        0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
145
        0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
146
        0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
147
        0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
148
        0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
149
        0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
150
        0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
151
        0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
152
        0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
153
        0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
154
        0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
155
        0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
156
        0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
157
        0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
158
        0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
159
        0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
160
        0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
161
        0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
162
        0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
163
        0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
164
        0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
165
        0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
166
        0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
167
        0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
168
        0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
169
        0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
170
        0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
171
        0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
172
        0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
173
        0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
174
        0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
175
        0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
176
        0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
177
        0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
178
        0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
179
        0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
180
        0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
181
        0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
182
        0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
183
        0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
184
        0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
185
        0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
186
        0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
187
        0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
188
        0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
189
        0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
190
        0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
191
        0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
192
        0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
193
        0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
194
        0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
195
        0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
196
        0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
197
        0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
198
        0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
199
        0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
200
        0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
201
        0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
202
        0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
203
    ]);
204

205
    /**
206
     * Contains a one byte text delimiter
207
     */
208
    private static readonly TD1: ByteVector = ByteVector.fromSize(1).makeReadOnly();
1✔
209

210
    /**
211
     * Contains a two byte text delimiter
212
     */
213
    private static readonly TD2: ByteVector = ByteVector.fromSize(2).makeReadOnly();
1✔
214

215
    private _bytes: Uint8Array;
216
    private _isReadOnly: boolean = false;
33,430✔
217

218
    // #endregion
219

220
    // #region Constructors
221

222
    private constructor(bytes: Uint8Array) {
223
        this._bytes = bytes;
33,430✔
224
    }
225

226
    /**
227
     * Creates a {@link ByteVector} from a collection of bytes, byte arrays, and byte vectors. This
228
     * method is better to use when a known quantity of byte vectors will be concatenated together,
229
     * since doing multiple calls to {@link ByteVector.addByteVector} results in the entire byte
230
     * vector being copied for each call.
231
     * @param vectors ByteVectors, byte arrays, or straight bytes to concatenate together into a
232
     *     new {@link ByteVector}
233
     * @returns
234
     *     Single byte vector with the contents of the byte vectors in `vectors` concatenated
235
     *     together
236
     */
237
    // @TODO Remove usages of .addX when this can be substituted
238
    public static concatenate(... vectors: Array<Uint8Array|ByteVector|number|undefined>): ByteVector {
239
        // Get the length of the vector we need to create
240
        const totalLength = vectors.reduce<number>((accum, vector) => {
5,028✔
241
            if (vector === undefined || vector === null) {
20,087✔
242
                // Ignore falsy values
243
                return accum;
755✔
244
            }
245
            if (typeof(vector) === "number") {
19,332✔
246
                // Add 1 for a single byte
247
                return accum + 1;
2,604✔
248
            }
249

250
            // Add length of vector to length
251
            return accum + vector.length;
16,728✔
252
        }, 0);
253

254
        // Create a single big vector and copy the contents into it
255
        const result = ByteVector.fromSize(totalLength);
5,028✔
256
        let currentPosition = 0;
5,028✔
257
        for (const v of vectors) {
5,028✔
258
            if (v === undefined || v === null) {
20,087✔
259
                // Ignore falsy values
260
                continue;
755✔
261
            }
262

263
            if (typeof(v) === "number") {
19,332✔
264
                // We were given a single byte
265
                Guards.byte(v, "Byte values");
2,604✔
266
                result._bytes[currentPosition] = v;
2,604✔
267
                currentPosition += 1;
2,604✔
268
            } else {
269
                // We were given an array of bytes
270
                const getter = v instanceof ByteVector
16,728✔
271
                    ? (i: number) => v.get(i)
657,142✔
272
                    : (i: number) => v[i];
38✔
273

274
                for (let i = 0; i < v.length; i++) {
16,728✔
275
                    result._bytes[currentPosition + i] = getter(i);
657,180✔
276
                }
277
                currentPosition += v.length;
16,728✔
278
            }
279
        }
280

281
        return result;
5,028✔
282
    }
283

284
    /**
285
     * Creates an empty {@link ByteVector}
286
     */
287
    public static empty(): ByteVector {
288
        return new ByteVector(new Uint8Array(0));
4,356✔
289
    }
290

291
    /**
292
     * Creates a {@link ByteVector} from a base64 string.
293
     * @param str Base64 string to convert into a byte vector
294
     */
295
    public static fromBase64String(str: string): ByteVector {
296
        Guards.notNullOrUndefined(str, "str");
50✔
297

298
        const bytes = Buffer.from(str, "base64");
50✔
299
        return new ByteVector(bytes);
50✔
300
    }
301

302
    /**
303
     * Creates a {@link ByteVector} from a `Uint8Array` or `Buffer`
304
     * @param bytes Uint8Array of the bytes to put in the ByteVector
305
     * @param length Optionally, number of bytes to read. If this is not provided, it will default
306
     *     to the full length of `bytes`. If it is less than the length of `bytes`, `bytes` will be
307
     *     copied into the {@link ByteVector}.
308
     */
309
    public static fromByteArray(
310
        bytes: Uint8Array | Buffer | number[],
311
        length: number = bytes.length
4,275✔
312
    ): ByteVector {
313
        Guards.truthy(bytes, "bytes");
5,471✔
314
        Guards.safeUint(length, "length");
5,469✔
315
        if (length > bytes.length) {
5,465✔
316
            throw new Error("Argument out of range: length must be less than or equal to the length of the byte array");
1✔
317
        }
318

319
        if (!(bytes instanceof Uint8Array || bytes instanceof Buffer)) {
5,464✔
320
            bytes = new Uint8Array(bytes);
191✔
321
        }
322

323
        if (length < bytes.length) {
5,464✔
324
            bytes = new Uint8Array(bytes.subarray(0, length));
68✔
325
        }
326
        return new ByteVector(bytes);
5,464✔
327
    }
328

329
    /**
330
     * Creates a new instance by reading in the contents of a specified file abstraction.
331
     * @param abstraction File abstraction to read
332
     */
333
    public static fromFileAbstraction(abstraction: IFileAbstraction): ByteVector {
334
        Guards.truthy(abstraction, "abstraction");
3✔
335

336
        const stream = abstraction.readStream;
1✔
337
        const output = this.fromInternalStream(stream);
1✔
338
        abstraction.closeStream(stream);
1✔
339
        return output;
1✔
340
    }
341

342
    /**
343
     * Creates a 4 byte {@link ByteVector} with a signed 32-bit integer as the data
344
     * @param value Signed 32-bit integer to use as the data.
345
     * @param isBigEndian If `true`, `value` will be stored in big endian format. If `false`,
346
     *     `value` will be stored in little endian format
347
     */
348
    public static fromInt(value: number, isBigEndian: boolean = true): ByteVector {
27✔
349
        Guards.int(value, "value");
35✔
350

351
        const bytes = new Uint8Array(4);
30✔
352
        const dv = new DataView(bytes.buffer);
30✔
353
        dv.setInt32(0, value, !isBigEndian);
30✔
354

355
        return new ByteVector(bytes);
30✔
356
    }
357

358
    /**
359
     * Creates a ByteVector using the contents of an TagLibSharp-node stream as the contents. This
360
     * method reads from the current offset of the stream, not the beginning of the stream
361
     * @param stream TagLibSharp-node internal stream object
362
     */
363
    public static fromInternalStream(stream: IStream): ByteVector {
364
        // @TODO: Validate how much memory is used vs doing a concatenate
365
        Guards.truthy(stream, "stream");
11✔
366

367
        const vector = ByteVector.empty();
9✔
368
        const bytes = new Uint8Array(4096);
9✔
369
        while (true) {
9✔
370
            const bytesRead = stream.read(bytes, 0, bytes.length);
10✔
371
            if (bytesRead < bytes.length) {
10✔
372
                vector.addByteArray(bytes, bytesRead);
9✔
373
                break;
9✔
374
            } else {
375
                vector.addByteArray(bytes);
1✔
376
            }
377
        }
378

379
        return vector;
9✔
380
    }
381

382
    /**
383
     * Creates an 8 byte {@link ByteVector} with a signed 64-bit integer as the data
384
     * @param value Signed 64-bit integer to use as the data. If using a `bigint`, it must fit
385
     *     within 8 bytes. If using a `number`, it must be a safe integer.
386
     * @param isBigEndian If `true`, `value` will be stored in big endian format. If `false`,
387
     *     `value` will be stored in little endian format
388
     */
389
    public static fromLong(value: bigint | number, isBigEndian: boolean = true): ByteVector {
24✔
390
        let bigIntValue: bigint;
391
        if (typeof(value) === "number") {
44✔
392
            Guards.safeInt(value, "value");
6✔
393
            bigIntValue = BigInt(value);
6✔
394
        } else {
395
            Guards.long(value, "value");
38✔
396
            bigIntValue = value;
36✔
397
        }
398

399
        const bytes = new Uint8Array(8);
42✔
400
        const dv = new DataView(bytes.buffer);
42✔
401
        dv.setBigInt64(0, bigIntValue, !isBigEndian);
42✔
402

403
        return new ByteVector(bytes);
40✔
404
    }
405

406
    /**
407
     * Creates a {@link ByteVector} using the contents of a file as the data
408
     * @param path Path to the file to store in the ByteVector
409
     */
410
    public static fromPath(path: string): ByteVector {
411
        Guards.truthy(path, "path");
4✔
412

413
        // NOTE: We are doing this with read file b/c it removes the headache of working with streams
414
        // @TODO: Add support for async file reading
415
        const fileBuffer = fs.readFileSync(path);
1✔
416
        return ByteVector.fromByteArray(fileBuffer);
1✔
417
    }
418

419
    /**
420
     * Creates a 2 byte {@link ByteVector} with a signed 16-bit integer as the data
421
     * @param value Signed 16-bit integer to use as the data.
422
     * @param isBigEndian If `true`, `value` will be stored in big endian format. If `false`,
423
     *     `value` will be stored in little endian format
424
     */
425
    public static fromShort(value: number, isBigEndian: boolean = true): ByteVector {
26✔
426
        Guards.short(value, "value");
36✔
427

428
        const bytes = new Uint8Array(2);
29✔
429
        const dv = new DataView(bytes.buffer);
29✔
430
        dv.setInt16(0, value, !isBigEndian);
29✔
431

432
        return new ByteVector(bytes);
29✔
433
    }
434

435
    /**
436
     * Creates a {@link ByteVector} of a given length with a given value for all the elements
437
     * @param size Length of the ByteVector. Must be a positive safe integer
438
     * @param fill Byte value to initialize all elements to. Must be a positive 8-bit integer
439
     */
440
    public static fromSize(size: number, fill: number = 0x0): ByteVector {
6,167✔
441
        Guards.safeUint(size, "size");
6,936✔
442
        Guards.byte(fill, "fill");
6,931✔
443

444
        const bytes = new Uint8Array(size);
6,928✔
445
        bytes.fill(fill);
6,928✔
446
        return new ByteVector(bytes);
6,928✔
447
    }
448

449
    /**
450
     * Creates {@link ByteVector} with the contents of a stream as the data. The stream will be read
451
     * to the end before the ByteVector is returned.
452
     * @param readStream Readable stream that will be read in entirety.
453
     */
454
    public static fromStream(readStream: NodeJS.ReadableStream): Promise<ByteVector> {
455
        return new Promise<ByteVector>((complete, fail) => {
4✔
456
            if (!readStream) {
4✔
457
                fail(new Error("Null argument exception: Stream was not provided"));
2✔
458
            }
459

460
            // Setup the output
461
            const output = ByteVector.empty();
4✔
462

463
            // Setup the events to read the stream
464
            readStream.on("readable", () => {
4✔
465
                const bytes = <Buffer> readStream.read();
3✔
466
                if (bytes) {
3✔
467
                    output.addByteArray(bytes);
1✔
468
                }
469
            });
470
            readStream.on("end", () => {
2✔
471
                complete(output);
2✔
472
            });
473
            readStream.on("error", (error: Error) => {
2✔
474
                fail(error);
×
475
            });
476
        });
477
    }
478

479
    /**
480
     * Creates {@link ByteVector} with the byte representation of a string as the data.
481
     * @param text String to store in the ByteVector
482
     * @param type StringType to use to encode the string. If {@link StringType.UTF16} is used, the
483
     *        string will be encoded as UTF16-LE.
484
     * @param length Number of characters from the string to store in the ByteVector. Must be a
485
     *        positive 32-bit integer.
486
     */
487
    public static fromString(
488
        text: string,
489
        type: StringType,
490
        length: number = Number.MAX_SAFE_INTEGER
4,013✔
491
    ): ByteVector {
492
        // @TODO: Allow adding delimiters and find usages that immediately add a delimiter
493
        Guards.notNullOrUndefined(text, "text");
4,022✔
494
        if (!Number.isInteger(length) || !Number.isSafeInteger(length) || length < 0) {
4,022✔
495
            throw new Error("Argument out of range exception: length is invalid");
4✔
496
        }
497

498
        // If we're doing UTF16 w/o specifying an endian-ness, inject a BOM which also coerces
499
        // the converter to use UTF16LE
500
        const vector = type === StringType.UTF16
4,018✔
501
            ? new ByteVector(new Uint8Array([0xff, 0xfe]))
4,018✔
502
            : ByteVector.empty();
503

504
        // NOTE: This mirrors the behavior from the original .NET implementation where empty
505
        //       strings return an empty ByteVector (possibly with UTF16LE BOM)
506
        if (!text) {
4,018✔
507
            return vector;
397✔
508
        }
509

510
        // Shorten text if only part of it was requested
511
        if (text.length > length) {
3,621✔
512
            text = text.substring(0, length);
5✔
513
        }
514

515
        // Encode the string into bytes
516
        const textBytes = Encoding.getEncoding(type, vector).encode(text);
3,621✔
517
        vector.addByteArray(textBytes);
3,621✔
518
        return vector;
3,621✔
519
    }
520

521
    /**
522
     * Creates a 4 byte {@link ByteVector} with a positive 32-bit integer as the data
523
     * @param value Positive 32-bit integer to use as the data
524
     * @param isBigEndian If `true`, `value` will be stored in big endian format. If `false`,
525
     *     `value` will be stored in little endian format
526
     */
527
    public static fromUint(value: number, isBigEndian: boolean = true): ByteVector {
917✔
528
        Guards.uint(value, "value");
4,224✔
529

530
        const bytes = new Uint8Array(4);
4,219✔
531
        const dv = new DataView(bytes.buffer);
4,219✔
532
        dv.setUint32(0, value, !isBigEndian);
4,219✔
533
        return new ByteVector(bytes);
4,219✔
534
    }
535

536
    /**
537
     * Creates an 8 byte {@link ByteVector} with a positive 64-bit integer as the data
538
     * @param value Positive 64-bit integer to use as the data. If using a `bigint` it must fit
539
     *     within 8 bytes.
540
     * @param isBigEndian If `true`, `value` will be stored in big endian format. If `false`,
541
     *     `value` will be stored in little endian format
542
     */
543
    public static fromUlong(value: bigint | number, isBigEndian: boolean = true): ByteVector {
98✔
544
        let bigIntValue: bigint;
545
        if (typeof(value) === "number") {
371✔
546
            Guards.safeUint(value, "value");
174✔
547
            bigIntValue = BigInt(value);
171✔
548
        } else {
549
            Guards.ulong(value, "value");
197✔
550
            bigIntValue = value;
195✔
551
        }
552

553
        const bytes = new Uint8Array(8);
366✔
554
        const dv = new DataView(bytes.buffer);
366✔
555
        dv.setBigUint64(0, bigIntValue, !isBigEndian);
366✔
556
        return new ByteVector(bytes);
362✔
557
    }
558

559
    /**
560
     * Creates a 2 byte {@link ByteVector} with a positive 16-bit integer as the data
561
     * @param value Positive 16-bit integer to use as the data.
562
     * @param isBigEndian If `true`, `value` will be stored in big endian format. If `false`,
563
     *     `value` will be stored in little endian format
564
     */
565
    public static fromUshort(value: number, isBigEndian: boolean = true): ByteVector {
507✔
566
        Guards.ushort(value, "value");
1,993✔
567

568
        const bytes = new Uint8Array(2);
1,986✔
569
        const dv = new DataView(bytes.buffer);
1,986✔
570
        dv.setUint16(0, value, !isBigEndian);
1,986✔
571
        return new ByteVector(bytes);
1,986✔
572
    }
573

574
    // #endregion
575

576
    // #region Properties
577

578
    /**
579
     * Calculates the CRC32 of the current instance.
580
     */
581
    public get checksum(): number {
582
        let sum = 0;
152✔
583
        for (const b of this._bytes) {
152✔
584
            const index = NumberUtils.uintXor(NumberUtils.uintAnd(NumberUtils.uintRShift(sum, 24), 0xFF), b);
959✔
585
            sum = NumberUtils.uintXor(NumberUtils.uintLShift(sum, 8), ByteVector.CRC_TABLE[index]);
959✔
586
        }
587
        return sum;
152✔
588
    }
589

590
    /**
591
     * Whether the current instance has 0 bytes stored.
592
     */
593
    public get isEmpty(): boolean { return this._bytes?.length === 0; }
170!
594

595
    /**
596
     * Whether the current instance is read-only. If `true`, any call that will modify the instance
597
     * will throw.
598
     */
599
    public get isReadOnly(): boolean { return this._isReadOnly; }
159✔
600

601
    /**
602
     * Whether the current instance is a 'view' of another byte vector.
603
     */
604
    public get isView(): boolean {
605
        return this._bytes.byteOffset !== 0 && this._bytes.byteLength !== this._bytes.buffer.byteLength;
1,243✔
606
    }
607

608
    /**
609
     * Number of bytes currently in this instance.
610
     */
611
    public get length(): number { return this._bytes.length; }
2,400,310✔
612

613
    // #endregion
614

615
    // #region Static Methods
616

617
    /**
618
     * Gets the appropriate length null-byte text delimiter for the specified `type`.
619
     * @param type String type to get delimiter for
620
     */
621
    public static getTextDelimiter(type: StringType): ByteVector {
622
        return type === StringType.UTF16 || type === StringType.UTF16BE || type === StringType.UTF16LE
718✔
623
            ? ByteVector.TD2
718✔
624
            : ByteVector.TD1;
625
    }
626

627
    /**
628
     * Compares two byte vectors. Returns a numeric value
629
     * @param a Byte vector to compare against `b`
630
     * @param b Byte vector to compare against `a`
631
     * @returns
632
     *     `0` if the two vectors are the same. Any other value indicates the two are
633
     *     different. If the two vectors differ by length, this will be the length of `a` minus the
634
     *     length of `b`. If the lengths are the same it will be the difference between the first
635
     *     element that differs.
636
     */
637
    public static compare(a: ByteVector, b: ByteVector): number {
638
        Guards.truthy(a, "a");
73✔
639
        Guards.truthy(b, "b");
73✔
640

641
        let diff = a.length - b.length;
69✔
642
        for (let i = 0; diff === 0 && i < a.length; i++) {
69✔
643
            diff = a.get(i) - b.get(i);
202✔
644
        }
645

646
        return diff;
69✔
647
    }
648

649
    /**
650
     * Returns `true` if the contents of the two {@link ByteVector}s are identical, returns `false`
651
     * otherwise
652
     * @param first ByteVector to compare with `second`
653
     * @param second ByteVector to compare with `first`
654
     */
655
    public static equals(first: ByteVector, second: ByteVector): boolean {
656
        const fNull = !first;
67✔
657
        const sNull = !second;
67✔
658
        if (fNull && sNull) {
67✔
659
            // Since (f|s)null could be true with `undefined` OR `null`, we'll just let === decide it for us
660
            return first === second;
4✔
661
        }
662

663
        if (fNull || sNull) {
63✔
664
            // If only one is null/undefined, then they are not equal
665
            return false;
8✔
666
        }
667

668
        return ByteVector.compare(first, second) === 0;
55✔
669
    }
670

671
    // #endregion
672

673
    // #region Public Methods
674

675
    /**
676
     * Gets iterator for iterating over bytes in the current instance.
677
     */
678
    public *[Symbol.iterator](): Iterator<number> {
679
        for (const b of this._bytes) {
2✔
680
            yield b;
8✔
681
        }
682
    }
683

684
    /**
685
     * Adds a single byte to the end of the current instance
686
     * @param byte Value to add to the end of the ByteVector. Must be positive 8-bit integer.
687
     */
688
    public addByte(byte: number): void {
689
        Guards.byte(byte, "byte");
229✔
690
        this.throwIfReadOnly();
226✔
691

692
        this.addByteArray(new Uint8Array([byte]));
225✔
693
    }
694

695
    /**
696
     * Adds an array of bytes to the end of the current instance
697
     * @param data Array of bytes to add to the end of the ByteVector
698
     * @param length Number of elements from `data` to copy into the current instance
699
     */
700
    public addByteArray(data: Uint8Array, length?: number): void {
701
        Guards.truthy(data, "data");
4,428✔
702
        this.throwIfReadOnly();
4,426✔
703

704
        if (data.length === 0 || length === 0) {
4,425✔
705
            return;
10✔
706
        }
707

708
        // Create a copy of the existing byte array with additional space at the end for the new
709
        // byte array. Copy the new array into it.
710
        length = length || data.length;
4,415✔
711
        const oldData = this._bytes;
4,415✔
712
        const newData = length !== data.length ? data.subarray(0, length) : data;
4,415✔
713

714
        this._bytes = new Uint8Array(oldData.length + length);
4,415✔
715
        this._bytes.set(oldData);
4,415✔
716
        this._bytes.set(newData, oldData.length);
4,415✔
717
    }
718

719
    /**
720
     * Adds a {@link ByteVector} to the end of this ByteVector
721
     * @param data ByteVector to add to the end of this ByteVector
722
     */
723
    public addByteVector(data: ByteVector): void {
724
        Guards.truthy(data, "data");
559✔
725
        this.throwIfReadOnly();
557✔
726

727
        this.addByteArray(data._bytes);
556✔
728
    }
729

730
    /**
731
     * Removes all elements from this {@link ByteVector}
732
     * @remarks This method replaces the internal byte array with a new one.
733
     */
734
    public clear(): void {
735
        this.throwIfReadOnly();
5✔
736
        this._bytes = new Uint8Array(0);
3✔
737
    }
738

739
    /**
740
     * Determines if `pattern` exists at a certain `offset` in this byte vector.
741
     * @param pattern ByteVector to search for at in this byte vector
742
     * @param offset Position in this byte vector to search for the pattern. If omitted, defaults
743
     *     to `0`
744
     */
745
    public containsAt(pattern: ByteVector, offset: number = 0): boolean {
5✔
746
        Guards.truthy(pattern, "pattern");
915✔
747
        Guards.safeInt(offset, "offset");
911✔
748

749
        // Sanity check - make sure we're within the range of the comprehension
750
        if (offset < 0 || offset >= this.length || pattern.length === 0) {
907✔
751
            return false;
4✔
752
        }
753

754
        // Look through looking for a mismatch
755
        for (let i = 0; i < pattern.length; i++) {
903✔
756
            if (this._bytes[offset + i] !== pattern.get(i)) {
2,513✔
757
                return false;
476✔
758
            }
759
        }
760

761
        return true;
427✔
762
    }
763

764
    /**
765
     * Compares the current instance to another byte vector. Returns a numeric result.
766
     * @param other Other byte vector to compare against the current instance.
767
     */
768
    public compareTo(other: ByteVector): number {
769
        return ByteVector.compare(this, other);
9✔
770
    }
771

772
    /**
773
     * Determines whether this byte vector ends with the provided `pattern`.
774
     * @param pattern ByteVector to look for at the end of this byte vector
775
     */
776
    public endsWith(pattern: ByteVector): boolean {
777
        return this.containsAt(pattern, this.length - pattern.length);
35✔
778
    }
779

780
    /**
781
     * Determines whether this byte vector ends with a part of the `pattern`.
782
     * NOTE: if this instance ends with `pattern` perfectly, it must end with n-1 or
783
     * fewer bytes.
784
     * @param pattern ByteVector to look for at the end of this byte vector
785
     */
786
    public endsWithPartialMatch(pattern: ByteVector): number {
787
        Guards.truthy(pattern, "pattern");
9✔
788

789
        // Short circuit impossible calls
790
        if (pattern.length > this.length) {
7✔
791
            return -1;
1✔
792
        }
793

794
        // Try to match the last n-1 bytes of the source (where n is the pattern length), if that
795
        // fails, try to match n-2, n-3... until there are no more to try.
796
        const startIndex = this.length - pattern.length;
6✔
797
        for (let i = 0; i < pattern.length; i++) {
6✔
798
            const patternSubset = pattern.subarray(0, pattern.length - i);
20✔
799
            if (this.containsAt(patternSubset, startIndex + i)) {
20✔
800
                return startIndex + i;
5✔
801
            }
802
        }
803

804
        return -1;
1✔
805
    }
806

807
    /**
808
     * Determines if this instance has identical contents to the `other` instance.
809
     * @param other Other instance to compare against the current instance.
810
     */
811
    public equals(other: ByteVector): boolean {
812
        return ByteVector.equals(this, other);
49✔
813
    }
814

815
    /**
816
     * Searches this instance for the `pattern`. Returns the index of the first instance
817
     * of the pattern, or `-1` if it was not found. Providing a `byteAlign` requires the
818
     * pattern to appear at an index that is a multiple of the byteAlign parameter.
819
     * Example: searching "abcd" for "ab" with byteAlign 1 will return 0. Searching "abcd" for
820
     * "ab" with byteAlign 2 will return 1. Searching "00ab" for "ab" with byteAlign 2 will return
821
     * 2. Searching "0abc" with byteAlign 2 will return -1.
822
     * @param pattern Pattern of bytes to search this instance for
823
     * @param byteAlign Optional, byte alignment the pattern much align to
824
     */
825
    public find(pattern: ByteVector, byteAlign: number = 1): number {
121✔
826
        Guards.truthy(pattern, "pattern");
487✔
827
        Guards.uint(byteAlign, "byteAlign");
485✔
828
        Guards.greaterThanInclusive(byteAlign, 1, "byteAlign");
477✔
829

830
        // Sanity check impossible matches
831
        if (this.length === 0 || pattern.length === 0 || pattern.length > this.length) {
475✔
832
            return -1;
13✔
833
        }
834

835
        for (let i = 0; i < this.length; i += byteAlign) {
462✔
836
            let j = 0;
23,368✔
837
            while (j < pattern.length) {
23,368✔
838
                if (this._bytes[i + j] !== pattern.get(j)) {
23,746✔
839
                    break;
23,081✔
840
                }
841

842
                j++;
665✔
843
            }
844

845
            if (j === pattern.length ) {
23,368✔
846
                return i;
287✔
847
            }
848
        }
849

850
        return -1;
175✔
851
    }
852

853
    /**
854
     * Gets the byte at the given `index`.
855
     * @param index Element index to return
856
     */
857
    public get(index: number): number {
858
        Guards.uint(index, "index");
1,111,639✔
859
        Guards.lessThanInclusive(index, this.length - 1, "index");
1,111,634✔
860
        return this._bytes[index];
1,111,631✔
861
    }
862

863
    /**
864
     * Gets the index of the first occurrence of the specified value.
865
     * @param item A byte to find within the current instance.
866
     * @returns
867
     *     An integer containing the first index at which the value was found, or -1 if it was not
868
     *     found
869
     */
870
    public indexOf(item: number): number {
871
        return this._bytes.indexOf(item);
42✔
872
    }
873

874
    /**
875
     * Makes the current instance read-only, causing any call that would modify it or allow it to
876
     * be modified to throw.
877
     */
878
    public makeReadOnly(): this {
879
        this._isReadOnly = true;
207✔
880
        return this;
207✔
881
    }
882

883
    /**
884
     * Searches this instance for the `pattern` occurring after a given offset. Returns the index
885
     * of the first instance of the pattern, relative to the start of the array, or `-1` if it was
886
     * not found. Providing a `byteAlign` requires the pattern to appear at an index that is a
887
     * multiple of the byteAlign parameter. Example: searching "abcd" for "ab" with byteAlign 1
888
     * will return 0. Searching "abcd" for "ab" with byteAlign 2 will return 1. Searching "00ab"
889
     * for "ab" with byteAlign 2 will return 2. Searching "0abc" with byteAlign 2 will return -1.
890
     * @param pattern Pattern of bytes to search this instance for
891
     * @param offset Index into the instance to begin searching for `pattern`
892
     * @param byteAlign Optional, byte alignment the pattern much align to
893
     */
894
    public offsetFind(pattern: ByteVector, offset: number, byteAlign?: number): number {
895
        const findIndex = this.subarray(offset).find(pattern, byteAlign);
403✔
896
        return findIndex >= 0 ? findIndex + offset : findIndex;
391✔
897
    }
898

899
    /**
900
     * Resizes this instance to the length specified in `size`. If the desired size is
901
     * longer than the current length, it will be filled with the byte value in
902
     * `padding`. If the desired size is shorter than the current length, bytes will be
903
     * removed.
904
     * @param size Length of the byte vector after resizing. Must be unsigned 32-bit integer
905
     * @param padding Byte to fill any excess space created after resizing
906
     */
907
    public resize(size: number, padding: number = 0x0): void {
51✔
908
        Guards.uint(size, "size");
56✔
909
        Guards.byte(padding, "padding");
51✔
910

911
        const oldData = this._bytes;
48✔
912
        if (size < this.length) {
48✔
913
            // Shorten it
914
            this._bytes = new Uint8Array(size);
20✔
915
            this._bytes.set(oldData.subarray(0, size));
20✔
916
        } else if (this.length < size) {
28✔
917
            // Lengthen it
918
            this._bytes = new Uint8Array(size);
26✔
919
            this._bytes.set(oldData);
26✔
920
            this._bytes.fill(padding, oldData.length);
26✔
921
        }
922
        // Do nothing on same size
923
    }
924

925
    /**
926
     * Finds a byte vector by searching from the end of this instance and working towards the
927
     * beginning of this instance. Returns the index of the first instance of the pattern, or `-1`
928
     * if it was not found. Providing a `byteAlign` requires the pattern to appear at an
929
     * index that is a multiple of the byteAlign parameter.
930
     * Example: searching "abcd" for "ab" with byteAlign 1 will return 0. Searching "abcd" for
931
     * "ab" with byteAlign 2 will return 1. Searching "00ab" for "ab" with byteAlign 2 will return
932
     * 2. Searching "0abc" with byteAlign 2 will return -1.
933
     * @param pattern Pattern of bytes to search this instance for
934
     * @param byteAlign Optional, byte alignment the pattern must align to
935
     */
936
    public rFind(pattern: ByteVector, byteAlign: number = 1): number {
44✔
937
        Guards.truthy(pattern, "pattern");
53✔
938
        Guards.uint(byteAlign, "byteAlign");
51✔
939
        Guards.greaterThanInclusive(byteAlign, 1, "byteAlign");
47✔
940

941
        // Sanity check impossible matches
942
        if (pattern.length === 0 || pattern.length > this.length) {
46✔
943
            return -1;
3✔
944
        }
945

946
        const alignOffset = this.length % byteAlign;
43✔
947
        for (let i = this.length - alignOffset - pattern.length; i >= 0; i -= byteAlign) {
43✔
948
            let j = 0;
22,481✔
949
            while (j < pattern.length) {
22,481✔
950
                if (this._bytes[i + j] !== pattern.get(j)) {
22,527✔
951
                    break;
22,462✔
952
                }
953

954
                j++;
65✔
955
            }
956

957
            if (j === pattern.length) {
22,481✔
958
                return i;
19✔
959
            }
960
        }
961

962
        return -1;
24✔
963
    }
964

965
    /**
966
     * Sets the value at a specified index
967
     * @param index Index to set the value of
968
     * @param value Value to set at the index. Must be a valid integer betweenInclusive 0x0 and 0xff
969
     */
970
    public set(index: number, value: number): void {
971
        Guards.uint(index, "index");
15,815✔
972
        Guards.lessThanInclusive(index, this.length - 1, "index");
15,810✔
973
        Guards.byte(value, "value");
15,809✔
974

975
        this.splice(index, 1, [value]);
15,806✔
976
    }
977

978
    /**
979
     * Changes the contents of the current instance by removing or replacing existing elements
980
     * and/or adding new elements.
981
     * @param start Index at which to start changing the array. Must be less than the length of
982
     *     the instance
983
     * @param deleteCount Number of elements in the array to remove from start. If greater than
984
     *     the remaining length of the element, it will be capped at the remaining length
985
     * @param items Elements to add to the array beginning from start. If omitted, the method will
986
     *     only remove elements from the current instance.
987
     */
988
    public splice(start: number, deleteCount: number, items?: ByteVector|Uint8Array|number[]): void {
989
        Guards.safeUint(start, "start");
15,987✔
990
        Guards.lessThanInclusive(start, this.length, "start");
15,982✔
991
        Guards.safeUint(deleteCount, "deleteCount");
15,981✔
992
        this.throwIfReadOnly();
15,976✔
993

994
        // Determine how many elements we're *actually* deleting
995
        deleteCount = Math.min(deleteCount, this.length - start);
15,974✔
996

997
        const addCount = items ? items.length : 0;
15,974✔
998
        const newBytes = new Uint8Array(this._bytes.length - deleteCount + addCount);
15,974✔
999
        newBytes.set(this._bytes.subarray(0, start));
15,974✔
1000
        if (items) {
15,974✔
1001
            items = items instanceof ByteVector ? items._bytes : items;
15,966✔
1002
            newBytes.set(items, start);
15,966✔
1003
        }
1004
        newBytes.set(this._bytes.subarray(start + deleteCount), start + addCount);
15,974✔
1005

1006
        this._bytes = newBytes;
15,974✔
1007
    }
1008

1009
    /**
1010
     * Splits this byte vector into a list of byte vectors using a separator
1011
     * @param separator Object to use to split this byte vector
1012
     * @param byteAlign Byte align to use when splitting. in order to split when a pattern is
1013
     *     encountered, the index at which it is found must be divisible by this value.
1014
     * @param max Maximum number of objects to return or 0 to not limit the number. If that number
1015
     *     is reached, the last value will contain the remainder of the file even if it contains
1016
     *     more instances of `separator`.
1017
     * @returns ByteVector[] The split contents of the current instance
1018
     */
1019
    public split(separator: ByteVector, byteAlign: number = 1, max: number = 0): ByteVector[] {
56✔
1020
        Guards.truthy(separator, "pattern");
45✔
1021
        Guards.uint(byteAlign, "byteAlign");
43✔
1022
        Guards.greaterThanInclusive(byteAlign, 1, "byteAlign");
39✔
1023
        Guards.uint(max, "max");
38✔
1024

1025
        const vectors = [];
34✔
1026
        const condition = (o: number): boolean => o !== -1 && (max < 1 || max > vectors.length + 1);
65✔
1027
        const increment = (o: number): number => this.offsetFind(separator, o + separator.length, byteAlign);
34✔
1028

1029
        let previousOffset = 0;
34✔
1030
        let offset = this.offsetFind(separator, 0, byteAlign);
34✔
1031
        for (offset; condition(offset); offset = increment(offset)) {
34✔
1032
            vectors.push(this.subarray(previousOffset, offset - previousOffset));
31✔
1033
            previousOffset = offset + separator.length;
31✔
1034
        }
1035

1036
        if (previousOffset < this.length) {
34✔
1037
            vectors.push(this.subarray(previousOffset));
29✔
1038
        }
1039

1040
        return vectors;
34✔
1041
    }
1042

1043
    /**
1044
     * Returns a window over the current instance.
1045
     * @param startIndex Offset into this instance where the comprehension begins
1046
     * @param length Number of elements from the instance to include. If omitted, defaults to the
1047
     *     remainder of the instance
1048
     */
1049
    public subarray(startIndex: number, length: number = this._bytes.length - startIndex): ByteVector {
792✔
1050
        Guards.safeUint(startIndex, "startIndex");
8,873✔
1051
        Guards.safeUint(length, "length");
8,863✔
1052
        return new ByteVector(this._bytes.subarray(startIndex, startIndex + length));
8,857✔
1053
    }
1054

1055
    /**
1056
     * Checks whether or not a pattern appears at the beginning of the current instance.
1057
     * @param pattern ByteVector containing the pattern to check for in the current instance.
1058
     * @returns
1059
     *     `true` if the pattern was found at the beginning of the current instance, `false`
1060
     *     otherwise.
1061
     */
1062
    public startsWith(pattern: ByteVector): boolean {
1063
        return this.containsAt(pattern, 0);
523✔
1064
    }
1065

1066
    /**
1067
     * Returns the current instance as a base64 encoded string.
1068
     */
1069
    public toBase64String(): string {
1070
        return Buffer.from(this._bytes.buffer, this._bytes.byteOffset, this._bytes.byteLength)
10✔
1071
            .toString("base64");
1072
    }
1073

1074
    /**
1075
     * Returns the bytes for the instance. Don't use it unless you need to.
1076
     * @internal
1077
     * @deprecated DON'T USE IT UNLESS YOU HAVE NO CHOICE.
1078
     */
1079
    public toByteArray(): Uint8Array {
1080
        return this._bytes;
1✔
1081
    }
1082

1083
    /**
1084
     * Returns a writable copy of the bytes represented by this instance.
1085
     * @remarks This is a **copy** of the data. Use sparingly.
1086
     */
1087
    public toByteVector(): ByteVector {
1088
        if (this.isView) {
1,075✔
1089
            const bytes = new Uint8Array(this._bytes);
254✔
1090
            return new ByteVector(bytes);
254✔
1091
        }
1092

1093
        // This is a concrete instance, return a new instance pointing to the existing bytes
1094
        // This is safe because the existing bytes will be copied on write
1095
        return new ByteVector(this._bytes);
821✔
1096
    }
1097

1098
    /**
1099
     * Converts the first eight bytes of the current instance to a double-precision floating-point
1100
     * value.
1101
     * @param mostSignificantByteFirst If `true` the most significant byte appears first (big
1102
     *        endian format).
1103
     * @throws Error If there are less than eight bytes in the current instance.
1104
     * @returns A double value containing the value read from the current instance.
1105
     */
1106
    public toDouble(mostSignificantByteFirst: boolean = true): number {
17✔
1107
        // NOTE: This is the behavior from the .NET implementation, due to BitConverter behavior
1108
        if (this.length < 8) {
22✔
1109
            throw new Error("Invalid operation: Cannot convert a byte vector of <8 bytes to double");
8✔
1110
        }
1111
        const dv = new DataView(this._bytes.buffer, this._bytes.byteOffset, 8);
14✔
1112
        return dv.getFloat64(0, !mostSignificantByteFirst);
14✔
1113
    }
1114

1115
    /**
1116
     * Converts the first four bytes of the current instance to a single-precision floating-point
1117
     * value.
1118
     * @param mostSignificantByteFirst If `true` the most significant byte appears first (big
1119
     *        endian format).
1120
     * @throws Error If there are less than four bytes in the current instance
1121
     * @returns A float value containing the value read from the current instance.
1122
     */
1123
    public toFloat(mostSignificantByteFirst: boolean = true): number {
11✔
1124
        const dv = new DataView(this._bytes.buffer, this._bytes.byteOffset, 4);
15✔
1125
        return dv.getFloat32(0, !mostSignificantByteFirst);
11✔
1126
    }
1127

1128
    /**
1129
     * Converts the first four bytes of the current instance to a signed integer. If the current
1130
     * instance is less than four bytes, the most significant bytes will be filled with 0x00.
1131
     * @param mostSignificantByteFirst If `true` the most significant byte appears first (big
1132
     *        endian format)
1133
     * @returns A signed integer value containing the value read from the current instance
1134
     */
1135
    public toInt(mostSignificantByteFirst: boolean = true): number {
18✔
1136
        const dv = this.getSizedDataView(4, mostSignificantByteFirst);
64✔
1137
        return dv.getInt32(0, !mostSignificantByteFirst);
64✔
1138
    }
1139

1140
    /**
1141
     * Converts the first eight bytes of the current instance to a signed long. If the current
1142
     * instance is less than eight bytes, the most significant bytes will be filled with 0x00.
1143
     * @param mostSignificantByteFirst If `true` the most significant byte appears first (big
1144
     *     endian format)
1145
     * @returns
1146
     *     A signed long value containing the value read from the current instance,
1147
     *     represented as a BigInt due to JavaScript's 52-bit integer limitation.
1148
     */
1149
    public toLong(mostSignificantByteFirst: boolean = true): bigint {
14✔
1150
        const dv = this.getSizedDataView(8, mostSignificantByteFirst);
248✔
1151
        return dv.getBigInt64(0, !mostSignificantByteFirst);
248✔
1152
    }
1153

1154
    /**
1155
     * Converts the first two bytes of the current instance to a signed short. If the current
1156
     * instance is less than two bytes, the most significant bytes will be filled with 0x00.
1157
     * @param mostSignificantByteFirst If `true` the most significant byte appears first (big
1158
     *    endian format)
1159
     * @returns A signed short value containing the value read from the current instance
1160
     */
1161
    public toShort(mostSignificantByteFirst: boolean = true): number {
35✔
1162
        const dv = this.getSizedDataView(2, mostSignificantByteFirst);
43✔
1163
        return dv.getInt16(0, !mostSignificantByteFirst);
43✔
1164
    }
1165

1166
    /**
1167
     * Converts a portion of the current instance to a string using a specified encoding
1168
     * @param type Value indicating the encoding to use when converting to a string.
1169
     * @returns String containing the converted bytes
1170
     */
1171
    public toString(type: StringType): string {
1172
        // @TODO: Would it be useful to have a null terminated version that finds first null
1173
        //      terminator and returns that subset of bytes?
1174
        const bom = type === StringType.UTF16 && this.length > 1
2,330✔
1175
            ? this.subarray(0, 2)
2,330✔
1176
            : undefined;
1177
        return Encoding.getEncoding(type, bom).decode(this._bytes);
2,330✔
1178
    }
1179

1180
    /**
1181
     * Converts the current instance into an array of strings starting at the specified offset and
1182
     * using the specified encoding, assuming the values are `null` separated and limiting it to a
1183
     * specified number of items.
1184
     * @param type A {@link StringType} value indicating the encoding to use when converting
1185
     * @param count Value specifying a limit to the number of strings to create. Once the limit has
1186
     *        been reached, the last string will be filled by the remainder of the data
1187
     * @returns Array of strings containing the converted text.
1188
     */
1189
    public toStrings(type: StringType, count: number = Number.MAX_SAFE_INTEGER): string[] {
73✔
1190
        Guards.safeUint(count, "count");
136✔
1191

1192
        const delimiter = ByteVector.getTextDelimiter(type);
134✔
1193
        let ptr = 0;
134✔
1194
        const strings = [];
134✔
1195
        while (ptr < this.length && strings.length < count) {
134✔
1196
            // Find the next delimiter
1197
            const delimiterPosition = this.offsetFind(delimiter, ptr, delimiter.length);
217✔
1198
            if (delimiterPosition < 0) {
217✔
1199
                // We didn't find another delimiter, so break out of the loop
1200
                break;
115✔
1201
            }
1202

1203
            const str = this.subarray(ptr, delimiterPosition - ptr).toString(type);
102✔
1204
            strings.push(str);
102✔
1205

1206
            ptr = delimiterPosition + delimiter.length;
102✔
1207
        }
1208

1209
        // If there's any remaining bytes, convert them to string
1210
        if (ptr < this.length && strings.length < count) {
134✔
1211
            const str = this.subarray(ptr).toString(type);
115✔
1212
            strings.push(str);
115✔
1213
        }
1214

1215
        return strings;
134✔
1216
    }
1217

1218
    /**
1219
     * Converts the first four bytes of the current instance to an unsigned integer. If the current
1220
     * instance is less than four bytes, the most significant bytes will be filled with 0x00.
1221
     * @param mostSignificantByteFirst If `true` the most significant byte appears first (big
1222
     *     endian format)
1223
     * @returns An unsigned integer value containing the value read from the current instance
1224
     */
1225
    public toUint(mostSignificantByteFirst: boolean = true): number {
611✔
1226
        const dv = this.getSizedDataView(4, mostSignificantByteFirst);
2,439✔
1227
        return dv.getUint32(0, !mostSignificantByteFirst);
2,439✔
1228
    }
1229

1230
    /**
1231
     * Converts the first eight bytes of the current instance to an unsigned long. If the current
1232
     * instance is less than eight bytes, the most significant bytes will be filled with 0x00.
1233
     * @param mostSignificantByteFirst If `true` the most significant byte appears first (big
1234
     *     endian format)
1235
     * @returns
1236
     *     An unsigned short value containing the value read from the current instance,
1237
     *     represented as a BigInt due to JavaScript's 32-bit integer limitation
1238
     */
1239
    public toUlong(mostSignificantByteFirst: boolean = true): bigint {
115✔
1240
        const dv = this.getSizedDataView(8, mostSignificantByteFirst);
125✔
1241
        return dv.getBigUint64(0, !mostSignificantByteFirst);
125✔
1242
    }
1243

1244
    /**
1245
     * Converts the first two bytes of the current instance to an unsigned short. If the current
1246
     * instance is less than two bytes, the most significant bytes will be filled with 0x00.
1247
     * @param mostSignificantByteFirst If `true` the most significant byte appears first (big
1248
     *     endian format)
1249
     * @returns An unsigned short value containing the value read from the current instance
1250
     */
1251
    public toUshort(mostSignificantByteFirst: boolean = true): number {
403✔
1252
        const dv = this.getSizedDataView(2, mostSignificantByteFirst);
1,292✔
1253
        return dv.getUint16(0, !mostSignificantByteFirst);
1,292✔
1254
    }
1255

1256
    // #endregion
1257

1258
    private getSizedDataView(size: number, mostSignificantByteFirst: boolean): DataView {
1259
        const difference = size - this._bytes.length;
4,211✔
1260
        if (difference <= 0) {
4,211✔
1261
            // Comprehension is at least the required size
1262
            return new DataView(this._bytes.buffer, this._bytes.byteOffset, size);
3,948✔
1263
        }
1264

1265
        // Comprehension is too short. Pad it out.
1266
        const fullSizeArray = new Uint8Array(size);
263✔
1267
        fullSizeArray.set(this._bytes, mostSignificantByteFirst ? difference : 0);
263✔
1268
        return new DataView(fullSizeArray.buffer);
263✔
1269
    }
1270

1271
    private throwIfReadOnly(): void {
1272
        if (this._isReadOnly) {
21,190✔
1273
            throw new Error("Invalid operation: Cannot modify a read-only ByteVector");
7✔
1274
        }
1275
    }
1276
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc