Skip to content

Commit

Permalink
adding support for bytestream writing
Browse files Browse the repository at this point in the history
  • Loading branch information
jleni committed Jun 12, 2024
1 parent 5bf829e commit 27fc9be
Show file tree
Hide file tree
Showing 7 changed files with 404 additions and 79 deletions.
113 changes: 113 additions & 0 deletions src/byteStream.test.ts
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]))
})
})
246 changes: 246 additions & 0 deletions src/byteStream.ts
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
}
}
3 changes: 2 additions & 1 deletion src/common.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*****************************************************************************/
import { errorCodeToString, processErrorResponse } from './common'
import { processErrorResponse } from './common'
import { LedgerError } from './consts'
import { errorCodeToString } from './errors'
import { ResponseError } from './responseError'

describe('errorCodeToString', () => {
Expand Down
Loading

0 comments on commit 27fc9be

Please sign in to comment.