Skip to content

Commit 9e7ff1f

Browse files
authored
adding support for bytestream writing (#30)
* adding support for bytestream writing * formatting
1 parent 5bf829e commit 9e7ff1f

File tree

9 files changed

+406
-83
lines changed

9 files changed

+406
-83
lines changed

bun.lockb

0 Bytes
Binary file not shown.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
4343
"@types/jest": "29.5.12",
4444
"@types/node": "^20.14.2",
45-
"@typescript-eslint/eslint-plugin": "^7.12.0",
46-
"@typescript-eslint/parser": "^7.12.0",
45+
"@typescript-eslint/eslint-plugin": "^7.13.0",
46+
"@typescript-eslint/parser": "^7.13.0",
4747
"eslint": "^9.4.0",
4848
"eslint-config-prettier": "^9.1.0",
4949
"eslint-config-standard-with-typescript": "^39.0.0",
@@ -52,7 +52,7 @@
5252
"eslint-plugin-promise": "^6.2.0",
5353
"eslint-plugin-tsdoc": "^0.3.0",
5454
"eslint-plugin-unused-imports": "^4.0.0",
55-
"prettier": "^3.3.1",
55+
"prettier": "^3.3.2",
5656
"ts-jest": "^29.1.4",
5757
"ts-node": "^10.9.2",
5858
"typescript": "^5.4.5"

src/byteStream.test.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/******************************************************************************
2+
* (c) 2018 - 2024 Zondax AG
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*****************************************************************************/
16+
import { Buffer } from 'buffer'
17+
18+
import { ByteStream } from './byteStream'
19+
import { LedgerError } from './consts'
20+
import { ResponseError } from './responseError'
21+
22+
describe('ByteStream', () => {
23+
let byteStream: ByteStream
24+
25+
beforeEach(() => {
26+
byteStream = new ByteStream(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]))
27+
})
28+
29+
test('getCompleteBuffer should return a complete buffer', () => {
30+
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]))
31+
})
32+
33+
test('getAvailableBuffer should return the available buffer after some bytes are read', () => {
34+
byteStream.readBytes(3)
35+
expect(byteStream.getAvailableBuffer()).toEqual(Buffer.from([0x04, 0x05]))
36+
})
37+
38+
test('readBytes should return the correct bytes and increase offset', () => {
39+
const readBuffer = byteStream.readBytes(2)
40+
expect(readBuffer).toEqual(Buffer.from([0x01, 0x02]))
41+
expect(byteStream.readBytes(1)).toEqual(Buffer.from([0x03]))
42+
})
43+
44+
test('skipBytes should increase the offset correctly', () => {
45+
byteStream.skipBytes(2)
46+
expect(byteStream.readBytes(1)).toEqual(Buffer.from([0x03]))
47+
})
48+
49+
test('resetOffset should reset the offset to zero', () => {
50+
byteStream.readBytes(3)
51+
byteStream.resetOffset()
52+
expect(byteStream.readBytes(2)).toEqual(Buffer.from([0x01, 0x02]))
53+
})
54+
55+
test('readBytes should throw an error when reading beyond the buffer length', () => {
56+
expect(() => byteStream.readBytes(10)).toThrow(new ResponseError(LedgerError.UnknownError, 'Attempt to read beyond buffer length'))
57+
})
58+
59+
test('skipBytes should throw an error when skipping beyond the buffer length', () => {
60+
expect(() => byteStream.skipBytes(10)).toThrow(new ResponseError(LedgerError.UnknownError, 'Attempt to skip beyond buffer length'))
61+
})
62+
63+
test('appendUint8 should correctly append a byte to the buffer', () => {
64+
byteStream.appendUint8(0x06)
65+
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]))
66+
})
67+
68+
test('appendUint16 should correctly append a two-byte integer to the buffer', () => {
69+
byteStream.appendUint16(0x0708)
70+
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x08, 0x07]))
71+
})
72+
73+
test('appendUint32 should correctly append a four-byte integer to the buffer', () => {
74+
byteStream.appendUint32(0x090a0b0c)
75+
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x0c, 0x0b, 0x0a, 0x09]))
76+
})
77+
78+
test('appendUint64 should correctly append an eight-byte integer to the buffer', () => {
79+
byteStream = new ByteStream()
80+
byteStream.appendUint64(BigInt('0x0102030405060708'))
81+
expect(byteStream.readBytes(8)).toEqual(Buffer.from([8, 7, 6, 5, 4, 3, 2, 1]))
82+
})
83+
84+
test('readBytesAt should return the correct bytes from a given offset', () => {
85+
const readBuffer = byteStream.readBytesAt(2, 1)
86+
expect(readBuffer).toEqual(Buffer.from([0x02, 0x03]))
87+
})
88+
89+
test('readBytesAt should throw an error when reading beyond the buffer length', () => {
90+
expect(() => byteStream.readBytesAt(10, 1)).toThrow(new ResponseError(LedgerError.UnknownError, 'Attempt to read beyond buffer length'))
91+
})
92+
93+
test('insertBytesAt should correctly insert bytes at a given offset', () => {
94+
byteStream.insertBytesAt(Buffer.from([0x06, 0x07]), 2)
95+
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x06, 0x07, 0x03, 0x04, 0x05]))
96+
})
97+
98+
test('insertBytesAt should expand the buffer if necessary', () => {
99+
byteStream.insertBytesAt(Buffer.from([0x08, 0x09]), 10)
100+
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x09]))
101+
})
102+
103+
test('writeBytesAt should correctly write bytes at a given offset and advance the write offset', () => {
104+
byteStream.writeBytesAt(Buffer.from([0x0a, 0x0b]), 1)
105+
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x0a, 0x0b, 0x04, 0x05]))
106+
expect(byteStream.readBytes(5)).toEqual(Buffer.from([0x01, 0x0a, 0x0b, 0x04, 0x05]))
107+
})
108+
109+
test('writeBytesAt should expand the buffer if necessary', () => {
110+
byteStream.writeBytesAt(Buffer.from([0x0c, 0x0d]), 10)
111+
expect(byteStream.getCompleteBuffer()).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0d]))
112+
})
113+
})

