feat(protocol): separate chunking from encoding

This commit is contained in:
Szymon Lesisz
2024-04-17 13:07:07 +02:00
committed by martin
parent 2aef4dc077
commit 2f445ba734
17 changed files with 241 additions and 140 deletions

View File

@@ -1,6 +1,9 @@
import { HEADER_SIZE } from './constants';
import { TransportProtocolEncode } from '../types';
// for type compatibility, bridge doesn't send chunks
export const getChunkHeader = (_data: Buffer) => Buffer.alloc(0);
// this file is basically combination of "trezor v1 protocol" and "bridge protocol"
// there is actually no officially described bridge protocol, but in fact there is one
// it is because bridge does some parts of the protocol itself (like chunking)
@@ -21,5 +24,5 @@ export const encode: TransportProtocolEncode = (data, options) => {
// then put in the actual message
data.copy(encodedBuffer, HEADER_SIZE);
return [encodedBuffer];
return encodedBuffer;
};

View File

@@ -1,4 +1,3 @@
export const MESSAGE_MAGIC_HEADER_BYTE = 63;
export const MESSAGE_HEADER_BYTE = 0x23;
export const HEADER_SIZE = 1 + 1 + 2 + 4; // MESSAGE_HEADER_BYTE + MESSAGE_HEADER_BYTE + messageType + dataLength
export const BUFFER_SIZE = 64;
export const HEADER_SIZE = 1 + 1 + 1 + 2 + 4; // MESSAGE_MAGIC_HEADER_BYTE + MESSAGE_HEADER_BYTE + MESSAGE_HEADER_BYTE + messageType + dataLength

View File

@@ -38,6 +38,6 @@ export const decode: TransportProtocolDecode = bytes => {
return {
length,
messageType,
payload: buffer.subarray(HEADER_SIZE + 1), // each chunk is prefixed by magic byte
payload: buffer.subarray(HEADER_SIZE),
};
};

View File

