blob: d14c0ae5959800de32bbab3e96c58eb7cb7f4196 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { assert } from 'chai';
import { Buffer } from 'buffer';
import StreamReader from '../../../lib/structure/io/binary/internals/StreamReader.js';
/**
* Helper: create a ReadableStream that yields the given chunks with optional delays.
* @param {Buffer[]} chunks
* @returns {ReadableStream}
*/
function chunkedStream(chunks) {
let i = 0;
return new ReadableStream({
pull(controller) {
if (i < chunks.length) {
controller.enqueue(new Uint8Array(chunks[i++]));
} else {
controller.close();
}
},
});
}
describe('StreamReader', () => {
describe('fromBuffer', () => {
it('readUInt8 reads single bytes', async () => {
const reader = StreamReader.fromBuffer(Buffer.from([0x01, 0xff, 0x00]));
assert.equal(await reader.readUInt8(), 0x01);
assert.equal(await reader.readUInt8(), 0xff);
assert.equal(await reader.readUInt8(), 0x00);
});
it('readByte reads signed bytes', async () => {
const reader = StreamReader.fromBuffer(Buffer.from([0x7f, 0x80]));
assert.equal(await reader.readByte(), 127);
assert.equal(await reader.readByte(), -128);
});
it('readInt16BE reads signed 16-bit', async () => {
const buf = Buffer.alloc(4);
buf.writeInt16BE(12345, 0);
buf.writeInt16BE(-1, 2);
const reader = StreamReader.fromBuffer(buf);
assert.equal(await reader.readInt16BE(), 12345);
assert.equal(await reader.readInt16BE(), -1);
});
it('readInt32BE reads signed 32-bit', async () => {
const buf = Buffer.alloc(8);
buf.writeInt32BE(2147483647, 0);
buf.writeInt32BE(-2147483648, 4);
const reader = StreamReader.fromBuffer(buf);
assert.equal(await reader.readInt32BE(), 2147483647);
assert.equal(await reader.readInt32BE(), -2147483648);
});
it('readBigInt64BE reads signed 64-bit', async () => {
const buf = Buffer.alloc(8);
buf.writeBigInt64BE(9223372036854775807n, 0);
const reader = StreamReader.fromBuffer(buf);
assert.equal(await reader.readBigInt64BE(), 9223372036854775807n);
});
it('readFloatBE reads 32-bit float', async () => {
const buf = Buffer.alloc(4);
buf.writeFloatBE(3.14, 0);
const reader = StreamReader.fromBuffer(buf);
assert.closeTo(await reader.readFloatBE(), 3.14, 0.001);
});
it('readDoubleBE reads 64-bit double', async () => {
const buf = Buffer.alloc(8);
buf.writeDoubleBE(3.141592653589793, 0);
const reader = StreamReader.fromBuffer(buf);
assert.equal(await reader.readDoubleBE(), 3.141592653589793);
});
it('readBytes returns exact slice', async () => {
const reader = StreamReader.fromBuffer(Buffer.from([0x01, 0x02, 0x03, 0x04]));
const bytes = await reader.readBytes(2);
assert.deepEqual([...bytes], [0x01, 0x02]);
const rest = await reader.readBytes(2);
assert.deepEqual([...rest], [0x03, 0x04]);
});
it('throws on read past end of buffer', async () => {
const reader = StreamReader.fromBuffer(Buffer.from([0x01]));
await reader.readUInt8();
try {
await reader.readUInt8();
assert.fail('should have thrown');
} catch (e) {
assert.match(e.message, /Unexpected end of buffer/);
}
});
it('mixed reads advance offset correctly', async () => {
const buf = Buffer.alloc(13);
buf.writeUInt8(0xAB, 0);
buf.writeInt32BE(42, 1);
buf.writeDoubleBE(1.5, 5);
const reader = StreamReader.fromBuffer(buf);
assert.equal(await reader.readUInt8(), 0xAB);
assert.equal(await reader.readInt32BE(), 42);
assert.equal(await reader.readDoubleBE(), 1.5);
});
});
describe('fromReadableStream', () => {
it('reads from a single chunk', async () => {
const stream = chunkedStream([Buffer.from([0x01, 0x02, 0x03])]);
const reader = StreamReader.fromReadableStream(stream);
assert.equal(await reader.readUInt8(), 0x01);
assert.equal(await reader.readUInt8(), 0x02);
assert.equal(await reader.readUInt8(), 0x03);
});
it('reads across chunk boundaries', async () => {
// Int32 (4 bytes) split across two 2-byte chunks
const buf = Buffer.alloc(4);
buf.writeInt32BE(305419896, 0); // 0x12345678
const stream = chunkedStream([buf.subarray(0, 2), buf.subarray(2, 4)]);
const reader = StreamReader.fromReadableStream(stream);
assert.equal(await reader.readInt32BE(), 305419896);
});
it('reads across many small chunks', async () => {
// 8-byte double split into 1-byte chunks
const buf = Buffer.alloc(8);
buf.writeDoubleBE(2.718281828, 0);
const chunks = [];
for (let i = 0; i < 8; i++) {
chunks.push(buf.subarray(i, i + 1));
}
const stream = chunkedStream(chunks);
const reader = StreamReader.fromReadableStream(stream);
assert.closeTo(await reader.readDoubleBE(), 2.718281828, 1e-9);
});
it('handles readBytes spanning chunks', async () => {
const stream = chunkedStream([Buffer.from([0x01, 0x02]), Buffer.from([0x03, 0x04, 0x05])]);
const reader = StreamReader.fromReadableStream(stream);
const bytes = await reader.readBytes(4);
assert.deepEqual([...bytes], [0x01, 0x02, 0x03, 0x04]);
assert.equal(await reader.readUInt8(), 0x05);
});
it('throws on premature end of stream', async () => {
const stream = chunkedStream([Buffer.from([0x01])]);
const reader = StreamReader.fromReadableStream(stream);
try {
await reader.readInt32BE();
assert.fail('should have thrown');
} catch (e) {
assert.match(e.message, /Unexpected end of stream/);
}
});
it('handles empty stream', async () => {
const stream = chunkedStream([]);
const reader = StreamReader.fromReadableStream(stream);
try {
await reader.readUInt8();
assert.fail('should have thrown');
} catch (e) {
assert.match(e.message, /Unexpected end of stream/);
}
});
it('sequential reads across multiple chunks', async () => {
const buf = Buffer.alloc(9);
buf.writeUInt8(0x84, 0); // version byte
buf.writeInt32BE(200, 1); // status code
buf.writeInt32BE(-1, 5); // another int
// Split: [version + 1 byte of int] [3 bytes of int + 2 bytes] [2 bytes]
const stream = chunkedStream([buf.subarray(0, 2), buf.subarray(2, 7), buf.subarray(7, 9)]);
const reader = StreamReader.fromReadableStream(stream);
assert.equal(await reader.readUInt8(), 0x84);
assert.equal(await reader.readInt32BE(), 200);
assert.equal(await reader.readInt32BE(), -1);
});
it('handles Uint8Array chunks from fetch', async () => {
// fetch response.body yields Uint8Array, not Buffer
const data = new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x2A]);
const stream = new ReadableStream({
start(controller) {
controller.enqueue(data);
controller.close();
},
});
const reader = StreamReader.fromReadableStream(stream);
assert.equal(await reader.readUInt8(), 0x01);
assert.equal(await reader.readInt32BE(), 42);
});
});
});