-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
singleFileArchive: Add DataInputStream
class to decode primitive types from a byte stream.
#108
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
/* eslint-disable max-classes-per-file */ | ||
|
||
/** | ||
* Byte lengths for primitive types. | ||
*/ | ||
const SHORT_BYTES = 2; | ||
const INT_BYTES = 4; | ||
const LONG_BYTES = 8; | ||
|
||
/** | ||
* EOF error thrown by DataInputStream. | ||
*/ | ||
class DataInputStreamEOFError extends Error { | ||
bufLen: number; | ||
|
||
requiredLen: number; | ||
|
||
/** | ||
* @param bufLen The length of the buffer. | ||
* @param requiredLen The required length of the buffer (i.e. what was needed for | ||
* a successful read). | ||
* @param message | ||
*/ | ||
constructor (bufLen: number, requiredLen: number, message: string = "") { | ||
let formattedMessage = `[bufLen=${bufLen}, requiredLen=${requiredLen}]`; | ||
if ("" !== message) { | ||
formattedMessage += ` ${message}`; | ||
} | ||
super(formattedMessage); | ||
this.name = "DataInputStreamEOFError"; | ||
this.bufLen = bufLen; | ||
this.requiredLen = requiredLen; | ||
} | ||
} | ||
|
||
/** | ||
* Decodes primitive types from a byte stream (similar to Java's DataInputStream class). | ||
*/ | ||
class DataInputStream { | ||
#dataView: DataView; | ||
|
||
#isLittleEndian: boolean; | ||
|
||
#byteIdx: number; | ||
|
||
/** | ||
* @param arrayBuffer Underlying array buffer. | ||
* @param isLittleEndian Byte endianness. | ||
*/ | ||
constructor (arrayBuffer: ArrayBufferLike, isLittleEndian: boolean = false) { | ||
this.#dataView = new DataView(arrayBuffer); | ||
this.#isLittleEndian = isLittleEndian; | ||
this.#byteIdx = 0; | ||
} | ||
|
||
/** | ||
* Seeks to the given index. | ||
* | ||
* @param idx | ||
* @throws {Error} If encounter EOF while seeking. | ||
*/ | ||
seek (idx: number): void { | ||
if (idx > this.#dataView.byteLength) { | ||
this.#byteIdx = this.#dataView.byteLength; | ||
throw new DataInputStreamEOFError(this.#dataView.byteLength, idx); | ||
} | ||
this.#byteIdx = idx; | ||
} | ||
|
||
/** | ||
* Returns the current read offset in the stream. | ||
* | ||
* @return | ||
*/ | ||
getPos (): number { | ||
return this.#byteIdx; | ||
} | ||
|
||
/** | ||
* Reads the specified amount of data. | ||
* | ||
* @param length | ||
* @return | ||
* @throws {Error} If encounter EOF while reading. | ||
*/ | ||
readFully (length: number): Uint8Array { | ||
const requiredLen: number = this.#byteIdx + length; | ||
if (this.#dataView.byteLength < requiredLen) { | ||
this.#byteIdx = this.#dataView.byteLength; | ||
throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen); | ||
Comment on lines
+87
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refactor repetitive EOF checks into a helper method Multiple methods perform similar EOF checks and error handling. Refactoring this code into a private helper method would reduce duplication and enhance maintainability. Consider adding a helper method: private checkEOF(length: number): void {
const requiredLen = this.#byteIdx + length;
if (this.#dataView.byteLength < requiredLen) {
this.#byteIdx = this.#dataView.byteLength;
throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen);
}
} Then update the read methods to use this helper. For example, in - const requiredLen: number = this.#byteIdx + 1;
- if (this.#dataView.byteLength < requiredLen) {
- this.#byteIdx = this.#dataView.byteLength;
- throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen);
- }
+ this.checkEOF(1); Also applies to: 106-110, 123-126, 139-142, 157-160, 175-178, 193-196, 211-214 |
||
} | ||
|
||
const val: Uint8Array = new Uint8Array(this.#dataView.buffer, this.#byteIdx, length); | ||
this.#byteIdx += length; | ||
|
||
return val; | ||
} | ||
|
||
/** | ||
* Reads an unsigned byte. | ||
* | ||
* @return | ||
* @throws {Error} If encounter EOF while reading. | ||
*/ | ||
readUnsignedByte (): number { | ||
const requiredLen: number = this.#byteIdx + 1; | ||
if (this.#dataView.byteLength < requiredLen) { | ||
this.#byteIdx = this.#dataView.byteLength; | ||
throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen); | ||
} | ||
|
||
return this.#dataView.getUint8(this.#byteIdx++); | ||
} | ||
|
||
/** | ||
* Reads a signed byte | ||
* | ||
* @return | ||
* @throws {Error} If encounter EOF while reading. | ||
*/ | ||
readSignedByte (): number { | ||
const requiredLen: number = this.#byteIdx + 1; | ||
if (this.#dataView.byteLength < requiredLen) { | ||
this.#byteIdx = this.#dataView.byteLength; | ||
throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen); | ||
} | ||
|
||
return this.#dataView.getInt8(this.#byteIdx++); | ||
} | ||
|
||
/** | ||
* Reads an unsigned short | ||
* | ||
* @return | ||
* @throws {Error} If encounter EOF while reading. | ||
*/ | ||
readUnsignedShort (): number { | ||
const requiredLen: number = this.#byteIdx + SHORT_BYTES; | ||
if (this.#dataView.byteLength < requiredLen) { | ||
this.#byteIdx = this.#dataView.byteLength; | ||
throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen); | ||
} | ||
const val: number = this.#dataView.getUint16(this.#byteIdx, this.#isLittleEndian); | ||
this.#byteIdx += SHORT_BYTES; | ||
|
||
return val; | ||
} | ||
|
||
/** | ||
* Reads a signed short | ||
* | ||
* @return | ||
* @throws {Error} If encounter EOF while reading. | ||
*/ | ||
readSignedShort (): number { | ||
const requiredLen: number = this.#byteIdx + SHORT_BYTES; | ||
if (this.#dataView.byteLength < requiredLen) { | ||
this.#byteIdx = this.#dataView.byteLength; | ||
throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen); | ||
} | ||
const val: number = this.#dataView.getInt16(this.#byteIdx, this.#isLittleEndian); | ||
this.#byteIdx += SHORT_BYTES; | ||
|
||
return val; | ||
} | ||
|
||
/** | ||
* Reads an int. | ||
* | ||
* @return | ||
* @throws {Error} If encounter EOF while reading. | ||
*/ | ||
readInt (): number { | ||
const requiredLen: number = this.#byteIdx + INT_BYTES; | ||
if (this.#dataView.byteLength < requiredLen) { | ||
this.#byteIdx = this.#dataView.byteLength; | ||
throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen); | ||
} | ||
const val: number = this.#dataView.getInt32(this.#byteIdx, this.#isLittleEndian); | ||
this.#byteIdx += INT_BYTES; | ||
|
||
return val; | ||
} | ||
|
||
/** | ||
* Reads a signed long int (64 bit). | ||
* | ||
* @return | ||
* @throws {Error} If encounter EOF while reading. | ||
*/ | ||
readSignedLong (): bigint { | ||
const requiredLen = this.#byteIdx + LONG_BYTES; | ||
if (this.#dataView.byteLength < requiredLen) { | ||
this.#byteIdx = this.#dataView.byteLength; | ||
throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen); | ||
} | ||
const val: bigint = this.#dataView.getBigInt64(this.#byteIdx, this.#isLittleEndian); | ||
this.#byteIdx += LONG_BYTES; | ||
|
||
return val; | ||
} | ||
|
||
/** | ||
* Reads an unsigned long int (64 bit). | ||
* | ||
* @return | ||
* @throws {Error} If encounter EOF while reading. | ||
*/ | ||
readUnsignedLong (): bigint { | ||
const requiredLen = this.#byteIdx + LONG_BYTES; | ||
if (this.#dataView.byteLength < requiredLen) { | ||
this.#byteIdx = this.#dataView.byteLength; | ||
throw new DataInputStreamEOFError(this.#dataView.byteLength, requiredLen); | ||
} | ||
const val: bigint = this.#dataView.getBigUint64(this.#byteIdx, this.#isLittleEndian); | ||
this.#byteIdx += LONG_BYTES; | ||
|
||
return val; | ||
} | ||
} | ||
|
||
export { | ||
DataInputStream, | ||
DataInputStreamEOFError, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix potential off-by-one error in the 'seek' method
The condition in the
seek
method should use>=
instead of>
to prevent seeking beyond the buffer length. Since indexing starts at zero, an index equal to the buffer length would be out of bounds and should trigger an EOF error.Apply this diff to correct the condition:
📝 Committable suggestion