UNPKG

bitfield

Version:

a simple bitfield, compliant with the BitTorrent spec

139 lines 4.92 kB
/** * Converts a number of bits to a number of bytes. * @param numberOfBits The number of bits to convert. * @returns The number of bytes that are needed to store the given number of bits. */ function bitsToBytes(numberOfBits) { return (numberOfBits >> 3) + Number(numberOfBits % 8 !== 0); } /** Bit-level set/get utility backed by a growable `Uint8Array`. */ export default class BitField { /** * Grow the bitfield up to this number of entries. * @default 0. */ grow; /** The internal storage of the bitfield. */ buffer; /** The number of bits in the bitfield. */ get length() { return this.buffer.length << 3; } /** * Constructs a BitField. * @param data Either a number representing the maximum number of supported bits, or a Uint8Array. * @param options Configuration for bitfield growth behavior. */ constructor(data = 0, options) { const grow = options?.grow; this.grow = grow ? Number.isFinite(grow) ? bitsToBytes(grow) : grow : 0; this.buffer = typeof data === "number" ? new Uint8Array(bitsToBytes(data)) : data; } /** * Get a particular bit. * @param bitIndex Bit index to retrieve. * @returns A boolean indicating whether the `i`th bit is set. */ get(bitIndex) { const byteIndex = bitIndex >> 3; return (byteIndex < this.buffer.length && !!(this.buffer[byteIndex] & (0b1000_0000 >> (bitIndex % 8)))); } /** * Set a particular bit. * * Will grow the underlying array if the bit is out of bounds and the `grow` option is set. * @param bitIndex Bit index to set. * @param value Value to set the bit to. Defaults to `true`. */ set(bitIndex, value = true) { const byteIndex = bitIndex >> 3; if (value) { if (byteIndex >= this.buffer.length) { const newLength = Math.max(byteIndex + 1, Math.min(2 * this.buffer.length, this.grow)); if (newLength <= this.grow) { const newBuffer = new Uint8Array(newLength); newBuffer.set(this.buffer); this.buffer = newBuffer; } } this.buffer[byteIndex] |= 0b1000_0000 >> (bitIndex % 8); } else if (byteIndex < this.buffer.length) { this.buffer[byteIndex] &= ~(0b1000_0000 >> (bitIndex % 8)); } } /** * Sets a value or an array of values. * @param array An array of booleans to set. * @param offset The bit offset at which the values are to be written. */ setAll(array, offset = 0) { const targetLength = Math.min(bitsToBytes(offset + array.length), this.grow); if (this.buffer.length < targetLength) { const newBuffer = new Uint8Array(targetLength); newBuffer.set(this.buffer); this.buffer = newBuffer; } let byteIndex = offset >> 3; let bitMask = 0b1000_0000 >> (offset % 8); // eslint-disable-next-line unicorn/no-for-loop -- `array` is `ArrayLike`, not guaranteed iterable. for (let index = 0; index < array.length; index++) { const element = array[index]; if (element) { this.buffer[byteIndex] |= bitMask; } else { this.buffer[byteIndex] &= ~bitMask; } if (bitMask === 1) { byteIndex += 1; if (byteIndex >= this.buffer.length) { break; } bitMask = 0b1000_0000; } else { bitMask >>= 1; } } } /** * Loop through the bits in the bitfield. * @param callbackfn Function to be called with the bit value and index. * @param start Index of the first bit to look at. * @param end Index of the first bit that should no longer be considered. */ forEach(callbackfn, start = 0, end = this.buffer.length * 8) { let byteIndex = start >> 3; let bitMask = 0b1000_0000 >> (start % 8); for (let bitIndex = start; bitIndex < end; bitIndex++) { callbackfn(!!(this.buffer[byteIndex] & bitMask), bitIndex); if (bitMask === 1) { byteIndex += 1; bitMask = 0b1000_0000; } else { bitMask >>= 1; } } } /** * Check if all bits in the Bitfield are unset. * @returns A boolean indicating whether all bits are unset. */ isEmpty() { for (let index = 0; index < this.buffer.length; index++) { if (this.buffer[index] !== 0) { return false; } } return true; } } //# sourceMappingURL=index.js.map