bufferfy
Version:
Fast and efficient buffer serialization.
1,579 lines (1,558 loc) • 76.6 kB
JavaScript
import { concat, compare } from 'uint8array-tools';
import deepEqual from 'fast-deep-equal';
import { base64url, base64, base58, base32 } from '@scure/base';
// src/utilities/Error.ts
var BufferfyError = class extends Error {
constructor(message, codecName, offset, context) {
super(message);
this.codecName = codecName;
this.offset = offset;
this.context = context;
this.name = "BufferfyError";
}
};
var BufferfyByteLengthError = class extends BufferfyError {
constructor(required, available, offset) {
const message = required !== void 0 && available !== void 0 ? `Buffer too short: need ${required} bytes, have ${available} at offset ${offset ?? 0}` : "Buffer is not of sufficient byteLength to decode.";
super(message, void 0, offset);
this.name = "BufferfyByteLengthError";
}
};
var BufferfyValidationError = class extends BufferfyError {
constructor(codecName, value) {
super(`Value does not match codec ${codecName}`, codecName, void 0, { value });
this.name = "BufferfyValidationError";
}
};
var BufferfyUnionError = class extends BufferfyError {
constructor(value, attemptedCodecs) {
super(
`Value does not match any codec in union: tried [${attemptedCodecs.join(", ")}]`,
"UnionCodec",
void 0,
{ value, attempted: attemptedCodecs }
);
this.name = "BufferfyUnionError";
}
};
var BufferfyRangeError = class extends BufferfyError {
constructor(message, codecName, value, limit, offset) {
super(message, codecName, offset, { value, limit });
this.name = "BufferfyRangeError";
}
};
// src/utilities/Reader.ts
var Reader = class {
constructor(buffer, offset = 0) {
this.buffer = buffer;
this.offset = offset;
this.view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
}
get position() {
return this.offset;
}
get remaining() {
return this.buffer.byteLength - this.offset;
}
get bytes() {
return this.buffer;
}
ensureBytes(count) {
if (this.remaining < count) {
throw new BufferfyByteLengthError();
}
}
readByte() {
this.ensureBytes(1);
return this.buffer[this.offset++];
}
readBytes(count) {
this.ensureBytes(count);
const bytes = this.buffer.subarray(this.offset, this.offset + count);
this.offset += count;
return bytes;
}
skipBytes(count) {
this.ensureBytes(count);
const offset = this.offset;
this.offset += count;
return offset;
}
readUint16(littleEndian) {
this.ensureBytes(2);
const value = this.view.getUint16(this.offset, littleEndian);
this.offset += 2;
return value;
}
readUint24(littleEndian) {
this.ensureBytes(3);
let high;
let low;
if (littleEndian) {
low = this.view.getUint8(this.offset);
high = this.view.getUint16(this.offset + 1, true);
} else {
high = this.view.getUint16(this.offset, false);
low = this.view.getUint8(this.offset + 2);
}
this.offset += 3;
return high << 8 | low;
}
readUint32(littleEndian) {
this.ensureBytes(4);
const value = this.view.getUint32(this.offset, littleEndian);
this.offset += 4;
return value;
}
readUint40(littleEndian) {
this.ensureBytes(5);
let high;
let low;
if (littleEndian) {
low = this.view.getUint32(this.offset, true);
high = this.view.getUint8(this.offset + 4);
} else {
high = this.view.getUint8(this.offset);
low = this.view.getUint32(this.offset + 1, false);
}
this.offset += 5;
return high * 4294967296 + low;
}
readUint48(littleEndian) {
this.ensureBytes(6);
let high;
let low;
if (littleEndian) {
low = this.view.getUint32(this.offset, true);
high = this.view.getUint16(this.offset + 4, true);
} else {
high = this.view.getUint16(this.offset, false);
low = this.view.getUint32(this.offset + 2, false);
}
this.offset += 6;
return high * 4294967296 + low;
}
readFloat32(littleEndian) {
this.ensureBytes(4);
const value = this.view.getFloat32(this.offset, littleEndian);
this.offset += 4;
return value;
}
readFloat64(littleEndian) {
this.ensureBytes(8);
const value = this.view.getFloat64(this.offset, littleEndian);
this.offset += 8;
return value;
}
readBigUint64(littleEndian) {
this.ensureBytes(8);
const value = this.view.getBigUint64(this.offset, littleEndian);
this.offset += 8;
return value;
}
peekBytes(start, end) {
return this.buffer.subarray(start, end);
}
};
// src/utilities/Writer.ts
var Writer = class {
constructor(buffer, initialSize = 1024) {
this.buffer = buffer || new Uint8Array(initialSize);
this.view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength);
this.offset = 0;
}
get position() {
return this.offset;
}
// Growth swaps the underlying array; a caller holding this reference across a write that grows the buffer must re-read the getter.
get bytes() {
return this.buffer;
}
ensureCapacity(additionalBytes) {
const required = this.offset + additionalBytes;
if (required <= this.buffer.byteLength)
return;
let newSize = this.buffer.byteLength * 2;
while (newSize < required)
newSize *= 2;
const newBuffer = new Uint8Array(newSize);
newBuffer.set(this.buffer);
this.buffer = newBuffer;
this.view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength);
}
toBuffer() {
return this.buffer.subarray(0, this.offset);
}
reset() {
this.offset = 0;
}
writeByte(value) {
this.ensureCapacity(1);
this.buffer[this.offset++] = value;
}
writeBytes(bytes) {
this.ensureCapacity(bytes.byteLength);
this.buffer.set(bytes, this.offset);
this.offset += bytes.byteLength;
}
reserve(byteLength) {
this.ensureCapacity(byteLength);
const offset = this.offset;
this.offset += byteLength;
return offset;
}
writeUint16(value, littleEndian) {
this.ensureCapacity(2);
this.view.setUint16(this.offset, value, littleEndian);
this.offset += 2;
}
writeUint24(value, littleEndian) {
this.ensureCapacity(3);
const high = value >>> 8;
const low = value & 255;
if (littleEndian) {
this.view.setUint8(this.offset, low);
this.view.setUint16(this.offset + 1, high, true);
} else {
this.view.setUint16(this.offset, high, false);
this.view.setUint8(this.offset + 2, low);
}
this.offset += 3;
}
writeUint32(value, littleEndian) {
this.ensureCapacity(4);
this.view.setUint32(this.offset, value, littleEndian);
this.offset += 4;
}
writeUint40(value, littleEndian) {
this.ensureCapacity(5);
const high = Math.floor(value / 4294967296);
const low = value % 4294967296;
if (littleEndian) {
this.view.setUint32(this.offset, low, true);
this.view.setUint8(this.offset + 4, high);
} else {
this.view.setUint8(this.offset, high);
this.view.setUint32(this.offset + 1, low, false);
}
this.offset += 5;
}
writeUint48(value, littleEndian) {
this.ensureCapacity(6);
const high = Math.floor(value / 4294967296);
const low = value % 4294967296;
if (littleEndian) {
this.view.setUint32(this.offset, low, true);
this.view.setUint16(this.offset + 4, high, true);
} else {
this.view.setUint16(this.offset, high, false);
this.view.setUint32(this.offset + 2, low, false);
}
this.offset += 6;
}
writeFloat32(value, littleEndian) {
this.ensureCapacity(4);
this.view.setFloat32(this.offset, value, littleEndian);
this.offset += 4;
}
writeFloat64(value, littleEndian) {
this.ensureCapacity(8);
this.view.setFloat64(this.offset, value, littleEndian);
this.offset += 8;
}
writeBigUint64(value, littleEndian) {
this.ensureCapacity(8);
this.view.setBigUint64(this.offset, value, littleEndian);
this.offset += 8;
}
};
var WARN_BUFFER_SIZE = 10 * 1024 * 1024;
var hasWarned = false;
var DecodeTransformStream = class extends TransformStream {
constructor(codec) {
super({
transform: async (chunk, controller) => {
this._valueBytes = concat([this._valueBytes, chunk]);
if (!hasWarned && this._valueBytes.byteLength > WARN_BUFFER_SIZE) {
console.warn(
`DecodeTransformStream buffer exceeded ${WARN_BUFFER_SIZE} bytes (${this._valueBytes.byteLength} bytes accumulated). This may indicate a DOS attack or incorrect codec usage. Consider validating input size upstream.`
);
hasWarned = true;
}
try {
while (this._valueBytes.byteLength) {
const value = codec.decode(this._valueBytes);
controller.enqueue(value);
const byteLength = codec.byteLength(value);
if (byteLength === 0) {
controller.error(new BufferfyError("Codec returned zero byteLength, cannot make progress"));
return;
}
this._valueBytes = this._valueBytes.subarray(byteLength, this._valueBytes.byteLength);
}
} catch (error) {
if (error instanceof BufferfyByteLengthError)
return;
controller.error(error);
}
}
});
this._valueBytes = Uint8Array.from([]);
}
};
// src/Codecs/Abstract/EncodeTransform.ts
var EncodeTransformStream = class extends TransformStream {
constructor(codec) {
super({
async transform(value, controller) {
try {
const chunk = codec.encode(value);
controller.enqueue(chunk);
} catch (error) {
controller.error(error);
}
}
});
}
};
// src/Codecs/Abstract/index.ts
var sharedWriter = new Writer();
var sharedWriterInUse = false;
var AbstractCodec = class {
/**
* Encodes a value of this codecs type into a buffer.
*
* **Note:** This method does NOT validate the value before encoding.
* Call `isValid()` first if you need to verify the value is encodable.
* Encoding invalid values may result in undefined behavior or runtime errors.
*
* @param {Value} value - Value of this codec's type.
* @param {Uint8Array} [target] - A target buffer to write into (uses byteLength for sizing).
* @param {number} [offset=0] - Offset at which to write into the target.
* @return {Uint8Array} Buffer encoding of value.
*
*/
encode(value, target, offset = 0) {
if (target) {
const buffer = offset ? new Uint8Array(target.buffer, target.byteOffset + offset) : target;
const writer = new Writer(buffer);
this._encode(value, writer);
return writer.toBuffer();
}
if (sharedWriterInUse) {
const writer = new Writer();
this._encode(value, writer);
return writer.toBuffer();
}
sharedWriterInUse = true;
try {
this._encode(value, sharedWriter);
return sharedWriter.toBuffer().slice();
} finally {
sharedWriter.reset();
sharedWriterInUse = false;
}
}
Encoder() {
return new EncodeTransformStream(this);
}
/**
* Decodes a buffer to a value of this codecs type.
*
* @param {Uint8Array} buffer - The buffer to be decoded.
* @param {number} [offset=0] - Offset at which to read at.
* @return {Value} Value decoded from the buffer
*
*/
decode(source, offset = 0) {
const reader = new Reader(source, offset);
return this._decode(reader);
}
Decoder() {
return new DecodeTransformStream(this);
}
};
// src/Codecs/UInt/index.ts
var buildNumberValidators = (codecName, options) => {
const minimum = options?.minimum;
const maximum = options?.maximum;
const mode = options?.validationMode ?? "both";
const hasRange = minimum !== void 0 || maximum !== void 0;
return {
validateEncode: hasRange && (mode === "both" || mode === "encode") ? (value) => {
if (minimum !== void 0 && value < minimum)
throw new BufferfyRangeError(`Encoded value ${value} is less than minimum ${minimum}`, codecName, value, minimum);
if (maximum !== void 0 && value > maximum)
throw new BufferfyRangeError(`Encoded value ${value} exceeds maximum ${maximum}`, codecName, value, maximum);
} : null,
validateDecode: hasRange && (mode === "both" || mode === "decode") ? (value, position) => {
if (minimum !== void 0 && value < minimum)
throw new BufferfyRangeError(`Decoded value ${value} is less than minimum ${minimum}`, codecName, value, minimum, position);
if (maximum !== void 0 && value > maximum)
throw new BufferfyRangeError(`Decoded value ${value} exceeds maximum ${maximum}`, codecName, value, maximum, position);
} : null
};
};
var createUIntCodec = (bits = 48, endianness = "BE", options) => {
switch (bits) {
case 8:
return new UInt8Codec(options);
case 16:
return new UInt16Codec(endianness, options);
case 24:
return new UInt24Codec(endianness, options);
case 32:
return new UInt32Codec(endianness, options);
case 40:
return new UInt40Codec(endianness, options);
case 48:
return new UInt48Codec(endianness, options);
}
};
var _UInt8Codec = class _UInt8Codec extends AbstractCodec {
constructor(options) {
super();
this.options = options;
const validators = buildNumberValidators("UInt8Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value >= 256)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _UInt8Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeByte(value);
}
_decode(reader) {
const value = reader.readByte();
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_UInt8Codec.BYTE_LENGTH = 1;
var UInt8Codec = _UInt8Codec;
var _UInt16Codec = class _UInt16Codec extends AbstractCodec {
constructor(endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("UInt16Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value >= 65536)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _UInt16Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint16(value, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint16(this._littleEndian);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_UInt16Codec.BYTE_LENGTH = 2;
var UInt16Codec = _UInt16Codec;
var _UInt24Codec = class _UInt24Codec extends AbstractCodec {
constructor(endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("UInt24Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value >= 16777216)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _UInt24Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint24(value, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint24(this._littleEndian);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_UInt24Codec.BYTE_LENGTH = 3;
var UInt24Codec = _UInt24Codec;
var _UInt32Codec = class _UInt32Codec extends AbstractCodec {
constructor(endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("UInt32Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value >= 4294967296)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _UInt32Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint32(value, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint32(this._littleEndian);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_UInt32Codec.BYTE_LENGTH = 4;
var UInt32Codec = _UInt32Codec;
var _UInt40Codec = class _UInt40Codec extends AbstractCodec {
constructor(endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("UInt40Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value >= 1099511627776)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _UInt40Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint40(value, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint40(this._littleEndian);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_UInt40Codec.BYTE_LENGTH = 5;
var UInt40Codec = _UInt40Codec;
var _UInt48Codec = class _UInt48Codec extends AbstractCodec {
constructor(endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("UInt48Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value >= 281474976710656)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _UInt48Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint48(value, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint48(this._littleEndian);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_UInt48Codec.BYTE_LENGTH = 6;
var UInt48Codec = _UInt48Codec;
// src/Codecs/VarInt/VarInt60.ts
var MAX_SAFE_VARINT60 = 281474976710656;
var VARINT60_THRESHOLDS = [32, 8192, 2097152, 536870912, 137438953472, 35184372088832];
var POW256 = [1, 256, 65536, 16777216, 4294967296, 1099511627776, 281474976710656];
var VarInt60Codec = class extends AbstractCodec {
constructor(options) {
super();
this.options = options;
const validators = buildNumberValidators("VarInt60Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value >= MAX_SAFE_VARINT60 || value > Number.MAX_SAFE_INTEGER)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength(value) {
for (let i = 0; i < VARINT60_THRESHOLDS.length; i++) {
if (value < VARINT60_THRESHOLDS[i])
return i + 1;
}
return 7;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
let byteLength = 7;
for (let i = 0; i < VARINT60_THRESHOLDS.length; i++) {
if (value < VARINT60_THRESHOLDS[i]) {
byteLength = i + 1;
break;
}
}
const offset = writer.reserve(byteLength);
const bytes = writer.bytes;
if (byteLength === 1) {
bytes[offset] = value;
return;
}
const remainingBytes = byteLength - 1;
bytes[offset] = remainingBytes << 5 | Math.floor(value / POW256[remainingBytes]) & 31;
let position = offset + 1;
for (let i = remainingBytes - 1; i >= 0; i--) {
bytes[position++] = Math.floor(value / POW256[i]) & 255;
}
}
_decode(reader) {
const firstByte = reader.readByte();
const remainingBytes = (firstByte & 224) / 32;
let value;
if (remainingBytes === 0) {
value = firstByte;
} else {
value = (firstByte & 31) * POW256[remainingBytes];
for (let i = remainingBytes - 1; i >= 0; i--) {
value += reader.readByte() * POW256[i];
}
}
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
// src/Codecs/Bytes/Variable.ts
var BytesVariableCodec = class extends AbstractCodec {
constructor(lengthCodec = new VarInt60Codec()) {
super();
this.lengthCodec = lengthCodec;
}
isValid(value) {
return value instanceof Uint8Array;
}
byteLength(value) {
return this.lengthCodec.byteLength(value.byteLength) + value.byteLength;
}
_encode(value, writer) {
this.lengthCodec._encode(value.byteLength, writer);
writer.writeBytes(value);
}
_decode(reader) {
const byteLength = this.lengthCodec._decode(reader);
return reader.readBytes(byteLength);
}
};
// src/Codecs/Any/index.ts
var createAnyCodec = (options) => new AnyCodec(options);
var AnyCodec = class extends AbstractCodec {
constructor(options) {
super();
this._encodeValue = options?.encode || ((value) => new TextEncoder().encode(JSON.stringify(value)));
this._decodeValue = options?.decode || ((buffer) => JSON.parse(new TextDecoder().decode(buffer)));
this.lengthCodec = options?.lengthCodec || new VarInt60Codec();
this._bytesCodec = new BytesVariableCodec(this.lengthCodec);
}
isValid(_value) {
return true;
}
byteLength(value) {
const byteLength = this._encodeValue(value).byteLength;
return this.lengthCodec.byteLength(byteLength) + byteLength;
}
_encode(value, writer) {
const valueBuffer = this._encodeValue(value);
return this._bytesCodec._encode(valueBuffer, writer);
}
_decode(reader) {
const valueBuffer = this._bytesCodec._decode(reader);
return this._decodeValue(valueBuffer);
}
};
// src/Codecs/Array/Fixed.ts
var ArrayFixedCodec = class extends AbstractCodec {
constructor(length, itemCodec) {
super();
this.length = length;
this.itemCodec = itemCodec;
}
isValid(value) {
if (!Array.isArray(value) || value.length !== this.length)
return false;
for (let i = 0; i < value.length; i++)
if (!this.itemCodec.isValid(value[i]))
return false;
return true;
}
byteLength(value) {
let byteLength = 0;
for (let i = 0; i < value.length; i++)
byteLength += this.itemCodec.byteLength(value[i]);
return byteLength;
}
_encode(value, writer) {
for (let i = 0; i < value.length; i++)
this.itemCodec._encode(value[i], writer);
}
_decode(reader) {
const value = Array(this.length);
for (let i = 0; i < this.length; i++)
value[i] = this.itemCodec._decode(reader);
return value;
}
};
// src/Codecs/Array/Variable.ts
var ArrayVariableCodec = class extends AbstractCodec {
constructor(itemCodec, lengthCodec = new VarInt60Codec()) {
super();
this.itemCodec = itemCodec;
this.lengthCodec = lengthCodec;
}
isValid(value) {
if (!Array.isArray(value))
return false;
for (let i = 0; i < value.length; i++)
if (!this.itemCodec.isValid(value[i]))
return false;
return true;
}
byteLength(value) {
let byteLength = this.lengthCodec.byteLength(value.length);
for (let i = 0; i < value.length; i++)
byteLength += this.itemCodec.byteLength(value[i]);
return byteLength;
}
_encode(value, writer) {
this.lengthCodec._encode(value.length, writer);
for (let i = 0; i < value.length; i++)
this.itemCodec._encode(value[i], writer);
}
_decode(reader) {
const length = this.lengthCodec._decode(reader);
const value = Array(length);
for (let i = 0; i < length; i++)
value[i] = this.itemCodec._decode(reader);
return value;
}
};
// src/Codecs/BigUInt/index.ts
var buildBigUIntValidators = (codecName, options) => {
const minimum = options?.minimum;
const maximum = options?.maximum;
const mode = options?.validationMode ?? "both";
const hasRange = minimum !== void 0 || maximum !== void 0;
return {
validateEncode: hasRange && (mode === "both" || mode === "encode") ? (value) => {
if (minimum !== void 0 && value < minimum)
throw new BufferfyRangeError(`Encoded value ${value} is less than minimum ${minimum}`, codecName, value, void 0);
if (maximum !== void 0 && value > maximum)
throw new BufferfyRangeError(`Encoded value ${value} exceeds maximum ${maximum}`, codecName, value, void 0);
} : null,
validateDecode: hasRange && (mode === "both" || mode === "decode") ? (value, position) => {
if (minimum !== void 0 && value < minimum)
throw new BufferfyRangeError(`Decoded value ${value} is less than minimum ${minimum}`, codecName, value, void 0, position);
if (maximum !== void 0 && value > maximum)
throw new BufferfyRangeError(`Decoded value ${value} exceeds maximum ${maximum}`, codecName, value, void 0, position);
} : null
};
};
var createBigUIntCodec = (endianness = "BE", options) => {
switch (endianness) {
case "BE": {
return new BigUIntBECodec(options);
}
case "LE": {
return new BigUIntLECodec(options);
}
}
};
var _BigUIntBECodec = class _BigUIntBECodec extends AbstractCodec {
constructor(options) {
super();
this.options = options;
const validators = buildBigUIntValidators(this.constructor.name, options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "bigint")
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _BigUIntBECodec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeBigUint64(value, false);
}
_decode(reader) {
const value = reader.readBigUint64(false);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_BigUIntBECodec.BYTE_LENGTH = 8;
var BigUIntBECodec = _BigUIntBECodec;
var BigUIntLECodec = class extends BigUIntBECodec {
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeBigUint64(value, true);
}
_decode(reader) {
const value = reader.readBigUint64(true);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
// src/Codecs/BitField/index.ts
var BIT_MAP = [128, 64, 32, 16, 8, 4, 2, 1];
var createBitFieldCodec = (keys, byteLength) => new BitFieldCodec(keys, byteLength);
var BitFieldCodec = class extends AbstractCodec {
constructor(keys, _byteLength = Math.ceil(keys.length / 8)) {
super();
this.keys = keys;
this._byteLength = _byteLength;
}
isValid(value) {
if (!value || typeof value !== "object")
return false;
for (const key of this.keys)
if (typeof value[key] !== "boolean")
return false;
return true;
}
byteLength() {
return this._byteLength;
}
_encode(value, writer) {
for (let i = 0; i < this._byteLength; i++) {
let byte = 0;
const offset = i * 8;
const bits = Math.min(this.keys.length - offset, 8);
for (let j = 0; j < bits; j++) {
if (value[this.keys[offset + j]] === true)
byte |= BIT_MAP[j];
}
writer.writeByte(byte);
}
}
_decode(reader) {
const value = {};
for (let i = 0; i < this._byteLength; i++) {
const byte = reader.readByte();
const offset = i * 8;
const bits = Math.min(this.keys.length - offset, 8);
for (let j = 0; j < bits; j++) {
value[this.keys[offset + j]] = (byte & BIT_MAP[j]) > 0;
}
}
return value;
}
};
// src/Codecs/Boolean/index.ts
var createBooleanCodec = () => new BooleanCodec();
var BooleanCodec = class extends AbstractCodec {
isValid(value) {
return typeof value === "boolean";
}
byteLength() {
return 1;
}
_encode(value, writer) {
writer.writeByte(value ? 1 : 0);
}
_decode(reader) {
return reader.readByte() === 1;
}
};
// src/Codecs/Bytes/Fixed.ts
var BytesFixedCodec = class extends AbstractCodec {
constructor(byteLength) {
super();
this._byteLength = byteLength;
}
isValid(value) {
return value instanceof Uint8Array && value.byteLength === this._byteLength;
}
byteLength() {
return this._byteLength;
}
_encode(value, writer) {
writer.writeBytes(value);
}
_decode(reader) {
return reader.readBytes(this._byteLength);
}
};
// src/Codecs/Bytes/Constant.ts
var constantTimeCompare = (a, b) => {
if (a.byteLength !== b.byteLength)
return false;
let result = 0;
for (let i = 0; i < a.byteLength; i++) {
result |= a[i] ^ b[i];
}
return result === 0;
};
var BytesConstantCodec = class extends BytesFixedCodec {
constructor(bytes, constantTime = false) {
super(bytes.byteLength);
this.bytes = bytes;
this.constantTime = constantTime;
}
isValid(value) {
if (!(value instanceof Uint8Array))
return false;
if (this.constantTime) {
return constantTimeCompare(value, this.bytes);
}
return compare(value, this.bytes) === 0;
}
_encode(_, writer) {
writer.writeBytes(this.bytes);
}
_decode(reader) {
reader.readBytes(this._byteLength);
return this.bytes;
}
};
var createConstantCodec = (value) => {
if (typeof value === "object" && value !== null)
return new DeepConstantCodec(value);
return new ConstantCodec(value);
};
var ConstantCodec = class extends AbstractCodec {
constructor(value) {
super();
this.value = value;
}
isValid(value) {
return value === this.value;
}
byteLength() {
return 0;
}
_encode(_value, _writer) {
}
_decode(_reader) {
return this.value;
}
};
var DeepConstantCodec = class extends ConstantCodec {
constructor(value) {
super(value);
this.value = value;
}
isValid(value) {
return deepEqual(value, this.value);
}
};
// src/Codecs/Float/index.ts
var createFloatCodec = (bits = 64, endianness = "BE", options) => {
switch (endianness) {
case "BE": {
switch (bits) {
case 32: {
return new Float32BECodec(options);
}
case 64: {
return new Float64BECodec(options);
}
}
}
case "LE": {
switch (bits) {
case 32: {
return new Float32LECodec(options);
}
case 64: {
return new Float64LECodec(options);
}
}
}
}
};
var _Float32BECodec = class _Float32BECodec extends AbstractCodec {
constructor(options) {
super();
this.options = options;
const validators = buildNumberValidators(this.constructor.name, options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number")
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _Float32BECodec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeFloat32(value, false);
}
_decode(reader) {
const value = reader.readFloat32(false);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_Float32BECodec.BYTE_LENGTH = 4;
var Float32BECodec = _Float32BECodec;
var Float32LECodec = class extends Float32BECodec {
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeFloat32(value, true);
}
_decode(reader) {
const value = reader.readFloat32(true);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
var _Float64BECodec = class _Float64BECodec extends AbstractCodec {
constructor(options) {
super();
this.options = options;
const validators = buildNumberValidators(this.constructor.name, options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number")
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _Float64BECodec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeFloat64(value, false);
}
_decode(reader) {
const value = reader.readFloat64(false);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_Float64BECodec.BYTE_LENGTH = 8;
var Float64BECodec = _Float64BECodec;
var Float64LECodec = class extends Float64BECodec {
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeFloat64(value, true);
}
_decode(reader) {
const value = reader.readFloat64(true);
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
// src/Codecs/Int/index.ts
var createIntCodec = (bits = 48, endianness = "BE", options) => {
switch (bits) {
case 8:
return new Int8Codec(options);
case 16:
return new Int16Codec(bits, endianness, options);
case 24:
return new Int24Codec(bits, endianness, options);
case 32:
return new Int32Codec(bits, endianness, options);
case 40:
return new Int40Codec(bits, endianness, options);
case 48:
return new Int48Codec(bits, endianness, options);
}
};
var _Int8Codec = class _Int8Codec extends AbstractCodec {
constructor(options) {
super();
this.options = options;
const validators = buildNumberValidators("Int8Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < _Int8Codec.MIN_VALUE || value > _Int8Codec.MAX_VALUE)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _Int8Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeByte(value + _Int8Codec.OFFSET);
}
_decode(reader) {
const value = reader.readByte() - _Int8Codec.OFFSET;
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_Int8Codec.BYTE_LENGTH = 1;
_Int8Codec.OFFSET = 128;
_Int8Codec.MIN_VALUE = -128;
_Int8Codec.MAX_VALUE = 127;
var Int8Codec = _Int8Codec;
var _Int16Codec = class _Int16Codec extends AbstractCodec {
constructor(_bits, endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("Int16Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < _Int16Codec.MIN_VALUE || value > _Int16Codec.MAX_VALUE)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _Int16Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint16(value + _Int16Codec.OFFSET, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint16(this._littleEndian) - _Int16Codec.OFFSET;
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_Int16Codec.BYTE_LENGTH = 2;
_Int16Codec.OFFSET = 32768;
_Int16Codec.MIN_VALUE = -32768;
_Int16Codec.MAX_VALUE = 32767;
var Int16Codec = _Int16Codec;
var _Int24Codec = class _Int24Codec extends AbstractCodec {
constructor(_bits, endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("Int24Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < _Int24Codec.MIN_VALUE || value > _Int24Codec.MAX_VALUE)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _Int24Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint24(value + _Int24Codec.OFFSET, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint24(this._littleEndian) - _Int24Codec.OFFSET;
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_Int24Codec.BYTE_LENGTH = 3;
_Int24Codec.OFFSET = 8388608;
_Int24Codec.MIN_VALUE = -8388608;
_Int24Codec.MAX_VALUE = 8388607;
var Int24Codec = _Int24Codec;
var _Int32Codec = class _Int32Codec extends AbstractCodec {
constructor(_bits, endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("Int32Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < _Int32Codec.MIN_VALUE || value > _Int32Codec.MAX_VALUE)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _Int32Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint32(value + _Int32Codec.OFFSET, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint32(this._littleEndian) - _Int32Codec.OFFSET;
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_Int32Codec.BYTE_LENGTH = 4;
_Int32Codec.OFFSET = 2147483648;
_Int32Codec.MIN_VALUE = -2147483648;
_Int32Codec.MAX_VALUE = 2147483647;
var Int32Codec = _Int32Codec;
var _Int40Codec = class _Int40Codec extends AbstractCodec {
constructor(_bits, endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("Int40Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < _Int40Codec.MIN_VALUE || value > _Int40Codec.MAX_VALUE)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _Int40Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint40(value + _Int40Codec.OFFSET, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint40(this._littleEndian) - _Int40Codec.OFFSET;
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_Int40Codec.BYTE_LENGTH = 5;
_Int40Codec.OFFSET = 549755813888;
_Int40Codec.MIN_VALUE = -549755813888;
_Int40Codec.MAX_VALUE = 549755813887;
var Int40Codec = _Int40Codec;
var _Int48Codec = class _Int48Codec extends AbstractCodec {
constructor(_bits, endianness = "BE", options) {
super();
this.options = options;
this._littleEndian = endianness === "LE";
const validators = buildNumberValidators("Int48Codec", options);
this._validateEncode = validators.validateEncode;
this._validateDecode = validators.validateDecode;
}
isValid(value) {
if (typeof value !== "number" || !Number.isInteger(value) || value < _Int48Codec.MIN_VALUE || value > _Int48Codec.MAX_VALUE)
return false;
if (this.options?.minimum !== void 0 && value < this.options.minimum)
return false;
if (this.options?.maximum !== void 0 && value > this.options.maximum)
return false;
return true;
}
byteLength() {
return _Int48Codec.BYTE_LENGTH;
}
_encode(value, writer) {
if (this._validateEncode !== null)
this._validateEncode(value);
writer.writeUint48(value + _Int48Codec.OFFSET, this._littleEndian);
}
_decode(reader) {
const value = reader.readUint48(this._littleEndian) - _Int48Codec.OFFSET;
if (this._validateDecode !== null)
this._validateDecode(value, reader.position);
return value;
}
};
_Int48Codec.BYTE_LENGTH = 6;
_Int48Codec.OFFSET = 140737488355328;
_Int48Codec.MIN_VALUE = -140737488355328;
_Int48Codec.MAX_VALUE = 140737488355327;
var Int48Codec = _Int48Codec;
// src/utilities/hex.ts
var BYTE_TO_HEX = [];
for (let byte = 0; byte < 256; byte++) {
BYTE_TO_HEX.push(byte.toString(16).padStart(2, "0"));
}
var CHAR_TO_NIBBLE = new Int8Array(128).fill(-1);
for (let digit = 0; digit < 10; digit++) {
CHAR_TO_NIBBLE[48 + digit] = digit;
}
for (let letter = 0; letter < 6; letter++) {
CHAR_TO_NIBBLE[97 + letter] = 10 + letter;
CHAR_TO_NIBBLE[65 + letter] = 10 + letter;
}
function encodeHex(bytes) {
let result = "";
for (let index = 0; index < bytes.length; index++) {
result += BYTE_TO_HEX[bytes[index]];
}
return result;
}
function decodeHex(value) {
if (value.length % 2 !== 0)
throw new Error(`hex string expected, got unpadded hex of length ${value.length}`);
const bytes = new Uint8Array(value.length / 2);
for (let index = 0; index < bytes.length; index++) {
const high = value.charCodeAt(index * 2);
const low = value.charCodeAt(index * 2 + 1);
const highNibble = high < 128 ? CHAR_TO_NIBBLE[high] : -1;
const lowNibble = low < 128 ? CHAR_TO_NIBBLE[low] : -1;
if (highNibble === -1 || lowNibble === -1)
throw new Error(`hex string expected, got non-hex character at index ${highNibble === -1 ? index * 2 : index * 2 + 1}`);
bytes[index] = highNibble << 4 | lowNibble;
}
return bytes;
}
function hexByteLength(value) {
return value.length / 2;
}
// src/utilities/utf8.ts
var textEncoder = new TextEncoder();
var textDecoder = new TextDecoder();
var SHORT_STRING_THRESHOLD = 48;
var DECODE_THRESHOLD = 48;
function utf8ByteLength(value) {
const length = value.length;
let byteLength = 0;
for (let index = 0; index < length; index++) {
const code = value.charCodeAt(index);
if (code < 128) {
byteLength += 1;
} else if (code < 2048) {
byteLength += 2;
} else if (code >= 55296 && code <= 56319) {
const next = value.charCodeAt(index + 1);
if (next >= 56320 && next <= 57343) {
byteLength += 4;
index++;
} else {
byteLength += 3;
}
} else if (code >= 56320 && code <= 57343) {
byteLength += 3;
} else {
byteLength += 3;
}
}
return byteLength;
}
function encodeUtf8Into(value, buffer, offset) {
const length = value.length;
if (length > SHORT_STRING_THRESHOLD) {
return textEncoder.encodeInto(value, buffer.subarray(offset)).written;
}
let position = offset;
for (let index = 0; index < length; index++) {
let code = value.charCodeAt(index);
if (code < 128) {
buffer[position++] = code;
} else if (code < 2048) {
buffer[position++] = 192 | code >> 6;
buffer[position++] = 128 | code & 63;
} else if (code >= 55296 && code <= 56319) {
const next = value.charCodeAt(index + 1);
if (next >= 56320 && next <= 57343) {
code = 65536 + (code - 55296 << 10) + (next - 56320);
index++;
buffer[position++] = 240 | code >> 18;
buffer[position++] = 128 | code >> 12 & 63;
buffer[position++] = 128 | code >> 6 & 63;
buffer[position++] = 128 | code & 63;
} else {
buffer[position++] = 239;
buffer[position++] = 191;
buffer[position++] = 189;
}
} else if (code >= 56320 && code <= 57343) {
buffer[position++] = 239;
buffer[position++] = 191;
buffer[position++] = 189;
} else {
buffer[position++] = 224 | code >> 12;
buffer[position++] = 128 | code >> 6 & 63;
buffer[position++] = 128 | code & 63;
}
}
return position - offset;
}
function decodeUtf8(buffer, start, end) {
if (end - start > DECODE_THRESHOLD) {
return textDecoder.decode(buffer.subarray(start, end));
}
for (let index = start; index < end; index++) {
if (buffer[index] >= 128) {
return textDecoder.decode(buffer.subarray(start, end));
}
}
let result = "";
for (let index = start; index < end; index++) {
result += String.fromCharCode(buffer[index]);
}
return result;
}
// src/Codecs/String/Fixed.ts
var textEncoder2 = new TextEncoder();
var StringFixedCodec = class extends AbstractCodec {
constructor(byteLength, encoding = "utf8") {
super();
this.encoding = encoding;
this._byteLength = byteLength;
this._bufferCodec = new BytesFixedCodec(byteLength);
if (encoding === "utf8") {
this._encoder = (value, writer) => {
if (value.length <= SHORT_STRING_THRESHOLD) {
const byteLength2 = utf8ByteLength(value);
if (byteLength2 <= this._byteLength) {
const offset = writer.reserve(byteLength2);
encodeUtf8Into(value, writer.bytes, offset);
return;
}
}
writer.writeBytes(textEncoder2.encode(value).subarray(0, this._byteLength));
};
this._decoder = (reader) => {
const start = reader.skipBytes(this._byteLength);
return decodeUtf8(reader.bytes, start, start + this._byteLength);
};
return;
}
let encoder;
let decoder;
switch (encoding) {
case "hex": {
encoder = decodeHex;
decoder = encodeHex;
break;
}
case "base32": {
encoder = base32.decode;
decoder = base32.encode;
break;
}
case "base58": {
encoder = base58.decode;
decoder = base58.encode;
break;
}
case "base64": {
encoder = base64.decode;
decoder = base64.encode;
break;
}
case "base64url": {
en