Ctrl K
ComunidadGuías…

src/proto.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * Protobuf wire format codec — zero-dependency, schema-less.
 *
 * Wire types:
 *   0 = Varint    (int32, uint64, bool, enum)
 *   1 = Fixed64   (double, fixed64)
 *   2 = LenDelim  (string, bytes, embedded messages)
 *   5 = Fixed32   (float, fixed32)
 */

// ─── Varint ────────────────────────────────────────────────

export function encodeVarint(value) {
  const bytes = [];
  // BigInt path for negatives (two's-complement uint64) and any int > 2^31
  // since JS `>>>` truncates to uint32 and silently corrupts larger varints.
  if (typeof value === 'bigint' || value < 0 || value > 0x7FFFFFFF) {
    let b = (typeof value === 'bigint' ? value : BigInt(value)) & 0xFFFFFFFFFFFFFFFFn;
    while (true) {
      const byte = Number(b & 0x7Fn);
      b >>= 7n;
      if (b === 0n) { bytes.push(byte); break; }
      bytes.push(byte | 0x80);
    }
    return Buffer.from(bytes);
  }
  let v = Number(value);
  do {
    let byte = v & 0x7F;
    v >>>= 7;
    if (v > 0) byte |= 0x80;
    bytes.push(byte);
  } while (v > 0);
  return Buffer.from(bytes);
}

export function decodeVarint(buf, offset = 0) {
  // Fast path: read up to 4 bytes (28 bits) with plain int math — covers
  // all field tags and most small ints without touching BigInt. Fall through
  // to BigInt for anything larger so uint64 values (request_id, credit
  // counters, timestamps) decode accurately without sign/truncation bugs.
  let result = 0, shift = 0, pos = offset;
  while (pos < buf.length && shift < 28) {
    const byte = buf[pos++];
    result |= (byte & 0x7F) << shift;
    if (!(byte & 0x80)) return { value: result >>> 0, length: pos - offset };
    shift += 7;
  }
  if (pos >= buf.length) throw new Error('Truncated varint');
  // Continuation byte needed beyond 28 bits — switch to BigInt.
  let big = BigInt(result >>> 0);
  let bigShift = BigInt(shift);
  while (pos < buf.length) {
    const byte = buf[pos++];
    big |= BigInt(byte & 0x7F) << bigShift;
    // A canonical protobuf varint is a uint64: the terminating byte may not
    // push the value past 2**64-1 (S4-D1). Without this, `80×9 7F` decodes to
    // ~2**70 and silently feeds field numbers/lengths that never existed.
    if (big > 0xFFFFFFFFFFFFFFFFn) throw new Error('Varint exceeds uint64');
    if (!(byte & 0x80)) {
      // Return Number if safely representable, else BigInt.
      const asNum = Number(big);
      return { value: Number.isSafeInteger(asNum) ? asNum : big, length: pos - offset };
    }
    bigShift += 7n;
    if (bigShift >= 64n) throw new Error('Varint overflow');
  }
  throw new Error('Truncated varint');
}

// ─── Field-level writers (standalone functions) ────────────

function makeTag(field, wireType) {
  return encodeVarint((field << 3) | wireType);
}

// The old tag expression is signed int32. Tags with bit 31 set intentionally
// stay on the old BigInt path; treating them as uint32 would change their bytes.
function isSmallField(field) {
  return Number.isInteger(field) && field >= 0 && field <= 0x0FFFFFFF;
}

function varintLength32(value) {
  let length = 1;
  while (value > 0x7F) { value >>>= 7; length++; }
  return length;
}

function writeVarint32(buf, offset, value) {
  do {
    const byte = value & 0x7F;
    value >>>= 7;
    buf[offset++] = value ? byte | 0x80 : byte;
  } while (value);
  return offset;
}

/** Write a varint field (wire type 0). */
export function writeVarintField(field, value) {
  // Restrict the fast path to primitive integers. The fallback preserves the
  // old coercion order, negative uint64 encoding, and unusual numeric inputs.
  if (isSmallField(field) && Number.isInteger(value) && value >= 0 && value <= 0x7FFFFFFF) {
    const tag = (field << 3) | 0;
    const out = Buffer.alloc(varintLength32(tag) + varintLength32(value));
    const offset = writeVarint32(out, 0, tag);
    writeVarint32(out, offset, value);
    return out;
  }
  return Buffer.concat([makeTag(field, 0), encodeVarint(value)]);
}

