/** * Bencode encoder — serializes JS values to bencode bytes. * Used primarily for UDP tracker connect/announce requests. */ /** * Encoder-only type hierarchy — mirrors BencodeValue but adds `string` at every level. * The *decoder* never returns strings (bencode byte-strings are always Buffer), so * BencodeValue intentionally excludes string. The encoder is more permissive: callers * can pass plain JS strings, and they are serialized as bencode byte-strings. */ export type EncodableDict = { [key: string]: EncodableValue }; export type EncodableList = EncodableValue[]; export type EncodableValue = number | string | Buffer | EncodableList | EncodableDict; export function encode(value: EncodableValue): Buffer { if (typeof value === "number") { return Buffer.from(`i${Math.trunc(value)}e`, "ascii"); } if (Buffer.isBuffer(value)) { return Buffer.concat([Buffer.from(`${value.length}:`, "ascii"), value]); } if (typeof value === "string") { const strBuf = Buffer.from(value, "utf8"); return Buffer.concat([Buffer.from(`${strBuf.length}:`, "ascii"), strBuf]); } if (Array.isArray(value)) { return encodeList(value); } return encodeDict(value); } function encodeList(list: EncodableList): Buffer { const parts: Buffer[] = [Buffer.from("l", "ascii")]; for (const item of list) { parts.push(encode(item)); } parts.push(Buffer.from("e", "ascii")); return Buffer.concat(parts); } function encodeDict(dict: EncodableDict): Buffer { const parts: Buffer[] = [Buffer.from("d", "ascii")]; // Keys must be sorted lexicographically const keys = Object.keys(dict).sort(); for (const key of keys) { const keyBuf = Buffer.from(key, "utf8"); parts.push(Buffer.from(`${keyBuf.length}:`, "ascii"), keyBuf); parts.push(encode(dict[key])); } parts.push(Buffer.from("e", "ascii")); return Buffer.concat(parts); }