/** * 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} 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; }