UNPKG

kafkajs

Version:

A modern Apache Kafka client for node.js

406 lines (347 loc) 10.2 kB
const Long = require('../utils/long') const INT8_SIZE = 1 const INT16_SIZE = 2 const INT32_SIZE = 4 const INT64_SIZE = 8 const DOUBLE_SIZE = 8 const MOST_SIGNIFICANT_BIT = 0x80 // 128 const OTHER_BITS = 0x7f // 127 const UNSIGNED_INT32_MAX_NUMBER = 0xffffff80 const UNSIGNED_INT64_MAX_NUMBER = 0xffffffffffffff80n module.exports = class Encoder { static encodeZigZag(value) { return (value << 1) ^ (value >> 31) } static encodeZigZag64(value) { const longValue = Long.fromValue(value) return longValue.shiftLeft(1).xor(longValue.shiftRight(63)) } static sizeOfVarInt(value) { let encodedValue = this.encodeZigZag(value) let bytes = 1 while ((encodedValue & UNSIGNED_INT32_MAX_NUMBER) !== 0) { bytes += 1 encodedValue >>>= 7 } return bytes } static sizeOfVarLong(value) { let longValue = Encoder.encodeZigZag64(value) let bytes = 1 while (longValue.and(UNSIGNED_INT64_MAX_NUMBER).notEquals(Long.fromInt(0))) { bytes += 1 longValue = longValue.shiftRightUnsigned(7) } return bytes } static sizeOfVarIntBytes(value) { const size = value == null ? -1 : Buffer.byteLength(value) if (size < 0) { return Encoder.sizeOfVarInt(-1) } return Encoder.sizeOfVarInt(size) + size } static nextPowerOfTwo(value) { return 1 << (31 - Math.clz32(value) + 1) } /** * Construct a new encoder with the given initial size * * @param {number} [initialSize] initial size */ constructor(initialSize = 511) { this.buf = Buffer.alloc(Encoder.nextPowerOfTwo(initialSize)) this.offset = 0 } /** * @param {Buffer} buffer */ writeBufferInternal(buffer) { const bufferLength = buffer.length this.ensureAvailable(bufferLength) buffer.copy(this.buf, this.offset, 0) this.offset += bufferLength } ensureAvailable(length) { if (this.offset + length > this.buf.length) { const newLength = Encoder.nextPowerOfTwo(this.offset + length) const newBuffer = Buffer.alloc(newLength) this.buf.copy(newBuffer, 0, 0, this.offset) this.buf = newBuffer } } get buffer() { return this.buf.slice(0, this.offset) } writeInt8(value) { this.ensureAvailable(INT8_SIZE) this.buf.writeInt8(value, this.offset) this.offset += INT8_SIZE return this } writeInt16(value) { this.ensureAvailable(INT16_SIZE) this.buf.writeInt16BE(value, this.offset) this.offset += INT16_SIZE return this } writeInt32(value) { this.ensureAvailable(INT32_SIZE) this.buf.writeInt32BE(value, this.offset) this.offset += INT32_SIZE return this } writeUInt32(value) { this.ensureAvailable(INT32_SIZE) this.buf.writeUInt32BE(value, this.offset) this.offset += INT32_SIZE return this } writeInt64(value) { this.ensureAvailable(INT64_SIZE) const longValue = Long.fromValue(value) this.buf.writeInt32BE(longValue.getHighBits(), this.offset) this.buf.writeInt32BE(longValue.getLowBits(), this.offset + INT32_SIZE) this.offset += INT64_SIZE return this } writeDouble(value) { this.ensureAvailable(DOUBLE_SIZE) this.buf.writeDoubleBE(value, this.offset) this.offset += DOUBLE_SIZE return this } writeBoolean(value) { value ? this.writeInt8(1) : this.writeInt8(0) return this } writeString(value) { if (value == null) { this.writeInt16(-1) return this } const byteLength = Buffer.byteLength(value, 'utf8') this.ensureAvailable(INT16_SIZE + byteLength) this.writeInt16(byteLength) this.buf.write(value, this.offset, byteLength, 'utf8') this.offset += byteLength return this } writeVarIntString(value) { if (value == null) { this.writeVarInt(-1) return this } const byteLength = Buffer.byteLength(value, 'utf8') this.writeVarInt(byteLength) this.ensureAvailable(byteLength) this.buf.write(value, this.offset, byteLength, 'utf8') this.offset += byteLength return this } writeUVarIntString(value) { if (value == null) { this.writeUVarInt(0) return this } const byteLength = Buffer.byteLength(value, 'utf8') this.writeUVarInt(byteLength + 1) this.ensureAvailable(byteLength) this.buf.write(value, this.offset, byteLength, 'utf8') this.offset += byteLength return this } writeBytes(value) { if (value == null) { this.writeInt32(-1) return this } if (Buffer.isBuffer(value)) { // raw bytes this.ensureAvailable(INT32_SIZE + value.length) this.writeInt32(value.length) this.writeBufferInternal(value) } else { const valueToWrite = String(value) const byteLength = Buffer.byteLength(valueToWrite, 'utf8') this.ensureAvailable(INT32_SIZE + byteLength) this.writeInt32(byteLength) this.buf.write(valueToWrite, this.offset, byteLength, 'utf8') this.offset += byteLength } return this } writeVarIntBytes(value) { if (value == null) { this.writeVarInt(-1) return this } if (Buffer.isBuffer(value)) { // raw bytes this.writeVarInt(value.length) this.writeBufferInternal(value) } else { const valueToWrite = String(value) const byteLength = Buffer.byteLength(valueToWrite, 'utf8') this.writeVarInt(byteLength) this.ensureAvailable(byteLength) this.buf.write(valueToWrite, this.offset, byteLength, 'utf8') this.offset += byteLength } return this } writeUVarIntBytes(value) { if (value == null) { this.writeVarInt(0) return this } if (Buffer.isBuffer(value)) { // raw bytes this.writeUVarInt(value.length + 1) this.writeBufferInternal(value) } else { const valueToWrite = String(value) const byteLength = Buffer.byteLength(valueToWrite, 'utf8') this.writeUVarInt(byteLength + 1) this.ensureAvailable(byteLength) this.buf.write(valueToWrite, this.offset, byteLength, 'utf8') this.offset += byteLength } return this } writeEncoder(value) { if (value == null || !Buffer.isBuffer(value.buf)) { throw new Error('value should be an instance of Encoder') } this.writeBufferInternal(value.buffer) return this } writeEncoderArray(value) { if (!Array.isArray(value) || value.some(v => v == null || !Buffer.isBuffer(v.buf))) { throw new Error('all values should be an instance of Encoder[]') } value.forEach(v => { this.writeBufferInternal(v.buffer) }) return this } writeBuffer(value) { if (!Buffer.isBuffer(value)) { throw new Error('value should be an instance of Buffer') } this.writeBufferInternal(value) return this } /** * @param {any[]} array * @param {'int32'|'number'|'string'|'object'} [type] */ writeNullableArray(array, type) { // A null value is encoded with length of -1 and there are no following bytes // On the context of this library, empty array and null are the same thing const length = array.length !== 0 ? array.length : -1 this.writeArray(array, type, length) return this } /** * @param {any[]} array * @param {'int32'|'number'|'string'|'object'} [type] * @param {number} [length] */ writeArray(array, type, length) { const arrayLength = length == null ? array.length : length this.writeInt32(arrayLength) if (type !== undefined) { switch (type) { case 'int32': case 'number': array.forEach(value => this.writeInt32(value)) break case 'string': array.forEach(value => this.writeString(value)) break case 'object': this.writeEncoderArray(array) break } } else { array.forEach(value => { switch (typeof value) { case 'number': this.writeInt32(value) break case 'string': this.writeString(value) break case 'object': this.writeEncoder(value) break } }) } return this } writeVarIntArray(array, type) { if (type === 'object') { this.writeVarInt(array.length) this.writeEncoderArray(array) } else { const objectArray = array.filter(v => typeof v === 'object') this.writeVarInt(objectArray.length) this.writeEncoderArray(objectArray) } return this } writeUVarIntArray(array, type) { if (type === 'object') { this.writeUVarInt(array.length + 1) this.writeEncoderArray(array) } else if (array === null) { this.writeUVarInt(0) } else { const objectArray = array.filter(v => typeof v === 'object') this.writeUVarInt(objectArray.length + 1) this.writeEncoderArray(objectArray) } return this } // Based on: // https://en.wikipedia.org/wiki/LEB128 Using LEB128 format similar to VLQ. // https://github.com/addthis/stream-lib/blob/master/src/main/java/com/clearspring/analytics/util/Varint.java#L106 writeVarInt(value) { return this.writeUVarInt(Encoder.encodeZigZag(value)) } writeUVarInt(value) { const byteArray = [] while ((value & UNSIGNED_INT32_MAX_NUMBER) !== 0) { byteArray.push((value & OTHER_BITS) | MOST_SIGNIFICANT_BIT) value >>>= 7 } byteArray.push(value & OTHER_BITS) this.writeBufferInternal(Buffer.from(byteArray)) return this } writeVarLong(value) { const byteArray = [] let longValue = Encoder.encodeZigZag64(value) while (longValue.and(UNSIGNED_INT64_MAX_NUMBER).notEquals(Long.fromInt(0))) { byteArray.push( longValue .and(OTHER_BITS) .or(MOST_SIGNIFICANT_BIT) .toInt() ) longValue = longValue.shiftRightUnsigned(7) } byteArray.push(longValue.toInt()) this.writeBufferInternal(Buffer.from(byteArray)) return this } size() { // We can use the offset here directly, because we anyways will not re-encode the buffer when writing return this.offset } toJSON() { return this.buffer.toJSON() } }