110 lines
3.4 KiB
JavaScript
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 };
|