@@ -1,11 +1,13 @@
import {
HEADER_SIZE,
MESSAGE_HEADER_BYTE,
BUFFER_SIZE,
MESSAGE_MAGIC_HEADER_BYTE,
} from './constants';
import { HEADER_SIZE, MESSAGE_HEADER_BYTE, MESSAGE_MAGIC_HEADER_BYTE } from './constants';
import { TransportProtocolEncode } from '../types';
export const getChunkHeader = (_data: Buffer) => {
const header = Buffer.alloc(1);
header.writeUInt8(MESSAGE_MAGIC_HEADER_BYTE);
return header;
};
export const encode: TransportProtocolEncode = (data, options) => {
const { messageType } = options;
if (typeof messageType === 'string') {
@@ -13,42 +15,23 @@ export const encode: TransportProtocolEncode = (data, options) => {
}
const fullSize = HEADER_SIZE + data.length;
const chunkSize = options.chunkSize || BUFFER_SIZE;
const encodedBuffer = Buffer.alloc(fullSize);
// 1 byte
encodedBuffer.writeUInt8(MESSAGE_MAGIC_HEADER_BYTE, 0);
// 2*1 byte
encodedBuffer.writeUInt8(MESSAGE_HEADER_BYTE, 0);
encodedBuffer.writeUInt8(MESSAGE_HEADER_BYTE, 1);
encodedBuffer.writeUInt8(MESSAGE_HEADER_BYTE, 2);
// 2 bytes
encodedBuffer.writeUInt16BE(messageType, 2);
encodedBuffer.writeUInt16BE(messageType, 3);
// 4 bytes (so 8 in total)
encodedBuffer.writeUInt32BE(data.length, 4);
// 4 bytes (so 9 in total)
encodedBuffer.writeUInt32BE(data.length, 5);
// then put in the actual message
data.copy(encodedBuffer, HEADER_SIZE);
const size = chunkSize - 1; // chunkSize - 1 byte of MESSAGE_MAGIC_HEADER_BYTE
const chunkCount = Math.ceil(encodedBuffer.length / size) || 1;
// size with one reserved byte for header
const result: Buffer[] = [];
// How many pieces will there actually be
// slice and dice
for (let i = 0; i < chunkCount; i++) {
const start = i * size;
const end = Math.min((i + 1) * size, encodedBuffer.length);
const buffer = Buffer.alloc(chunkSize);
buffer.writeUInt8(MESSAGE_MAGIC_HEADER_BYTE);
encodedBuffer.copy(buffer, 1, start, end);
result.push(buffer);
}
return result;
return encodedBuffer;
};

View File

@@ -6,15 +6,15 @@ export type TransportProtocolDecode = (bytes: ArrayBuffer) => {
export interface TransportProtocolEncodeOptions {
messageType: number | string;
chunkSize?: number;
}
export type TransportProtocolEncode = (
data: Buffer,
options: TransportProtocolEncodeOptions,
) => Buffer[];
) => Buffer;
export interface TransportProtocol {
encode: TransportProtocolEncode;
decode: TransportProtocolDecode;
getChunkHeader: (data: Buffer) => Buffer;
}

View File

@@ -2,18 +2,16 @@ import { bridge } from '../src/index';
describe('protocol-bridge', () => {
it('encode', () => {
let chunks;
let result;
// encode small chunk, message without data
chunks = bridge.encode(Buffer.alloc(0), { messageType: 55 });
expect(chunks.length).toEqual(1);
expect(chunks[0].length).toEqual(6);
result = bridge.encode(Buffer.alloc(0), { messageType: 55 });
expect(result.length).toEqual(6);
// encode big chunk, message with data
chunks = bridge.encode(Buffer.alloc(371), { messageType: 55 });
expect(chunks.length).toEqual(1);
expect(chunks[0].subarray(0, 6).toString('hex')).toEqual('003700000173');
expect(chunks[0].readUint32BE(2)).toEqual(371);
expect(chunks[0].length).toEqual(371 + 6);
result = bridge.encode(Buffer.alloc(371), { messageType: 55 });
expect(result.subarray(0, 6).toString('hex')).toEqual('003700000173');
expect(result.readUint32BE(2)).toEqual(371);
expect(result.length).toEqual(371 + 6);
// fail to encode unsupported messageType (string)
expect(() => bridge.encode(Buffer.alloc(64), { messageType: 'Initialize' })).toThrow(

View File

@@ -1,27 +1,18 @@
import { v1 } from '../src/index';
import { HEADER_SIZE } from '../src/protocol-v1/constants';
describe('protocol-v1', () => {
it('encode', () => {
let chunks;
// encode only one chunk, message without data
chunks = v1.encode(Buffer.alloc(0), { messageType: 55 });
expect(chunks.length).toEqual(1);
expect(chunks[0].length).toEqual(64);
let result;
// encode message without data
result = v1.encode(Buffer.alloc(0), { messageType: 55 });
expect(result.length).toEqual(HEADER_SIZE);
// encode multiple chunks, message with data
chunks = v1.encode(Buffer.alloc(371), { messageType: 55 });
expect(chunks.length).toEqual(7);
chunks.forEach((chunk, index) => {
expect(chunk.length).toEqual(64);
if (index === 0) {
// first chunk with additional data
expect(chunk.subarray(0, 9).toString('hex')).toEqual('3f2323003700000173');
expect(chunk.readUint32BE(5)).toEqual(371);
} else {
// following chunk starts with MESSAGE_MAGIC_HEADER_BYTE
expect(chunk.subarray(0, 5).toString('hex')).toEqual('3f00000000');
}
});
// encode message with data
result = v1.encode(Buffer.alloc(371).fill(0xa3), { messageType: 55 });
expect(result.length).toEqual(371 + HEADER_SIZE);
expect(result.subarray(0, HEADER_SIZE).toString('hex')).toEqual('3f2323003700000173');
expect(result.subarray(HEADER_SIZE).toString('hex')).toEqual('a3'.repeat(371));
// fail to encode unsupported messageType (string)
expect(() => v1.encode(Buffer.alloc(64), { messageType: 'Initialize' })).toThrow(

View File

@@ -2,6 +2,7 @@ import { WebUSB } from 'usb';
import { v1 as protocolV1, bridge as protocolBridge } from '@trezor/protocol';
import { receive as receiveUtil } from '@trezor/transport/src/utils/receive';
import { createChunks } from '@trezor/transport/src/utils/send';
import { SessionsBackground } from '@trezor/transport/src/sessions/background';
import { SessionsClient } from '@trezor/transport/src/sessions/client';
import { UsbApi } from '@trezor/transport/src/api/usb';
@@ -41,7 +42,12 @@ export const createApi = (apiStr: 'usb' | 'udp', logger?: Log) => {
new Uint8Array(Buffer.from(data, 'hex')),
);
const buffers = protocolV1.encode(payload, { messageType });
const encodedMessage = protocolV1.encode(payload, { messageType });
const buffers = createChunks(
encodedMessage,
protocolV1.getChunkHeader(encodedMessage),
api.chunkSize,
);
for (let i = 0; i < buffers.length; i++) {
const bufferSegment = buffers[i];
@@ -65,12 +71,12 @@ export const createApi = (apiStr: 'usb' | 'udp', logger?: Log) => {
}
throw new Error(result.error);
}),
protocolV1.decode,
protocolV1,
);
return {
success: true as const,
payload: protocolBridge.encode(payload, { messageType })[0].toString('hex'),
payload: protocolBridge.encode(payload, { messageType }).toString('hex'),
};
} catch (err) {
return { success: false as const, error: err.message as string };

View File

@@ -108,6 +108,11 @@ export abstract class AbstractApi extends TypedEmitter<{
| typeof ERRORS.UNEXPECTED_ERROR
>;
/**
* packet size for api
*/
abstract chunkSize: number;
protected success<T>(payload: T): Success<T> {
return success(payload);
}

View File

@@ -7,6 +7,7 @@ import { AsyncResultWithTypedError, ResultWithTypedError } from '../types';
import * as ERRORS from '../errors';
export class UdpApi extends AbstractApi {
chunkSize = 64;
interface = UDP.createSocket('udp4');
protected communicating = false;

View File

@@ -28,6 +28,7 @@ interface TransportInterfaceDevice {
const INTERFACE_DEVICE_DISCONNECTED = 'The device was disconnected.' as const;
export class UsbApi extends AbstractApi {
chunkSize = 64;
devices: TransportInterfaceDevice[] = [];
usbInterface: ConstructorParams['usbInterface'];

View File

@@ -7,7 +7,7 @@ import {
AbstractTransportMethodParams,
} from './abstract';
import { AbstractApi } from '../api/abstract';
import { buildBuffers } from '../utils/send';
import { buildMessage, createChunks } from '../utils/send';
import { receiveAndParse } from '../utils/receive';
import { SessionsClient } from '../sessions/client';
import * as ERRORS from '../errors';
@@ -176,7 +176,12 @@ export abstract class AbstractApiTransport extends AbstractTransport {
});
}
public call({ session, name, data, protocol }: AbstractTransportMethodParams<'call'>) {
public call({
session,
name,
data,
protocol: customProtocol,
}: AbstractTransportMethodParams<'call'>) {
return this.scheduleAction(
async () => {
const getPathBySessionResponse = await this.sessionsClient.getPathBySession({
@@ -193,8 +198,18 @@ export abstract class AbstractApiTransport extends AbstractTransport {
const { path } = getPathBySessionResponse.payload;
try {
const { encode, decode } = protocol || v1Protocol;
const buffers = buildBuffers(this.messages, name, data, encode);
const protocol = customProtocol || v1Protocol;
const bytes = buildMessage({
messages: this.messages,
name,
data,
encode: protocol.encode,
});
const buffers = createChunks(
bytes,
protocol.getChunkHeader(bytes),
this.api.chunkSize,
);
for (let i = 0; i < buffers.length; i++) {
const chunk = buffers[i];
@@ -214,7 +229,7 @@ export abstract class AbstractApiTransport extends AbstractTransport {
}
throw new Error(result.error);
}),
decode,
protocol,
);
return this.success(message);
@@ -247,8 +262,14 @@ export abstract class AbstractApiTransport extends AbstractTransport {
const { path } = getPathBySessionResponse.payload;
try {
const { encode } = protocol || v1Protocol;
const buffers = buildBuffers(this.messages, name, data, encode);
const { encode, getChunkHeader } = protocol || v1Protocol;
const bytes = buildMessage({
messages: this.messages,
name,
data,
encode,
});
const buffers = createChunks(bytes, getChunkHeader(bytes), this.api.chunkSize);
for (let i = 0; i < buffers.length; i++) {
const chunk = buffers[i];
@@ -270,7 +291,10 @@ export abstract class AbstractApiTransport extends AbstractTransport {
});
}
public receive({ session, protocol }: AbstractTransportMethodParams<'receive'>) {
public receive({
session,
protocol: customProtocol,
}: AbstractTransportMethodParams<'receive'>) {
return this.scheduleAction(async () => {
const getPathBySessionResponse = await this.sessionsClient.getPathBySession({
session,
@@ -281,7 +305,7 @@ export abstract class AbstractApiTransport extends AbstractTransport {
const { path } = getPathBySessionResponse.payload;
try {
const { decode } = protocol || v1Protocol;
const protocol = customProtocol || v1Protocol;
const message = await receiveAndParse(
this.messages,
() =>
@@ -292,7 +316,7 @@ export abstract class AbstractApiTransport extends AbstractTransport {
return result.payload;
}),
decode,
protocol,
);
return this.success(message);

View File

@@ -2,7 +2,7 @@ import { versionUtils, createDeferred, Deferred, createTimeoutPromise } from '@t
import { PROTOCOL_MALFORMED, bridge as bridgeProtocol } from '@trezor/protocol';
import { bridgeApiCall } from '../utils/bridgeApiCall';
import * as bridgeApiResult from '../utils/bridgeApiResult';
import { buildBuffers } from '../utils/send';
import { buildMessage } from '../utils/send';
import { receiveAndParse } from '../utils/receive';
import {
AbstractTransport,
@@ -225,11 +225,21 @@ export class BridgeTransport extends AbstractTransport {
}
// https://github.dev/trezor/trezord-go/blob/f559ee5079679aeb5f897c65318d3310f78223ca/core/core.go#L534
public call({ session, name, data, protocol }: AbstractTransportMethodParams<'call'>) {
public call({
session,
name,
data,
protocol: customProtocol,
}: AbstractTransportMethodParams<'call'>) {
return this.scheduleAction(
async signal => {
const { encode, decode } = protocol || bridgeProtocol;
const [bytes] = buildBuffers(this.messages, name, data, encode);
const protocol = customProtocol || bridgeProtocol;
const bytes = buildMessage({
messages: this.messages,
name,
data,
encode: protocol.encode,
});
const response = await this.post(`/call`, {
params: session,
body: bytes.toString('hex'),
@@ -241,7 +251,7 @@ export class BridgeTransport extends AbstractTransport {
const message = await receiveAndParse(
this.messages,
() => Promise.resolve(Buffer.from(response.payload, 'hex')),
decode,
protocol,
);
return this.success(message);
@@ -253,7 +263,12 @@ export class BridgeTransport extends AbstractTransport {
public send({ session, name, data, protocol }: AbstractTransportMethodParams<'send'>) {
return this.scheduleAction(async signal => {
const { encode } = protocol || bridgeProtocol;
const [bytes] = buildBuffers(this.messages, name, data, encode);
const bytes = buildMessage({
messages: this.messages,
name,
data,
encode,
});
const response = await this.post('/post', {
params: session,
body: bytes.toString('hex'),
@@ -267,7 +282,10 @@ export class BridgeTransport extends AbstractTransport {
});
}
public receive({ session, protocol }: AbstractTransportMethodParams<'receive'>) {
public receive({
session,
protocol: customProtocol,
}: AbstractTransportMethodParams<'receive'>) {
return this.scheduleAction(async signal => {
const response = await this.post('/read', {
params: session,
@@ -277,11 +295,11 @@ export class BridgeTransport extends AbstractTransport {
if (!response.success) {
return response;
}
const { decode } = protocol || bridgeProtocol;
const protocol = customProtocol || bridgeProtocol;
const message = await receiveAndParse(
this.messages,
() => Promise.resolve(Buffer.from(response.payload, 'hex')),
decode,
protocol,
);
return this.success(message);

View File

@@ -1,13 +1,14 @@
import { Root } from 'protobufjs/light';
import { decode as decodeProtobuf, createMessageFromType } from '@trezor/protobuf';
import { TransportProtocolDecode } from '@trezor/protocol';
import { TransportProtocol } from '@trezor/protocol';
async function receiveRest(
result: Buffer,
receiver: () => Promise<ArrayBuffer>,
offset: number,
expectedLength: number,
chunkHeader: Buffer,
): Promise<void> {
if (offset >= expectedLength) {
return;
@@ -17,25 +18,23 @@ async function receiveRest(
if (data == null) {
throw new Error('Received no data.');
}
const length = offset + data.byteLength - 1;
Buffer.from(data).copy(result, offset, 1, length);
const length = offset + data.byteLength - chunkHeader.byteLength;
Buffer.from(data).copy(result, offset, chunkHeader.byteLength, length);
return receiveRest(result, receiver, length, expectedLength);
return receiveRest(result, receiver, length, expectedLength, chunkHeader);
}
export async function receive(
receiver: () => Promise<ArrayBuffer>,
decoder: TransportProtocolDecode,
) {
export async function receive(receiver: () => Promise<ArrayBuffer>, protocol: TransportProtocol) {
const data = await receiver();
const { length, messageType, payload } = decoder(data);
const { length, messageType, payload } = protocol.decode(data);
const result = Buffer.alloc(length);
const chunkHeader = protocol.getChunkHeader(Buffer.from(data));
if (length) {
payload.copy(result);
}
await receiveRest(result, receiver, payload.length, length);
await receiveRest(result, receiver, payload.length, length, chunkHeader);
return { messageType, payload: result };
}
@@ -43,9 +42,9 @@ export async function receive(
export async function receiveAndParse(
messages: Root,
receiver: () => Promise<ArrayBuffer>,
decoder: TransportProtocolDecode,
protocol: TransportProtocol,
) {
const { messageType, payload } = await receive(receiver, decoder);
const { messageType, payload } = await receive(receiver, protocol);
const { Message, messageName } = createMessageFromType(messages, messageType);
const message = decodeProtobuf(Message, payload);

View File

@@ -5,16 +5,41 @@ import { Root } from 'protobufjs/light';
import { encode as encodeProtobuf, createMessageFromName } from '@trezor/protobuf';
import { TransportProtocolEncode } from '@trezor/protocol';
export const buildBuffers = (
messages: Root,
name: string,
data: Record<string, unknown>,
encoder: TransportProtocolEncode,
) => {
export const createChunks = (data: Buffer, chunkHeader: Buffer, chunkSize: number) => {
if (!chunkSize || data.byteLength <= chunkSize) {
const buffer = Buffer.alloc(Math.max(chunkSize, data.byteLength));
data.copy(buffer);
return [buffer];
}
// create first chunk without chunkHeader
const chunks = [data.subarray(0, chunkSize)];
// create following chunks prefixed with chunkHeader
let position = chunkSize;
while (position < data.byteLength) {
const sliceEnd = Math.min(position + chunkSize - chunkHeader.byteLength, data.byteLength);
const slice = data.subarray(position, sliceEnd);
const chunk = Buffer.concat([chunkHeader, slice]);
chunks.push(Buffer.alloc(chunkSize).fill(chunk, 0, chunk.byteLength));
position = sliceEnd;
}
return chunks;
};
interface BuildMessageProps {
messages: Root;
name: string;
data: Record<string, unknown>;
encode: TransportProtocolEncode;
}
export const buildMessage = ({ messages, name, data, encode }: BuildMessageProps) => {
const { Message, messageType } = createMessageFromName(messages, name);
const buffer = encodeProtobuf(Message, data);
return encoder(buffer, {
return encode(buffer, {
messageType,
});
};

View File

@@ -383,34 +383,28 @@ describe('Usb', () => {
.spyOn(testUsbApi, 'write')
.mockImplementation(() => Promise.resolve({ success: true, payload: undefined }));
// override protocol options
const overrideProtocol = (protocol: typeof v1Protocol, chunkSize: number) =>
({
...protocol,
encode: (...[data, options]: Parameters<typeof protocol.encode>) =>
protocol.encode(data, { ...options, chunkSize }),
}) as typeof protocol;
const send = (chunkSize: number) =>
const send = () =>
transport.send({
name: 'SignMessage',
data: {
message: '00'.repeat(200),
},
session: acquireRes.payload,
protocol: overrideProtocol(v1Protocol, chunkSize),
protocol: v1Protocol,
}).promise;
// count encoded/sent chunks
await send(64); // default usb
await send(); // 64 default chunkSize for usb
expect(writeSpy).toHaveBeenCalledTimes(4);
writeSpy.mockClear();
await send(16); // smaller chunks
testUsbApi.chunkSize = 16;
await send(); // smaller chunks
expect(writeSpy).toHaveBeenCalledTimes(15);
writeSpy.mockClear();
await send(128); // bigger chunks
testUsbApi.chunkSize = 128;
await send(); // bigger chunks
expect(writeSpy).toHaveBeenCalledTimes(2);
writeSpy.mockClear();
});

View File

@@ -1,6 +1,6 @@
import * as protobuf from 'protobufjs/light';
import { v1 as v1Protocol, bridge as bridgeProtocol } from '@trezor/protocol';
import { buildBuffers } from '../src/utils/send';
import { buildMessage, createChunks } from '../src/utils/send';
import { receiveAndParse } from '../src/utils/receive';
const messages = {
@@ -97,34 +97,38 @@ const parsedMessages = protobuf.Root.fromJSON({
describe('encoding json -> protobuf -> json', () => {
fixtures.forEach(f => {
describe(`${f.name} - payload length ${f.in.source_account.length}`, () => {
test('bridgeProtocol: buildBuffers - receiveAndParse', async () => {
const result = buildBuffers(parsedMessages, f.name, f.in, bridgeProtocol.encode);
// bridgeProtocol returns only one big chunk
expect(result.length).toBe(1);
const [chunk] = result;
test('bridgeProtocol: buildMessage - receiveAndParse', async () => {
const result = buildMessage({
messages: parsedMessages,
name: f.name,
data: f.in,
encode: bridgeProtocol.encode,
});
const { length } = Buffer.from(f.in.source_account);
// chunk length cannot be less than message header/constant (28) + variable source_account length
// result length cannot be less than message header/constant (28) + variable source_account length
// additional bytes are expected (encoded Uint32) if message length is greater
expect(chunk.length).toBeGreaterThanOrEqual(28 + length);
let i = -1;
expect(result.length).toBeGreaterThanOrEqual(28 + length);
const decoded = await receiveAndParse(
parsedMessages,
() => {
i++;
return Promise.resolve(result[i]);
},
bridgeProtocol.decode,
() => Promise.resolve(result),
bridgeProtocol,
);
// then decode message and check, whether decoded message matches original json
expect(decoded.type).toEqual(f.name);
expect(decoded.message).toEqual(f.in);
});
test('v1Protocol: buildBuffers - receiveAndParse', async () => {
const result = buildBuffers(parsedMessages, f.name, f.in, v1Protocol.encode);
test('v1Protocol: buildMessage - createChunks - receiveAndParse', async () => {
const result = buildMessage({
messages: parsedMessages,
name: f.name,
data: f.in,
encode: v1Protocol.encode,
});
const chunks = createChunks(result, v1Protocol.getChunkHeader(result), 64);
// each protocol chunks are equal 64 bytes
result.forEach(chunk => {
chunks.forEach(chunk => {
expect(chunk.length).toEqual(64);
});
let i = -1;
@@ -133,9 +137,9 @@ describe('encoding json -> protobuf -> json', () => {
() => {
i++;
return Promise.resolve(result[i]);
return Promise.resolve(chunks[i]);
},
v1Protocol.decode,
v1Protocol,
);
// then decode message and check, whether decoded message matches original json
expect(decoded.type).toEqual(f.name);
@@ -144,3 +148,53 @@ describe('encoding json -> protobuf -> json', () => {
});
});
});
describe('createChunks', () => {
const chunkHeader = Buffer.from([63]);
test('small packet = one chunk', () => {
const result = createChunks(Buffer.alloc(63).fill(0x12), chunkHeader, 64);
expect(result.length).toBe(1);
expect(result[0].toString('hex')).toBe('12'.repeat(63) + '00');
});
test('exact packet = one chunk', () => {
const result = createChunks(Buffer.alloc(64), chunkHeader, 64);
expect(result.length).toBe(1);
});
test('byte overflow = two chunks', () => {
const result = createChunks(Buffer.alloc(65).fill('a0a1'), chunkHeader, 64);
expect(result.length).toBe(2);
// header + last byte from data
expect(result[1].subarray(0, 2).toString('hex')).toBe('3f61');
// the rest is filled with 00
expect(result[1].subarray(2).toString('hex')).toBe('00'.repeat(62));
});
test('exact packet, big chunkHeader = two chunks', () => {
const result = createChunks(
Buffer.alloc(64 + 64 - 7).fill(0x12),
Buffer.alloc(7).fill(0x73),
64,
);
expect(result.length).toBe(2);
});
test('byte overflow, big chunkHeader = three chunks', () => {
const result = createChunks(
Buffer.alloc(64 * 2 - 6).fill(0x12),
Buffer.alloc(7).fill(0x73),
64,
);
expect(result.length).toBe(3);
expect(result[2].subarray(0, 8).toString('hex')).toBe('7373737373737312');
expect(result[2].subarray(8).toString('hex')).toBe('00'.repeat(64 - 8));
});
test('chunkSize not set = one chunk', () => {
const result = createChunks(Buffer.alloc(128).fill(0x12), Buffer.alloc(7).fill(0x73), 0);
expect(result.length).toBe(1);
expect(result[0].byteLength).toBe(128);
});
});