Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/utils/byte_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ export type ByteUtils = {
compare: (buffer1: Uint8Array, buffer2: Uint8Array) => -1 | 0 | 1;
/** Concatenating all the Uint8Arrays in new Uint8Array. */
concat: (list: Uint8Array[]) => Uint8Array;
/** Copy bytes from source Uint8Array to target Uint8Array */
copy: (
source: Uint8Array,
target: Uint8Array,
targetStart?: number,
sourceStart?: number,
sourceEnd?: number
) => number;
/** Check if two Uint8Arrays are deep equal */
equals: (a: Uint8Array, b: Uint8Array) => boolean;
/** Create a Uint8Array from an array of numbers */
Expand Down
12 changes: 12 additions & 0 deletions src/utils/node_byte_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ export const nodeJsByteUtils = {
return Buffer.concat(list);
},

copy(
source: Uint8Array,
target: Uint8Array,
targetStart?: number,
sourceStart?: number,
sourceEnd?: number
): number {
return nodeJsByteUtils
.toLocalBufferType(source)
.copy(target, targetStart ?? 0, sourceStart ?? 0, sourceEnd ?? source.length);
},

equals(a: Uint8Array, b: Uint8Array): boolean {
return nodeJsByteUtils.toLocalBufferType(a).equals(b);
},
Expand Down
43 changes: 43 additions & 0 deletions src/utils/web_byte_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,49 @@ export const webByteUtils = {
return result;
},

copy(
source: Uint8Array,
target: Uint8Array,
targetStart?: number,
sourceStart?: number,
sourceEnd?: number
): number {
// validate and standardize passed-in sourceEnd
if (sourceEnd !== undefined && sourceEnd < 0) {
throw new RangeError(
`The value of "sourceEnd" is out of range. It must be >= 0. Received ${sourceEnd}`
);
}
sourceEnd = sourceEnd ?? source.length;

// validate and standardize passed-in sourceStart
if (sourceStart !== undefined && (sourceStart < 0 || sourceStart > sourceEnd)) {
throw new RangeError(
`The value of "sourceStart" is out of range. It must be >= 0 and <= ${sourceEnd}. Received ${sourceStart}`
);
}
sourceStart = sourceStart ?? 0;

// validate and standardize passed-in targetStart
if (targetStart !== undefined && targetStart < 0) {
throw new RangeError(
`The value of "targetStart" is out of range. It must be >= 0. Received ${targetStart}`
);
}
targetStart = targetStart ?? 0;

// figure out how many bytes we can copy
const srcSlice = source.subarray(sourceStart, sourceEnd);
const maxLen = Math.min(srcSlice.length, target.length - targetStart);
if (maxLen <= 0) {
return 0;
}

// perform the copy
target.set(srcSlice.subarray(0, maxLen), targetStart);
return maxLen;
},

equals(uint8Array: Uint8Array, otherUint8Array: Uint8Array): boolean {
if (uint8Array.byteLength !== otherUint8Array.byteLength) {
return false;
Expand Down
212 changes: 197 additions & 15 deletions test/node/byte_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type ByteUtilTest<K extends keyof ByteUtils> = {
web: boolean;
output: ReturnType<ByteUtils[K]> | null;
error: Error | null;
inputs: Parameters<ByteUtils[K]>;
}) => void;
};

Expand Down Expand Up @@ -412,31 +413,31 @@ const encodeUTF8IntoTests: ByteUtilTest<'encodeUTF8Into'>[] = [
{
name: 'should insert utf8 bytes into buffer',
inputs: [Buffer.alloc(7), 'abc\u{1f913}', 0],
expectation({ output, error }) {
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(7);
expect(this.inputs[0]).to.deep.equal(Buffer.from('abc\u{1f913}', 'utf8'));
expect(inputs[0]).to.deep.equal(Buffer.from('abc\u{1f913}', 'utf8'));
}
},
{
name: 'should return 0 and not modify input buffer',
inputs: [Uint8Array.from([2, 2]), '', 0],
expectation({ output, error }) {
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(0);
expect(this.inputs[0]).to.deep.equal(Uint8Array.from([2, 2]));
expect(inputs[0]).to.deep.equal(Uint8Array.from([2, 2]));
}
},
{
name: 'should insert replacement character bytes if string is not encodable',
inputs: [Uint8Array.from({ length: 10 }, () => 2), '\u{1f913}'.slice(0, 1), 2],
expectation({ output, error }) {
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(3);
expect(this.inputs[0]).to.have.property('2', 0xef);
expect(this.inputs[0]).to.have.property('3', 0xbf);
expect(this.inputs[0]).to.have.property('4', 0xbd);
const backToString = Buffer.from(this.inputs[0].subarray(2, 5)).toString('utf8');
expect(inputs[0]).to.have.property('2', 0xef);
expect(inputs[0]).to.have.property('3', 0xbf);
expect(inputs[0]).to.have.property('4', 0xbd);
const backToString = Buffer.from(inputs[0].subarray(2, 5)).toString('utf8');
const replacementCharacter = '\u{fffd}';
expect(backToString).to.equal(replacementCharacter);
}
Expand Down Expand Up @@ -676,6 +677,182 @@ const concatTests: ByteUtilTest<'concat'>[] = [
}
}
];
const copyTests: ByteUtilTest<'copy'>[] = [
{
Comment thread
baileympearson marked this conversation as resolved.
name: 'should copy bytes from source to target buffer, with no offsets',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0])],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(5);
expect(inputs[1]).to.deep.equal(Uint8Array.from([1, 2, 3, 4, 5]));
}
},
{
name: 'should copy bytes from source to target buffer, specify valid targetOffset',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), 2],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(3);
expect(inputs[1]).to.deep.equal(Uint8Array.from([0, 0, 1, 2, 3]));
}
},
{
name: 'should copy bytes from source to target buffer, specify high targetOffset',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), 10],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(0);
expect(inputs[1]).to.deep.equal(Uint8Array.from([0, 0, 0, 0, 0]));
}
},
{
name: 'should copy bytes from source to target buffer, specify negative targetOffset',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), -1],
expectation({ error }) {
expect(error).to.match(/The value of "targetStart" is out of range/i);
}
},
{
name: 'should copy bytes from source to target buffer, specify valid targetOffset and sourceOffset',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), 2, 1],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(3);
expect(inputs[1]).to.deep.equal(Uint8Array.from([0, 0, 2, 3, 4]));
}
},
{
name: 'should copy bytes from source to target buffer, specify undefined targetOffset and valid sourceOffset',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), undefined, 1],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(4);
expect(inputs[1]).to.deep.equal(Uint8Array.from([2, 3, 4, 5, 0]));
}
},
{
name: 'should copy bytes from source to target buffer, specify valid targetOffset and high sourceOffset',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), 2, 10],
expectation({ error }) {
expect(error).to.match(/The value of "sourceStart" is out of range/i);
}
},
{
name: 'should copy bytes from source to target buffer, specify valid targetOffset and negative sourceOffset',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), 2, -1],
expectation({ error }) {
expect(error).to.match(/The value of "sourceStart" is out of range/i);
}
},
{
name: 'should copy bytes from source to target buffer, specify valid targetOffset, sourceOffset, and sourceEnd',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), 2, 1, 3],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(2);
expect(inputs[1]).to.deep.equal(Uint8Array.from([0, 0, 2, 3, 0]));
}
},
{
name: 'should copy bytes from source to target buffer, specify valid targetOffset, undefined sourceOffset, and valid sourceEnd',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), 2, undefined, 3],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(3);
expect(inputs[1]).to.deep.equal(Uint8Array.from([0, 0, 1, 2, 3]));
}
},
{
name: 'should copy bytes from source to target buffer, specify undefined targetOffset, valid sourceOffset, and valid sourceEnd',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), undefined, 1, 3],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(2);
expect(inputs[1]).to.deep.equal(Uint8Array.from([2, 3, 0, 0, 0]));
}
},
{
name: 'should copy bytes from source to target buffer, specify undefined targetOffset, undefined sourceOffset, and valid sourceEnd',
inputs: [
Uint8Array.from([1, 2, 3, 4, 5]),
Uint8Array.from([0, 0, 0, 0, 0]),
undefined,
undefined,
3
],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(3);
expect(inputs[1]).to.deep.equal(Uint8Array.from([1, 2, 3, 0, 0]));
}
},
{
name: 'should copy bytes from source to target buffer, specify valid targetOffset, valid sourceOffset, and high sourceEnd',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), 2, 1, 10],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(3);
expect(inputs[1]).to.deep.equal(Uint8Array.from([0, 0, 2, 3, 4]));
}
},
{
name: 'should copy bytes from source to target buffer, specify valid targetOffset, valid sourceOffset, and negative sourceEnd',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([0, 0, 0, 0, 0]), 2, 1, -1],
expectation({ error }) {
expect(error).to.match(
/The value of "sourceEnd" is out of range. It must be >= 0. Received -1/i
);
}
},
{
name: 'should not copy bytes from source to empty target buffer',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([])],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(0);
expect(inputs[1]).to.deep.equal(Uint8Array.from([]));
}
},
{
name: 'should not copy bytes from empty source to target buffer',
inputs: [Uint8Array.from([]), Uint8Array.from([1, 2, 3, 4, 5])],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(0);
expect(inputs[1]).to.deep.equal(Uint8Array.from([1, 2, 3, 4, 5]));
}
},
{
name: 'should copy bytes from source to smaller target buffer',
inputs: [Uint8Array.from([1, 2, 3, 4, 5]), Uint8Array.from([6, 7, 8])],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(3);
expect(inputs[1]).to.deep.equal(Uint8Array.from([1, 2, 3]));
}
},
{
name: 'should copy bytes from smaller source to target buffer',
inputs: [Uint8Array.from([1, 2, 3]), Uint8Array.from([6, 7, 8, 9, 10])],
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(3);
expect(inputs[1]).to.deep.equal(Uint8Array.from([1, 2, 3, 9, 10]));
}
},
{
name: 'should copy bytes from one buffer to itself',
inputs: () => {
const buf = Uint8Array.from([1, 2, 3, 4, 5]);
return [buf, buf, 1, 2, 3];
},
expectation({ output, error, inputs }) {
expect(error).to.be.null;
expect(output).to.equal(1);
expect(inputs[1]).to.deep.equal(Uint8Array.from([1, 3, 3, 4, 5]));
}
}
];

