'use strict'; /** * Minimal CBOR encoder for CWT/COSE structures (RFC 7049). * * Supports: * - null / undefined * - booleans * - integers (positive and negative) * - byte strings (Buffer) * - text strings * - fixed-length arrays * - maps (ES6 Map — preserves key order; plain objects — string keys only) * - tagged values * * Not supported: floats, indefinite-length items. */ /** * Encodes a CBOR "head": major type (3 bits) + additional info (variable length). * @param {number} majorType - 0..7 * @param {number} n - Argument value (uint). * @returns {Buffer} */ function encodeHead(majorType, n) { const mt = majorType << 5; if (n <= 23) return Buffer.from([mt | n]); if (n <= 0xff) return Buffer.from([mt | 24, n]); if (n <= 0xffff) return Buffer.from([mt | 25, (n >> 8) & 0xff, n & 0xff]); return Buffer.from([ mt | 26, (n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff, ]); } /** * CBOR-encodes a JavaScript value. * * - null / undefined → 0xf6 (null) * - boolean → 0xf4 / 0xf5 * - integer >= 0 → major type 0 * - integer < 0 → major type 1 * - Buffer → major type 2 (byte string) * - string → major type 3 (text string, UTF-8) * - Array → major type 4 * - Map → major type 5 (keys encoded in insertion order) * - plain object → major type 5 (string keys, own enumerable) * * @param {*} value * @returns {Buffer} */ function encode(value) { if (value === null || value === undefined) { return Buffer.from([0xf6]); } if (typeof value === 'boolean') { return Buffer.from([value ? 0xf5 : 0xf4]); } if (typeof value === 'number') { if (!Number.isInteger(value)) { throw new Error('cbor: float encoding not supported'); } if (value >= 0) return encodeHead(0, value); // unsigned int return encodeHead(1, -1 - value); // negative int } if (typeof value === 'string') { const buf = Buffer.from(value, 'utf8'); return Buffer.concat([encodeHead(3, buf.length), buf]); } if (Buffer.isBuffer(value)) { return Buffer.concat([encodeHead(2, value.length), value]); } if (Array.isArray(value)) { const parts = [encodeHead(4, value.length)]; for (const item of value) parts.push(encode(item)); return Buffer.concat(parts); } if (value instanceof Map) { const parts = [encodeHead(5, value.size)]; for (const [k, v] of value.entries()) { parts.push(encode(k), encode(v)); } return Buffer.concat(parts); } if (typeof value === 'object') { const keys = Object.keys(value); const parts = [encodeHead(5, keys.length)]; for (const k of keys) { parts.push(encode(k), encode(value[k])); } return Buffer.concat(parts); } throw new Error(`cbor: cannot encode type "${typeof value}"`); } /** * Wraps an already-encoded CBOR Buffer with a CBOR tag (major type 6). * @param {number} tagNum - Tag number (e.g. 18 for COSE_Sign1). * @param {Buffer} encoded - Pre-encoded CBOR value to tag. * @returns {Buffer} */ function tagged(tagNum, encoded) { return Buffer.concat([encodeHead(6, tagNum), encoded]); } module.exports = { encode, tagged };