2026-04-27 04:25:52 +00:00

110 lines
3.4 KiB
JavaScript

'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 };