diamante-js-xdr
Version:
Read/write XDR encoded data structures (RFC 4506)
180 lines (164 loc) • 4.42 kB
JavaScript
const BUFFER_CHUNK = 8192; // 8 KB chunk size increment
/**
* @internal
*/
export class XdrWriter {
/**
* @param {Buffer|Number} [buffer] - Optional destination buffer
*/
constructor(buffer) {
if (typeof buffer === 'number') {
buffer = Buffer.allocUnsafe(buffer);
} else if (!(buffer instanceof Buffer)) {
buffer = Buffer.allocUnsafe(BUFFER_CHUNK);
}
this._buffer = buffer;
this._length = buffer.length;
}
/**
* @type {Buffer}
* @private
* @readonly
*/
_buffer;
/**
* @type {Number}
* @private
* @readonly
*/
_length;
/**
* @type {Number}
* @private
* @readonly
*/
_index = 0;
/**
* Advance writer position, write padding if needed, auto-resize the buffer
* @param {Number} size - Bytes to write
* @return {Number} Position to read from
* @private
*/
alloc(size) {
const from = this._index;
// advance cursor position
this._index += size;
// ensure sufficient buffer size
if (this._length < this._index) {
this.resize(this._index);
}
return from;
}
/**
* Increase size of the underlying buffer
* @param {Number} minRequiredSize - Minimum required buffer size
* @return {void}
* @private
*/
resize(minRequiredSize) {
// calculate new length, align new buffer length by chunk size
const newLength = Math.ceil(minRequiredSize / BUFFER_CHUNK) * BUFFER_CHUNK;
// create new buffer and copy previous data
const newBuffer = Buffer.allocUnsafe(newLength);
this._buffer.copy(newBuffer, 0, 0, this._length);
// update references
this._buffer = newBuffer;
this._length = newLength;
}
/**
* Return XDR-serialized value
* @return {Buffer}
*/
finalize() {
// clip underlying buffer to the actually written value
return this._buffer.subarray(0, this._index);
}
/**
* Return XDR-serialized value as byte array
* @return {Number[]}
*/
toArray() {
return [...this.finalize()];
}
/**
* Write byte array from the buffer
* @param {Buffer|String} value - Bytes/string to write
* @param {Number} size - Size in bytes
* @return {XdrReader} - XdrReader wrapper on top of a subarray
*/
write(value, size) {
if (typeof value === 'string') {
// serialize string directly to the output buffer
const offset = this.alloc(size);
this._buffer.write(value, offset, 'utf8');
} else {
// copy data to the output buffer
if (!(value instanceof Buffer)) {
value = Buffer.from(value);
}
const offset = this.alloc(size);
value.copy(this._buffer, offset, 0, size);
}
// add padding for 4-byte XDR alignment
const padding = 4 - (size % 4 || 4);
if (padding > 0) {
const offset = this.alloc(padding);
this._buffer.fill(0, offset, this._index);
}
}
/**
* Write i32 from buffer
* @param {Number} value - Value to serialize
* @return {void}
*/
writeInt32BE(value) {
const offset = this.alloc(4);
this._buffer.writeInt32BE(value, offset);
}
/**
* Write u32 from buffer
* @param {Number} value - Value to serialize
* @return {void}
*/
writeUInt32BE(value) {
const offset = this.alloc(4);
this._buffer.writeUInt32BE(value, offset);
}
/**
* Write i64 from buffer
* @param {BigInt} value - Value to serialize
* @return {void}
*/
writeBigInt64BE(value) {
const offset = this.alloc(8);
this._buffer.writeBigInt64BE(value, offset);
}
/**
* Write u64 from buffer
* @param {BigInt} value - Value to serialize
* @return {void}
*/
writeBigUInt64BE(value) {
const offset = this.alloc(8);
this._buffer.writeBigUInt64BE(value, offset);
}
/**
* Write float from buffer
* @param {Number} value - Value to serialize
* @return {void}
*/
writeFloatBE(value) {
const offset = this.alloc(4);
this._buffer.writeFloatBE(value, offset);
}
/**
* Write double from buffer
* @param {Number} value - Value to serialize
* @return {void}
*/
writeDoubleBE(value) {
const offset = this.alloc(8);
this._buffer.writeDoubleBE(value, offset);
}
static bufferChunkSize = BUFFER_CHUNK;
}