/** Write a length-delimited string field (wire type 2). */
export function writeStringField(field, str) {
  if (!str && str !== '') return Buffer.alloc(0);
  // Strings are immutable: measuring UTF-8 does not re-evaluate user coercions.
  // Buffer-like and coercible inputs retain Buffer.from's original semantics.
  if (typeof str === 'string' && isSmallField(field)) {
    const length = Buffer.byteLength(str, 'utf-8');
    if (length <= 0x7FFFFFFF) {
      const tag = (field << 3) | 2;
      const out = Buffer.alloc(varintLength32(tag) + varintLength32(length) + length);
      let offset = writeVarint32(out, 0, tag);
      offset = writeVarint32(out, offset, length);
      out.write(str, offset, length, 'utf-8');
      return out;
    }
  }
  const data = Buffer.from(str, 'utf-8');
  return Buffer.concat([makeTag(field, 2), encodeVarint(data.length), data]);
}

/** Write a length-delimited bytes field (wire type 2). */
export function writeBytesField(field, data) {
  const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
  return Buffer.concat([makeTag(field, 2), encodeVarint(buf.length), buf]);
}

/** Write an embedded message field (wire type 2). */
export function writeMessageField(field, msgBuf) {
  if (!msgBuf || msgBuf.length === 0) return Buffer.alloc(0);
  if (Buffer.isBuffer(msgBuf) && isSmallField(field) && msgBuf.length <= 0x7FFFFFFF) {
    const length = msgBuf.length;
    const tag = (field << 3) | 2;
    const out = Buffer.alloc(varintLength32(tag) + varintLength32(length) + length);
    let offset = writeVarint32(out, 0, tag);
    offset = writeVarint32(out, offset, length);
    out.set(msgBuf, offset);
    return out;
  }
  return Buffer.concat([makeTag(field, 2), encodeVarint(msgBuf.length), msgBuf]);
}

/** Write a fixed64 field (wire type 1). */
export function writeFixed64Field(field, buf8) {
  return Buffer.concat([makeTag(field, 1), buf8]);
}

/** Write a fixed32 field (wire type 5). */
export function writeFixed32Field(field, buf4) {
  return Buffer.concat([makeTag(field, 5), buf4]);
}

/** Write a bool field (wire type 0), only if true. */
export function writeBoolField(field, value) {
  if (!value) return Buffer.alloc(0);
  return writeVarintField(field, 1);
}

// ─── Parser ────────────────────────────────────────────────

/**
 * Parse a protobuf buffer into an array of { field, wireType, value }.
 * For varint (0): value is a Number.
 * For lendelim (2): value is a Buffer (caller decides string vs message).
 * For fixed64 (1): value is an 8-byte Buffer.
 * For fixed32 (5): value is a 4-byte Buffer.
 */
export function parseFields(buf) {
  const fields = [];
  let pos = 0;
  while (pos < buf.length) {
    const tag = decodeVarint(buf, pos);
    pos += tag.length;
    const fieldNum = Number(tag.value) >>> 3;
    const wireType = Number(tag.value) & 0x07;

    let value;
    switch (wireType) {
      case 0: { // varint
        const v = decodeVarint(buf, pos);
        pos += v.length;
        value = v.value;
        break;
      }
      case 1: { // fixed64
        if (pos + 8 > buf.length) throw new Error(`truncated fixed64 at offset ${pos}`);
        value = buf.subarray(pos, pos + 8);
        pos += 8;
        break;
      }
      case 2: { // length-delimited
        const len = decodeVarint(buf, pos);
        pos += len.length;
        const sz = Number(len.value);
        if (sz < 0 || pos + sz > buf.length) {
          throw new Error(`truncated len-delim field ${fieldNum} at offset ${pos} (need ${sz}, have ${buf.length - pos})`);
        }
        value = buf.subarray(pos, pos + sz);
        pos += sz;
        break;
      }
      case 5: { // fixed32
        if (pos + 4 > buf.length) throw new Error(`truncated fixed32 at offset ${pos}`);
        value = buf.subarray(pos, pos + 4);
        pos += 4;
        break;
      }
      default:
        throw new Error(`Unknown wire type ${wireType} at offset ${pos}`);
    }
    fields.push({ field: fieldNum, wireType, value });
  }
  return fields;
}

/** Get first field matching number and optional wire type. */
export function getField(fields, num, wireType) {
  return fields.find(f => f.field === num && (wireType === undefined || f.wireType === wireType)) || null;
}

/** Get all fields matching number. */
export function getAllFields(fields, num) {
  return fields.filter(f => f.field === num);
}