const utils = new Map([
['nodeJsByteUtils', nodeJsByteUtils],
Expand All @@ -701,7 +878,8 @@ const table = new Map<keyof ByteUtils, ByteUtilTest<keyof ByteUtils>[]>([
['randomBytes', randomBytesTests],
['swap32', swap32Tests],
['compare', compareTests],
['concat', concatTests]
['concat', concatTests],
['copy', copyTests]
]);

describe('ByteUtils', () => {
Expand Down Expand Up @@ -969,12 +1147,11 @@ describe('ByteUtils', () => {
expect(byteUtils).to.have.property(utility).that.is.a('function');
let output = null;
let error = null;
const inputs: Parameters<ByteUtils[typeof utility]> =
typeof test.inputs === 'function' ? test.inputs() : test.inputs;

try {
output = byteUtils[utility].call(
null,
...(typeof test.inputs === 'function' ? test.inputs() : test.inputs)
);
output = byteUtils[utility].call(null, ...inputs);
} catch (thrownError) {
error = thrownError;
}
Expand All @@ -987,7 +1164,12 @@ describe('ByteUtils', () => {
expect(error).to.be.null;
}

test.expectation({ web: byteUtilsName === 'webByteUtils', output, error });
test.expectation({
web: byteUtilsName === 'webByteUtils',
output,
error,
inputs
});
});
}
});
Expand Down
Loading