~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
85 lines · 2.3 KB Raw History
/**
 * pkt-line framing for the git smart protocol: each line is a 4-hex-digit
 * length (including the prefix itself) followed by payload; '0000' is the
 * flush packet that separates sections.
 */

const encoder = new TextEncoder();
const decoder = new TextDecoder();

export const FLUSH = new Uint8Array([0x30, 0x30, 0x30, 0x30]);

/**
 * @param {string|Uint8Array} payload
 * @returns {Uint8Array}
 */
export function pktLine(payload) {
  const bytes = typeof payload === 'string' ? encoder.encode(payload) : payload;
  const length = bytes.length + 4;
  if (length > 0xffff) throw new Error('pkt-line payload too large');
  const out = new Uint8Array(length);
  out.set(encoder.encode(length.toString(16).padStart(4, '0')), 0);
  out.set(bytes, 4);
  return out;
}

/**
 * @param {Array<Uint8Array>} parts
 * @returns {Uint8Array}
 */
export function concatBytes(parts) {
  const total = parts.reduce((sum, p) => sum + p.length, 0);
  const out = new Uint8Array(total);
  let offset = 0;
  for (const part of parts) {
    out.set(part, offset);
    offset += part.length;
  }
  return out;
}

/**
 * @typedef {{type: 'flush'}|{type: 'line', data: Uint8Array}} Pkt
 */

/**
 * Parse a buffer of pkt-lines. Stops cleanly at the end of the buffer;
 * throws on a truncated or malformed frame.
 * @param {Uint8Array} bytes
 * @returns {Pkt[]}
 */
export function parsePkts(bytes) {
  /** @type {Pkt[]} */
  const pkts = [];
  let offset = 0;
  while (offset < bytes.length) {
    if (offset + 4 > bytes.length) throw new Error('truncated pkt-line');
    const lenHex = decoder.decode(bytes.subarray(offset, offset + 4));
    const length = Number.parseInt(lenHex, 16);
    if (Number.isNaN(length)) throw new Error(`bad pkt-line length ${lenHex}`);
    if (length === 0) {
      pkts.push({ type: 'flush' });
      offset += 4;
      continue;
    }
    if (length < 4 || offset + length > bytes.length) {
      throw new Error('malformed pkt-line');
    }
    pkts.push({
      type: 'line',
      data: bytes.subarray(offset + 4, offset + length),
    });
    offset += length;
  }
  return pkts;
}

/**
 * Decode a pkt payload as text with the customary trailing newline removed.
 * @param {Uint8Array} data
 * @returns {string}
 */
export function pktText(data) {
  const text = decoder.decode(data);
  return text.endsWith('\n') ? text.slice(0, -1) : text;
}