-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adding support for bytestream writing (#30)
* adding support for bytestream writing * formatting
- Loading branch information
Showing
9 changed files
with
406 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/****************************************************************************** | ||
* (c) 2018 - 2024 Zondax AG | ||
* | ||
* Licensed 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 { Buffer } from 'buffer' | ||
|
||
import { ByteStream } from './byteStream' | ||
import { LedgerError } from './consts' | ||
import { ResponseError } from './responseError' | ||
|
||
describe('ByteStream', () => { | ||
let byteStream: ByteStream | ||
|
||
beforeEach(() => { | ||
byteStream = new ByteStream(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05])) | ||
}) | ||
|
||
test('getCompleteBuffer should return a complete buffer', () => { | ||
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05])) | ||
}) | ||
|
||
test('getAvailableBuffer should return the available buffer after some bytes are read', () => { | ||
byteStream.readBytes(3) | ||
expect(byteStream.getAvailableBuffer()).toEqual(Buffer.from([0x04, 0x05])) | ||
}) | ||
|
||
test('readBytes should return the correct bytes and increase offset', () => { | ||
const readBuffer = byteStream.readBytes(2) | ||
expect(readBuffer).toEqual(Buffer.from([0x01, 0x02])) | ||
expect(byteStream.readBytes(1)).toEqual(Buffer.from([0x03])) | ||
}) | ||
|
||
test('skipBytes should increase the offset correctly', () => { | ||
byteStream.skipBytes(2) | ||
expect(byteStream.readBytes(1)).toEqual(Buffer.from([0x03])) | ||
}) | ||
|
||
test('resetOffset should reset the offset to zero', () => { | ||
byteStream.readBytes(3) | ||
byteStream.resetOffset() | ||
expect(byteStream.readBytes(2)).toEqual(Buffer.from([0x01, 0x02])) | ||
}) | ||
|
||
test('readBytes should throw an error when reading beyond the buffer length', () => { | ||
expect(() => byteStream.readBytes(10)).toThrow(new ResponseError(LedgerError.UnknownError, 'Attempt to read beyond buffer length')) | ||
}) | ||
|
||
test('skipBytes should throw an error when skipping beyond the buffer length', () => { | ||
expect(() => byteStream.skipBytes(10)).toThrow(new ResponseError(LedgerError.UnknownError, 'Attempt to skip beyond buffer length')) | ||
}) | ||
|
||
test('appendUint8 should correctly append a byte to the buffer', () => { | ||
byteStream.appendUint8(0x06) | ||
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06])) | ||
}) | ||
|
||
test('appendUint16 should correctly append a two-byte integer to the buffer', () => { | ||
byteStream.appendUint16(0x0708) | ||
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x08, 0x07])) | ||
}) | ||
|
||
test('appendUint32 should correctly append a four-byte integer to the buffer', () => { | ||
byteStream.appendUint32(0x090a0b0c) | ||
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x0c, 0x0b, 0x0a, 0x09])) | ||
}) | ||
|
||
test('appendUint64 should correctly append an eight-byte integer to the buffer', () => { | ||
byteStream = new ByteStream() | ||
byteStream.appendUint64(BigInt('0x0102030405060708')) | ||
expect(byteStream.readBytes(8)).toEqual(Buffer.from([8, 7, 6, 5, 4, 3, 2, 1])) | ||
}) | ||
|
||
test('readBytesAt should return the correct bytes from a given offset', () => { | ||
const readBuffer = byteStream.readBytesAt(2, 1) | ||
expect(readBuffer).toEqual(Buffer.from([0x02, 0x03])) | ||
}) | ||
|
||
test('readBytesAt should throw an error when reading beyond the buffer length', () => { | ||
expect(() => byteStream.readBytesAt(10, 1)).toThrow(new ResponseError(LedgerError.UnknownError, 'Attempt to read beyond buffer length')) | ||
}) | ||
|
||
test('insertBytesAt should correctly insert bytes at a given offset', () => { | ||
byteStream.insertBytesAt(Buffer.from([0x06, 0x07]), 2) | ||
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x06, 0x07, 0x03, 0x04, 0x05])) | ||
}) | ||
|
||
test('insertBytesAt should expand the buffer if necessary', () => { | ||
byteStream.insertBytesAt(Buffer.from([0x08, 0x09]), 10) | ||
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x09])) | ||
}) | ||
|
||
test('writeBytesAt should correctly write bytes at a given offset and advance the write offset', () => { | ||
byteStream.writeBytesAt(Buffer.from([0x0a, 0x0b]), 1) | ||
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x0a, 0x0b, 0x04, 0x05])) | ||
expect(byteStream.readBytes(5)).toEqual(Buffer.from([0x01, 0x0a, 0x0b, 0x04, 0x05])) | ||
}) | ||
|
||
test('writeBytesAt should expand the buffer if necessary', () => { | ||
byteStream.writeBytesAt(Buffer.from([0x0c, 0x0d]), 10) | ||
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0d])) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
/****************************************************************************** | ||
* (c) 2018 - 2024 Zondax AG | ||
* | ||
* Licensed 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 { LedgerError } from './consts' | ||
import { ResponseError } from './responseError' | ||
|
||
/** | ||
* Class representing a byte stream for reading and writing data. | ||
*/ | ||
export class ByteStream { | ||
private readOffset = 0 | ||
private writeOffset = 0 | ||
protected internalBuffer: Buffer | ||
|
||
constructor(buffer?: Buffer) { | ||
this.internalBuffer = buffer ? Buffer.from(buffer) : Buffer.alloc(0) | ||
this.readOffset = 0 | ||
this.writeOffset = this.internalBuffer.length | ||
} | ||
|
||
/** | ||
* Writes a single byte (Uint8) to the buffer at the current write offset, then advances the write offset. | ||
* If the write offset is at the buffer's end, the buffer is expanded. | ||
* @param value The byte to write. | ||
*/ | ||
appendUint8(value: number) { | ||
const byteBuffer = Buffer.from([value]) | ||
this.appendBytes(byteBuffer) | ||
} | ||
|
||
/** | ||
* Writes a two-byte unsigned integer (Uint16) to the buffer at the current write offset in little-endian format, then advances the write offset. | ||
* If the write offset is at the buffer's end, the buffer is expanded. | ||
* @param value The two-byte unsigned integer to write. | ||
*/ | ||
appendUint16(value: number) { | ||
const byteBuffer = Buffer.alloc(2) | ||
byteBuffer.writeUInt16LE(value, 0) | ||
this.appendBytes(byteBuffer) | ||
} | ||
|
||
/** | ||
* Writes a four-byte unsigned integer (Uint32) to the buffer at the current write offset in little-endian format, then advances the write offset. | ||
* If the write offset is at the buffer's end, the buffer is expanded. | ||
* @param value The four-byte unsigned integer to write. | ||
*/ | ||
appendUint32(value: number) { | ||
const byteBuffer = Buffer.alloc(4) | ||
byteBuffer.writeUInt32LE(value, 0) | ||
this.appendBytes(byteBuffer) | ||
} | ||
|
||
/** | ||
* Writes an eight-byte unsigned integer (Uint64) to the buffer at the current write offset in little-endian format, then advances the write offset. | ||
* If the write offset is at the buffer's end, the buffer is expanded. | ||
* @param value The eight-byte unsigned integer to write. | ||
*/ | ||
appendUint64(value: bigint) { | ||
const byteBuffer = Buffer.alloc(8) | ||
byteBuffer.writeBigUInt64LE(value, 0) | ||
this.appendBytes(byteBuffer) | ||
} | ||
|
||
/** | ||
* Reads a specified number of bytes from the current read offset, then advances the read offset. | ||
* @param length The number of bytes to read. | ||
* @returns A buffer containing the read bytes. | ||
* @throws Error if attempting to read beyond the buffer length. | ||
*/ | ||
readBytes(length: number): Buffer { | ||
if (this.readOffset + length > this.internalBuffer.length) { | ||
throw new ResponseError(LedgerError.UnknownError, 'Attempt to read beyond buffer length') | ||
} | ||
const response = this.internalBuffer.subarray(this.readOffset, this.readOffset + length) | ||
this.readOffset += length | ||
return response | ||
} | ||
|
||
/** | ||
* Reads a specified number of bytes from a given offset without changing the current read offset. | ||
* @param length The number of bytes to read. | ||
* @param offset The offset from which to read the bytes. | ||
* @returns A buffer containing the read bytes. | ||
* @throws Error if attempting to read beyond the buffer length. | ||
*/ | ||
readBytesAt(length: number, offset: number): Buffer { | ||
if (offset + length > this.internalBuffer.length) { | ||
throw new ResponseError(LedgerError.UnknownError, 'Attempt to read beyond buffer length') | ||
} | ||
return this.internalBuffer.subarray(offset, offset + length) | ||
} | ||
|
||
/** | ||
* Writes data to the buffer at the current write offset, then advances the write offset. | ||
* If the data exceeds the buffer length, the buffer is expanded. | ||
* @param data The data to write. | ||
*/ | ||
appendBytes(data: Buffer) { | ||
if (this.writeOffset + data.length > this.internalBuffer.length) { | ||
const newBuffer = Buffer.alloc(this.writeOffset + data.length) | ||
this.internalBuffer.copy(newBuffer, 0, 0, this.writeOffset) | ||
this.internalBuffer = newBuffer | ||
} | ||
data.copy(this.internalBuffer, this.writeOffset) | ||
this.writeOffset += data.length | ||
} | ||
|
||
/** | ||
* Inserts data into the buffer at the specified offset without changing the current write offset. | ||
* Expands the buffer if necessary. | ||
* @param data The data to insert. | ||
* @param offset The offset at which to insert the data. | ||
*/ | ||
insertBytesAt(data: Buffer, offset: number) { | ||
if (offset > this.internalBuffer.length) { | ||
const padding = Buffer.alloc(offset - this.internalBuffer.length, 0) | ||
this.internalBuffer = Buffer.concat([this.internalBuffer, padding, data]) | ||
} else { | ||
const before = this.internalBuffer.subarray(0, offset) | ||
const after = this.internalBuffer.subarray(offset) | ||
this.internalBuffer = Buffer.concat([before, data, after]) | ||
} | ||
} | ||
|
||
/** | ||
* Writes data to the buffer at the specified offset and advances the write offset from that point. | ||
* Expands the buffer if the data exceeds the buffer length. | ||
* @param data The data to write. | ||
* @param offset The offset at which to write the data. | ||
*/ | ||
writeBytesAt(data: Buffer, offset: number) { | ||
if (offset + data.length > this.internalBuffer.length) { | ||
const newBuffer = Buffer.alloc(offset + data.length) | ||
this.internalBuffer.copy(newBuffer, 0, 0, offset) | ||
this.internalBuffer = newBuffer | ||
} | ||
data.copy(this.internalBuffer, offset) | ||
this.writeOffset = offset + data.length | ||
} | ||
|
||
/** | ||
* Advances the current read offset by a specified number of bytes. | ||
* @param length The number of bytes to skip. | ||
* @throws Error if attempting to skip beyond the buffer length. | ||
*/ | ||
skipBytes(length: number) { | ||
if (this.readOffset + length > this.internalBuffer.length) { | ||
throw new ResponseError(LedgerError.UnknownError, 'Attempt to skip beyond buffer length') | ||
} | ||
this.readOffset += length | ||
} | ||
|
||
clear() { | ||
this.internalBuffer = Buffer.alloc(0) | ||
this.readOffset = 0 | ||
this.writeOffset = 0 | ||
} | ||
|
||
/** | ||
* Resets the current read and write offsets to zero. | ||
*/ | ||
resetOffset() { | ||
this.readOffset = 0 | ||
this.writeOffset = 0 | ||
} | ||
|
||
/** | ||
* Returns a new buffer containing all bytes of the internal buffer. | ||
*/ | ||
getCompleteBuffer(): Buffer { | ||
return Buffer.from(this.internalBuffer) | ||
} | ||
|
||
/** | ||
* Returns a new buffer containing the bytes from the current read offset to the end of the internal buffer. | ||
*/ | ||
getAvailableBuffer(): Buffer { | ||
return Buffer.from(this.internalBuffer.subarray(this.readOffset)) | ||
} | ||
|
||
/** | ||
* Returns the remaining length of the buffer from the current read offset. | ||
* @returns The remaining length of the buffer. | ||
*/ | ||
length(): number { | ||
return this.internalBuffer.length - this.readOffset | ||
} | ||
|
||
/** | ||
* Returns the total capacity of the internal buffer, irrespective of the current read or write offset. | ||
* @returns The total length of the internal buffer. | ||
*/ | ||
capacity(): number { | ||
return this.internalBuffer.length | ||
} | ||
|
||
/** | ||
* Returns the current read offset. | ||
* @returns The current read offset. | ||
*/ | ||
getReadOffset(): number { | ||
return this.readOffset | ||
} | ||
|
||
/** | ||
* Returns the current write offset. | ||
* @returns The current write offset. | ||
*/ | ||
getWriteOffset(): number { | ||
return this.writeOffset | ||
} | ||
|
||
/** | ||
* Sets the read offset to a specified value. | ||
* @param offset The new read offset. | ||
*/ | ||
setReadOffset(offset: number) { | ||
if (offset < 0 || offset > this.internalBuffer.length) { | ||
throw new ResponseError(LedgerError.UnknownError, 'Invalid read offset') | ||
} | ||
this.readOffset = offset | ||
} | ||
|
||
/** | ||
* Sets the write offset to a specified value. | ||
* @param offset The new write offset. | ||
*/ | ||
setWriteOffset(offset: number) { | ||
if (offset < 0 || offset > this.internalBuffer.length) { | ||
throw new ResponseError(LedgerError.UnknownError, 'Invalid write offset') | ||
} | ||
this.writeOffset = offset | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.