src/byteStream.ts

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/******************************************************************************
2+
* (c) 2018 - 2024 Zondax AG
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*****************************************************************************/
16+
import { LedgerError } from './consts'
17+
import { ResponseError } from './responseError'
18+
19+
/**
20+
* Class representing a byte stream for reading and writing data.
21+
*/
22+
export class ByteStream {
23+
private readOffset = 0
24+
private writeOffset = 0
25+
protected internalBuffer: Buffer
26+
27+
constructor(buffer?: Buffer) {
28+
this.internalBuffer = buffer ? Buffer.from(buffer) : Buffer.alloc(0)
29+
this.readOffset = 0
30+
this.writeOffset = this.internalBuffer.length
31+
}
32+
33+
/**
34+
* Writes a single byte (Uint8) to the buffer at the current write offset, then advances the write offset.
35+
* If the write offset is at the buffer's end, the buffer is expanded.
36+
* @param value The byte to write.
37+
*/
38+
appendUint8(value: number) {
39+
const byteBuffer = Buffer.from([value])
40+
this.appendBytes(byteBuffer)
41+
}
42+
43+
/**
44+
* Writes a two-byte unsigned integer (Uint16) to the buffer at the current write offset in little-endian format, then advances the write offset.
45+
* If the write offset is at the buffer's end, the buffer is expanded.
46+
* @param value The two-byte unsigned integer to write.
47+
*/
48+
appendUint16(value: number) {
49+
const byteBuffer = Buffer.alloc(2)
50+
byteBuffer.writeUInt16LE(value, 0)
51+
this.appendBytes(byteBuffer)
52+
}
53+
54+
/**
55+
* Writes a four-byte unsigned integer (Uint32) to the buffer at the current write offset in little-endian format, then advances the write offset.
56+
* If the write offset is at the buffer's end, the buffer is expanded.
57+
* @param value The four-byte unsigned integer to write.
58+
*/
59+
appendUint32(value: number) {
60+
const byteBuffer = Buffer.alloc(4)
61+
byteBuffer.writeUInt32LE(value, 0)
62+
this.appendBytes(byteBuffer)
63+
}
64+
65+
/**
66+
* Writes an eight-byte unsigned integer (Uint64) to the buffer at the current write offset in little-endian format, then advances the write offset.
67+
* If the write offset is at the buffer's end, the buffer is expanded.
68+
* @param value The eight-byte unsigned integer to write.
69+
*/
70+
appendUint64(value: bigint) {
71+
const byteBuffer = Buffer.alloc(8)
72+
byteBuffer.writeBigUInt64LE(value, 0)
73+
this.appendBytes(byteBuffer)
74+
}
75+
76+
/**
77+
* Reads a specified number of bytes from the current read offset, then advances the read offset.
78+
* @param length The number of bytes to read.
79+
* @returns A buffer containing the read bytes.
80+
* @throws Error if attempting to read beyond the buffer length.
81+
*/
82+
readBytes(length: number): Buffer {
83+
if (this.readOffset + length > this.internalBuffer.length) {
84+
throw new ResponseError(LedgerError.UnknownError, 'Attempt to read beyond buffer length')
85+
}
86+
const response = this.internalBuffer.subarray(this.readOffset, this.readOffset + length)
87+
this.readOffset += length
88+
return response
89+
}
90+
91+
/**
92+
* Reads a specified number of bytes from a given offset without changing the current read offset.
93+
* @param length The number of bytes to read.
94+
* @param offset The offset from which to read the bytes.
95+
* @returns A buffer containing the read bytes.
96+
* @throws Error if attempting to read beyond the buffer length.
97+
*/
98+
readBytesAt(length: number, offset: number): Buffer {
99+
if (offset + length > this.internalBuffer.length) {
100+
throw new ResponseError(LedgerError.UnknownError, 'Attempt to read beyond buffer length')
101+
}
102+
return this.internalBuffer.subarray(offset, offset + length)
103+
}
104+
105+
/**
106+
* Writes data to the buffer at the current write offset, then advances the write offset.
107+
* If the data exceeds the buffer length, the buffer is expanded.
108+
* @param data The data to write.
109+
*/
110+
appendBytes(data: Buffer) {
111+
if (this.writeOffset + data.length > this.internalBuffer.length) {
112+
const newBuffer = Buffer.alloc(this.writeOffset + data.length)
113+
this.internalBuffer.copy(newBuffer, 0, 0, this.writeOffset)
114+
this.internalBuffer = newBuffer
115+
}
116+
data.copy(this.internalBuffer, this.writeOffset)
117+
this.writeOffset += data.length
118+
}
119+
120+
/**
121+
* Inserts data into the buffer at the specified offset without changing the current write offset.
122+
* Expands the buffer if necessary.
123+
* @param data The data to insert.
124+
* @param offset The offset at which to insert the data.
125+
*/
126+
insertBytesAt(data: Buffer, offset: number) {
127+
if (offset > this.internalBuffer.length) {
128+
const padding = Buffer.alloc(offset - this.internalBuffer.length, 0)
129+
this.internalBuffer = Buffer.concat([this.internalBuffer, padding, data])
130+
} else {
131+
const before = this.internalBuffer.subarray(0, offset)
132+
const after = this.internalBuffer.subarray(offset)
133+
this.internalBuffer = Buffer.concat([before, data, after])
134+
}
135+
}
136+
137+
/**
138+
* Writes data to the buffer at the specified offset and advances the write offset from that point.
139+
* Expands the buffer if the data exceeds the buffer length.
140+
* @param data The data to write.
141+
* @param offset The offset at which to write the data.
142+
*/
143+
writeBytesAt(data: Buffer, offset: number) {
144+
if (offset + data.length > this.internalBuffer.length) {
145+
const newBuffer = Buffer.alloc(offset + data.length)
146+
this.internalBuffer.copy(newBuffer, 0, 0, offset)
147+
this.internalBuffer = newBuffer
148+
}
149+
data.copy(this.internalBuffer, offset)
150+
this.writeOffset = offset + data.length
151+
}
152+
153+
/**
154+
* Advances the current read offset by a specified number of bytes.
155+
* @param length The number of bytes to skip.
156+
* @throws Error if attempting to skip beyond the buffer length.
157+
*/
158+
skipBytes(length: number) {
159+
if (this.readOffset + length > this.internalBuffer.length) {
160+
throw new ResponseError(LedgerError.UnknownError, 'Attempt to skip beyond buffer length')
161+
}
162+
this.readOffset += length
163+
}
164+
165+
clear() {
166+
this.internalBuffer = Buffer.alloc(0)
167+
this.readOffset = 0
168+
this.writeOffset = 0
169+
}
170+
171+
/**
172+
* Resets the current read and write offsets to zero.
173+
*/
174+
resetOffset() {
175+
this.readOffset = 0
176+
this.writeOffset = 0
177+
}
178+
179+
/**
180+
* Returns a new buffer containing all bytes of the internal buffer.
181+
*/
182+
getCompleteBuffer(): Buffer {
183+
return Buffer.from(this.internalBuffer)
184+
}
185+
186+
/**
187+
* Returns a new buffer containing the bytes from the current read offset to the end of the internal buffer.
188+
*/
189+
getAvailableBuffer(): Buffer {
190+
return Buffer.from(this.internalBuffer.subarray(this.readOffset))
191+
}
192+
193+
/**
194+
* Returns the remaining length of the buffer from the current read offset.
195+
* @returns The remaining length of the buffer.
196+
*/
197+
length(): number {
198+
return this.internalBuffer.length - this.readOffset
199+
}
200+
201+
/**
202+
* Returns the total capacity of the internal buffer, irrespective of the current read or write offset.
203+
* @returns The total length of the internal buffer.
204+
*/
205+
capacity(): number {
206+
return this.internalBuffer.length
207+
}
208+
209+
/**
210+
* Returns the current read offset.
211+
* @returns The current read offset.
212+
*/
213+
getReadOffset(): number {
214+
return this.readOffset
215+
}
216+
217+
/**
218+
* Returns the current write offset.
219+
* @returns The current write offset.
220+
*/
221+
getWriteOffset(): number {
222+
return this.writeOffset
223+
}
224+
225+
/**
226+
* Sets the read offset to a specified value.
227+
* @param offset The new read offset.
228+
*/
229+
setReadOffset(offset: number) {
230+
if (offset < 0 || offset > this.internalBuffer.length) {
231+
throw new ResponseError(LedgerError.UnknownError, 'Invalid read offset')
232+
}
233+
this.readOffset = offset
234+
}
235+
236+
/**
237+
* Sets the write offset to a specified value.
238+
* @param offset The new write offset.
239+
*/
240+
setWriteOffset(offset: number) {
241+
if (offset < 0 || offset > this.internalBuffer.length) {
242+
throw new ResponseError(LedgerError.UnknownError, 'Invalid write offset')
243+
}
244+
this.writeOffset = offset
245+
}
246+
}

src/common.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*****************************************************************************/
16-
import { errorCodeToString, processErrorResponse } from './common'
16+
import { processErrorResponse } from './common'
1717
import { LedgerError } from './consts'
18+
import { errorCodeToString } from './errors'
1819
import { ResponseError } from './responseError'
1920

2021
describe('errorCodeToString', () => {

0 commit comments

Comments
 (0)