byte-packet
Version:
A BytePacket is a data structure designed to encapsulate and manage binary data (bytes) in a compact and secure format. It is used in situations where you need to handle and transmit small pieces of data, ensuring that the integrity of the data is maintai
315 lines (272 loc) • 11.2 kB
JavaScript
/**
* @fileoverview A BytePacket is a data structure designed to encapsulate and manage binary data (bytes)
* in a compact and secure format. It is used in situations where you need to handle and transmit small
* pieces of data, ensuring that the integrity of the data is maintained through checksum validation.
* byte-packet is a lightweight utility module designed for generating, manipulating, and validating
* BytePacket objects in Node.js or the browser. Emphasis on performance, minimalism, and ease of integration.
*
* https://github.com/adam-ballinger/byte-packet#readme
*
* @module byte-packet
* @author Adam Ballinger
* @license MIT
*/
const sha256 = require('js-sha256');
const randomBytes = require('randombytes');
const bs58 = require('bs58').default;
// Class for byte-packet objects
class BytePacket {
/**
* Constructor to generate a BytePacket from the specified packet data
*
* This function creates a BytePacket by first calculating a checksum based on the
* provided payload and checksum size. It then generates a header containing the
* checksum size and flag information. Finally, it combines the header, payload,
* and checksum into a single `Uint8Array`.
*
* @param {Uint8Array} packet - The data to be included in the packet.
* @returns {BytePacket} The generated BytePacket
*/
constructor(packet) {
Object.defineProperty(this, 'packet', {
value: packet,
writable: false,
enumerable: true,
configurable: false
});
}
/**
* Getter to get header component of BytePacket
*
* @returns {Uint8Array} A Uint8Array header
*/
get header() {
return this.slice(0, 1);
}
/**
* Getter to get checksumSize of the BytePacket
*
* @returns {number} size of the checksum according to the BytePacket header
*/
get checksumSize() {
// Extract the first 3 bits of the header for the checksum size
return (this.header[0] >> 5) & 0b00000111; // Shift right 5 bits and mask the lower 3 bits
}
/**
* Getter to get the length of the BytePacket
*
* @returns {number} number of bytes of the BytePacket (includes header, payload, and checksum)
*/
get length() {
return this.packet.length;
}
/**
* Getter to get payloadSize of the BytePacket
*
* @returns {number} the size of the payload of the BytePacket
*/
get payloadSize() {
return this.length - 1 - this.checksumSize;
}
/**
* Getter to get payload component of the BytePacket
*
* @returns {Uint8Array} the the payload component of the BytePacket
*/
get payload() {
return this.slice(1, 1+this.payloadSize);
}
/**
* Getter to get the checksum component of the BytePacket
*
* @returns {Uint8Array} the checksum component of the BytePacket
*/
get checksum() {
return this.slice(1+this.payloadSize, 1+this.payloadSize+this.checksumSize);
}
/**
* Method to slice the packet
*
* @param {number} start The zero-based index at which to begin extraction.
* @param {number} end The zero-based index before which to end extraction. slice extracts up to but not including end.
*
* @returns {Uint8Array} slice of the BytePacket
*/
slice(start=0, end=this.packet.length) {
return this.packet.slice(start, end);
}
/**
* Getter to get the flag value from the header
*
* @returns {number} the 5 bit flag value from the header of the BytePacket
*/
get flag() {
// Extract the lower 5 bits by masking with 0b00011111 (or 0x1F in hexadecimal)
return this.header[0] & 0b00011111;
}
/**
* Getter to check the validity of a byte packet by validating its payload and checksum.
*
* @returns {boolean} True if the packet is valid, false otherwise.
*/
get isValid() {
return BytePacket.checkPair(this.payload, this.checksum);
}
/**
* Get the encoded Base 58 string of a byte packet
*
* @returns {string} The Base58-encoded string representing the byte packet.
*/
get encodeBase58() {
return bs58.encode(this.packet);
}
/**
* Static function to calculate a checksum from a given input payload.
*
* @param {Uint8Array} payload - The input payload for which to calculate the checksum.
* @param {number} size - The size of the checksum to generate.
* @returns {Uint8Array} A Uint8Array containing the checksum derived from the payload.
*/
static calculateChecksum(payload, size) {
// Create SHA-256 hash of the payload
const hash = sha256.array(payload);
// Return the first n bytes of the hash as the checksum, according to the size parameter
return new Uint8Array(hash.slice(0, size));
}
/**
* Generates a 1-byte header as a Uint8Array.
*
* @param {number} checksumSize - A 3-bit value (0-7) representing the checksum size.
* @param {number} flag - A 5-bit value (0-31) representing flags to be used by consumers.
* @returns {Uint8Array} A Uint8Array containing the 1-byte header.
*/
static generateHeader(checksumSize, flag) {
// Validate input ranges
if (checksumSize < 0 || checksumSize > 7) {
throw new Error('Checksum size must be a 3-bit value (0-7).');
}
if (flag < 0 || flag > 31) {
throw new Error('Type must be a 3-bit value (0-7).');
}
// Pack the values into a single byte
const headerByte = (checksumSize << 5) | (flag);
// Create a Uint8Array with the packed header
return new Uint8Array([headerByte]);
}
/**
* Static function to generate a random Uint8Array payload of the specified size.
*
* @param {number} size - The size of the Uint8Array to generate.
* @returns {Uint8Array} A Uint8Array filled with cryptographically secure random values.
*/
static generateRandomPayload(size) {
// Fill the array with cryptographically secure random values
let randomBuffer = randomBytes(size);
return new Uint8Array(randomBuffer);
}
/**
* Checks if two Uint8Arrays are equal in length and content.
*
* @param {Uint8Array} arr1 - The first Uint8Array to compare.
* @param {Uint8Array} arr2 - The second Uint8Array to compare.
* @returns {boolean} True if the Uint8Arrays are equal, false otherwise.
*/
static Uint8ArraysEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
/**
* Checks the validity of a payload and its associated checksum.
*
* @param {Uint8Array} payload - The payload to validate.
* @param {Uint8Array} checksum - The checksum to validate against.
* @returns {boolean} True if the checksum is valid for the given payload, false otherwise.
*/
static checkPair(payload, checksum) {
// Assume checksum provided is the correct size
let size = checksum.length;
const calculatedChecksum = BytePacket.calculateChecksum(payload, size);
return BytePacket.Uint8ArraysEqual(checksum, calculatedChecksum);
}
/**
* Static method to combine a list of Uint8Arrays into a single Uint8Array.
*
* @param {Uint8Array[]} arrays - An array of Uint8Array instances to be combined.
* @returns {Uint8Array} A single Uint8Array containing all the concatenated data.
*/
static combineUint8Arrays(arrays) {
// Calculate the total length of the combined array
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
// Create a new Uint8Array with the total length
const combinedArray = new Uint8Array(totalLength);
// Copy each array into the combined array
let offset = 0;
for (const arr of arrays) {
// Ensure offset is within bounds and copy data
if (offset + arr.length > combinedArray.length) {
throw new RangeError('Offset is out of bounds for the combined array.');
}
combinedArray.set(arr, offset);
offset += arr.length;
}
return combinedArray;
}
/**
* Constructor to generate a BytePacket from the specified payload, checksum size, and flag.
*
* This function creates a BytePAcket by first calculating a checksum based on the
* provided payload and checksum size. It then generates a header containing the
* checksum size and flag information. Finally, it combines the header, payload,
* and checksum into a single `Uint8Array`.
*
* @param {Uint8Array} payload - The data to be included in the packet.
* @param {number} [checksumSize=1] - The size of the checksum in bytes. Default is 1 byte.
* @param {number} [flag=0] - A flag used in the header for additional packet information. Default is 0.
* @returns {BytePacket} The generated BytePacket.
*/
static generatePacket(payload, checksumSize=1, flag=0) {
const checksum = BytePacket.calculateChecksum(payload, checksumSize);
const header = BytePacket.generateHeader(checksumSize, flag);
const packet = BytePacket.combineUint8Arrays([header, payload, checksum]);
return new BytePacket(packet);
}
/**
* Static function to construct a random BytePacket with a specified payload size, checksum size, and header flag.
*
* @param {number} payloadSize - The size of the payload to generate.
* @param {number} checksumSize - The size of the checksum to generate.
* @param {number} flag - A flag value to include in the packet header.
* @returns {BytePacket} The generated BytePacket.
*/
static generateRandomPacket(payloadSize=10, checksumSize=1, flag=0) {
const payload = BytePacket.generateRandomPayload(payloadSize);
return BytePacket.generatePacket(payload, checksumSize, flag);
}
/**
* Static function that constructs a new BytePacket from a Base58 string
*
* This function decodes a Base58 string into a BytePacket and checks if the resulting
* BytePacket is valid. If the packet is valid, it returns the decoded byte packet.
* If the packet is not valid, it throws an error.
*
* @param {string} base58 - The Base58-encoded string to be decoded.
* @returns {BytePacket} The decoded byte packet (Uint8Array) if valid.
* @throws {Error} If the decoded packet is not a valid byte packet.
*/
static decodeBase58(base58) {
let decodedPacket = new BytePacket(bs58.decode(base58));
if(decodedPacket.isValid) {
return decodedPacket;
} else {
throw new Error(`The decoded packet is not a valid byte packet.\n${decodedPacket}`);
}
}
}
module.exports = BytePacket;