|
class CBORDecoderBase { |
|
// Possible monkeypatch apis responsibilities: |
|
// decode() :: |
|
// *iter_decode() :: |
|
// async decode_stream() :: |
|
// async * aiter_decode_stream() :: |
|
|
|
static options(options) { |
|
return (class extends this {}) |
|
.compile(options)} |
|
|
|
static compile(options) { |
|
this.prototype.compile(options); |
|
return this} |
|
|
|
constructor(options) { |
|
if (null != options) { |
|
this.compile(options);} |
|
|
|
this._U8Ctx_.bind_decode_api(this);} |
|
|
|
compile(options) { |
|
this.jmp = this._bind_cbor_jmp(options, this.jmp); |
|
|
|
if (options.types) { |
|
this.types = Object.assign( |
|
Object.create(this.types || null), |
|
options.types); } |
|
|
|
this._U8Ctx_ = this._bind_u8ctx( |
|
this.types, this.jmp, options.unknown); |
|
return this} } |
|
|
|
Array.from(Array(256), |
|
(_, v) => v.toString(2).padStart(8, '0')); |
|
|
|
Array.from(Array(256), |
|
(_, v) => v.toString(16).padStart(2, '0')); |
|
|
|
function u8_to_utf8(u8) { |
|
return new TextDecoder('utf-8').decode(u8) } |
|
|
|
function as_u8_buffer(u8) { |
|
|
|
|
|
|
|
|
|
if (u8 instanceof Uint8Array) { |
|
return u8} |
|
if (ArrayBuffer.isView(u8)) { |
|
return new Uint8Array(u8.buffer)} |
|
if (u8 instanceof ArrayBuffer) { |
|
return new Uint8Array(u8)} |
|
return Uint8Array.from(u8)} |
|
|
|
function u8_concat(parts) { |
|
let i=0, len=0; |
|
for (const b of parts) { |
|
const byteLength = b.byteLength; |
|
if ('number' !== typeof byteLength) { |
|
throw new Error("Invalid part byteLength") } |
|
len += byteLength;} |
|
|
|
const u8 = new Uint8Array(len); |
|
for (const u8_part of parts) { |
|
u8.set(u8_part, i); |
|
i += u8_part.byteLength;} |
|
return u8} |
|
|
|
const cbor_decode_sym = Symbol('CBOR-decode'); |
|
const cbor_encode_sym = Symbol('CBOR-encode'); |
|
|
|
const cbor_break_sym = Symbol('CBOR-break'); |
|
const cbor_done_sym = Symbol('CBOR-done'); |
|
const cbor_eoc_sym = Symbol('CBOR-EOC'); |
|
|
|
const cbor_tagged_proto ={ |
|
[Symbol.toStringTag]: 'cbor_tag', |
|
|
|
[cbor_encode_sym](enc_ctx, v) { |
|
enc_ctx.tag_encode(v.tag, v.body);} }; |
|
|
|
|
|
function cbor_accum(base) { |
|
return iv => ({ |
|
__proto__: base, |
|
res: base.init(iv) })} |
|
|
|
const decode_types ={ |
|
__proto__: null |
|
|
|
, nested_cbor(u8, ctx) { |
|
ctx = ctx.from_nested_u8(u8); |
|
u8.decode_cbor = () => ctx.decode_cbor(); |
|
return u8} |
|
|
|
, u32(u8, idx) { |
|
const u32 = (u8[idx] << 24) | (u8[idx+1] << 16) | (u8[idx+2] << 8) | u8[idx+3]; |
|
return u32 >>> 0 }// unsigned int32 |
|
|
|
, u64(u8, idx) { |
|
const v_hi = (u8[idx] << 24) | (u8[idx+1] << 16) | (u8[idx+2] << 8) | u8[idx+3]; |
|
const v_lo = (u8[idx+4] << 24) | (u8[idx+5] << 16) | (u8[idx+6] << 8) | u8[idx+7]; |
|
const u64 = (v_lo >>> 0) + 0x100000000*(v_hi >>> 0); |
|
return u64} |
|
|
|
, float16(u8) { |
|
return {'@f2': u8}} |
|
, float32(u8, idx=u8.byteOffset) { |
|
return new DataView(u8.buffer, idx, 4).getFloat32(0)} |
|
, float64(u8, idx=u8.byteOffset) { |
|
return new DataView(u8.buffer, idx, 8).getFloat64(0)} |
|
|
|
, bytes(u8) {return u8} |
|
, bytes_stream: |
|
cbor_accum({ |
|
init: () => [] |
|
, accum: _res_push |
|
, done: res => u8_concat(res)}) |
|
|
|
, utf8(u8) {return u8_to_utf8(u8)} |
|
, utf8_stream: |
|
cbor_accum({ |
|
init: () => [] |
|
, accum: _res_push |
|
, done: res => res.join('')}) |
|
|
|
|
|
, list: |
|
cbor_accum({ |
|
init: () => [] |
|
, accum: _res_attr}) |
|
|
|
, list_stream() { |
|
return this.list()} |
|
|
|
|
|
, map: |
|
cbor_accum({ |
|
init: () => ({}) |
|
, accum: _res_attr}) |
|
|
|
, map_stream() { |
|
return this.map()} }; |
|
|
|
|
|
function _res_push(res,i,v) {res.push(v);} |
|
function _res_attr(res,k,v) {res[k] = v;} |
|
|
|
const decode_Map ={ |
|
map: |
|
cbor_accum({ |
|
init: () => new Map() |
|
, accum: (res, k, v) => res.set(k, v)}) }; |
|
|
|
const decode_Set ={ |
|
list: |
|
cbor_accum({ |
|
init: () => new Set() |
|
, accum: (res, i, v) => res.add(v)}) }; |
|
|
|
function basic_tags(tags_lut) { |
|
// from https://tools.ietf.org/html/rfc7049#section-2.4 |
|
|
|
// Standard date/time string; see Section 2.4.1 |
|
tags_lut.set(0, () => ts_sz => new Date(ts_sz)); |
|
// Epoch-based date/time; see Section 2.4.1 |
|
tags_lut.set(1, () => seconds => new Date(seconds * 1000)); |
|
|
|
// Positive bignum; see Section 2.4.2 |
|
// tags_lut.set @ 2, () => v => v |
|
|
|
// Negative bignum; see Section 2.4.2 |
|
// tags_lut.set @ 3, () => v => v |
|
|
|
// Decimal fraction; see Section 2.4.3 |
|
// tags_lut.set @ 4, () => v => v |
|
|
|
// Bigfloat; see Section 2.4.3 |
|
// tags_lut.set @ 5, () => v => v |
|
|
|
// Expected conversion to base64url encoding; see Section 2.4.4.2 |
|
// tags_lut.set @ 21, () => v => v |
|
|
|
// Expected conversion to base64 encoding; see Section 2.4.4.2 |
|
// tags_lut.set @ 22, () => v => v |
|
|
|
// Expected conversion to base16 encoding; see Section 2.4.4.2 |
|
// tags_lut.set @ 23, () => v => v |
|
|
|
// Encoded CBOR data item; see Section 2.4.4.1 |
|
tags_lut.set(24, ctx => u8 => ctx.types.nested_cbor(u8, ctx)); |
|
|
|
// URI; see Section 2.4.4.3 |
|
tags_lut.set(32, () => url_sz => new URL(url_sz)); |
|
|
|
// base64url; see Section 2.4.4.3 |
|
//tags_lut.set @ 33, () => v => v |
|
|
|
// base64; see Section 2.4.4.3 |
|
//tags_lut.set @ 34, () => v => v |
|
|
|
// Regular expression; see Section 2.4.4.3 |
|
//tags_lut.set @ 35, () => v => v |
|
|
|
// MIME message; see Section 2.4.4.3 |
|
//tags_lut.set @ 36, () => v => v |
|
|
|
// Self-describe CBOR; see Section 2.4.5 |
|
tags_lut.set(55799, () => {}); |
|
|
|
|
|
// EXTENSIONS |
|
|
|
// CBOR Sets https://github.com/input-output-hk/cbor-sets-spec/blob/master/CBOR_SETS.md |
|
tags_lut.set(258, ctx => { ctx.use_overlay(decode_Set); }); |
|
|
|
// CBOR Maps https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-256-spec--explicit-maps.md |
|
tags_lut.set(259, ctx => { ctx.use_overlay(decode_Map); }); |
|
|
|
return tags_lut} |
|
|
|
class U8DecodeBaseCtx { |
|
|
|
static subclass(types, jmp, unknown) { |
|
class U8DecodeCtx_ extends this {} |
|
let {prototype} = U8DecodeCtx_; |
|
prototype.next_value = U8DecodeCtx_.bind_next_value(jmp, unknown); |
|
prototype.types = types; |
|
return U8DecodeCtx_} |
|
|
|
|
|
from_nested_u8(u8) { |
|
return this.constructor |
|
.from_u8(u8, this.types)} |
|
|
|
|
|
use_overlay(overlay_types) { |
|
let {types, _apply_overlay, _overlay_noop} = this; |
|
|
|
if (_overlay_noop === _apply_overlay) { |
|
_apply_overlay = () => { |
|
this.types = types;}; } |
|
|
|
this._apply_overlay = (() => { |
|
this._apply_overlay = _apply_overlay; |
|
this.types = overlay_types;} ); |
|
return types} |
|
|
|
_error_unknown(ctx, type_b) { |
|
throw new Error(`No CBOR decorder regeistered for ${type_b} (0x${('0'+type_b.toString(16)).slice(-2)})`) } |
|
|
|
_overlay_noop() {} |
|
|
|
// Subclass responsibilities: |
|
// static bind_decode_api(decoder) |
|
// static bind_next_value(jmp, unknown) :: |
|
// move(count_bytes) :: |
|
|
|
// Possible Subclass responsibilities: |
|
// decode_cbor() :: |
|
// *iter_decode_cbor() :: |
|
// async decode_cbor() :: |
|
}// async * aiter_decode_cbor() :: |
|
|
|
class U8SyncDecodeCtx extends U8DecodeBaseCtx { |
|
static bind_decode_api(decoder) { |
|
decoder.decode = u8 => |
|
this.from_u8(u8, decoder.types) |
|
.decode_cbor(); |
|
|
|
decoder.iter_decode = u8 => |
|
this.from_u8(u8, decoder.types) |
|
.iter_decode_cbor();} |
|
|
|
|
|
static get from_u8() { |
|
const inst0 = new this(); |
|
|
|
return (u8, types) => { |
|
u8 = as_u8_buffer(u8); |
|
const inst ={ |
|
__proto__: inst0 |
|
, idx: 0, u8 |
|
, _apply_overlay: inst0._overlay_noop}; |
|
|
|
if (types && types !== inst0.types) { |
|
inst.types = types;} |
|
return inst} } |
|
|
|
|
|
static bind_next_value(jmp, unknown) { |
|
if (null == unknown) { |
|
unknown = this._error_unknown;} |
|
|
|
return function next_value() { |
|
const doneTypes = this._apply_overlay(); |
|
|
|
const type_b = this.u8[ this.idx ++ ]; |
|
if (undefined === type_b) { |
|
this.idx--; |
|
throw cbor_done_sym} |
|
|
|
const decode = jmp[type_b] || unknown; |
|
const res = decode(this, type_b); |
|
|
|
return undefined === doneTypes |
|
? res : doneTypes(res)} } |
|
|
|
|
|
decode_cbor() { |
|
try { |
|
return this.next_value()} |
|
catch (e) { |
|
throw cbor_done_sym !== e ? e |
|
: new Error(`End of content`) } } |
|
|
|
|
|
*iter_decode_cbor() { |
|
try { |
|
while (1) { |
|
yield this.next_value();} } |
|
catch (e) { |
|
if (cbor_done_sym !== e) { |
|
throw e} } } |
|
|
|
|
|
move(count_bytes) { |
|
const {idx, byteLength} = this; |
|
const idx_next = idx + count_bytes; |
|
if (idx_next >= byteLength) { |
|
throw cbor_eoc_sym} |
|
this.idx = idx_next; |
|
return idx} } |
|
|
|
const _cbor_jmp_base ={ |
|
bind_jmp(options, jmp) { |
|
jmp = jmp ? jmp.slice() |
|
: this.bind_basics_dispatch( new Map() ); |
|
|
|
if (null == options) { |
|
options = {};} |
|
|
|
if (options.simple) { |
|
this.bind_jmp_simple(options, jmp);} |
|
|
|
if (options.tags) { |
|
this.bind_jmp_tag(options, jmp);} |
|
return jmp} |
|
|
|
, bind_jmp_simple(options, jmp) { |
|
if (options.simple) { |
|
const as_simple_value = this.bind_simple_dispatch(options.simple); |
|
const tiny_simple = this.cbor_tiny(as_simple_value); |
|
|
|
for (let i=0xe0; i<= 0xf3; i++) { |
|
jmp[i] = tiny_simple;} |
|
|
|
jmp[0xf8] = this.cbor_w1(as_simple_value);} |
|
return jmp} |
|
|
|
|
|
, bind_jmp_tag(options, jmp) { |
|
if (options.tags) { |
|
const as_tag = this.bind_tag_dispatch( |
|
this.build_tags_lut(options.tags)); |
|
const tiny_tag = this.cbor_tiny(as_tag); |
|
|
|
for (let i=0xc0; i<= 0xd7; i++) { |
|
jmp[0xc0 | i] = tiny_tag;} |
|
|
|
jmp[0xd8] = this.cbor_w1(as_tag); |
|
jmp[0xd9] = this.cbor_w2(as_tag); |
|
jmp[0xda] = this.cbor_w4(as_tag); |
|
jmp[0xdb] = this.cbor_w8(as_tag);} |
|
|
|
return jmp} |
|
|
|
|
|
, bind_basics_dispatch(tags_lut) { |
|
this.bind_tag_dispatch(tags_lut); |
|
|
|
const tiny_pos_int = this.cbor_tiny(this.as_pos_int); |
|
const tiny_neg_int = this.cbor_tiny(this.as_neg_int); |
|
const tiny_bytes = this.cbor_tiny(this.as_bytes); |
|
const tiny_utf8 = this.cbor_tiny(this.as_utf8); |
|
const tiny_list = this.cbor_tiny(this.as_list); |
|
const tiny_map = this.cbor_tiny(this.as_map); |
|
const tiny_tag = this.cbor_tiny(this.as_tag); |
|
const tiny_simple_repr = this.cbor_tiny(this.as_simple_repr); |
|
|
|
const jmp = new Array(256); |
|
|
|
for (let i=0; i<= 23; i++) { |
|
jmp[0x00 | i] = tiny_pos_int; |
|
jmp[0x20 | i] = tiny_neg_int; |
|
jmp[0x40 | i] = tiny_bytes; |
|
jmp[0x60 | i] = tiny_utf8; |
|
jmp[0x80 | i] = tiny_list; |
|
jmp[0xa0 | i] = tiny_map; |
|
jmp[0xc0 | i] = tiny_tag; |
|
jmp[0xe0 | i] = tiny_simple_repr;} |
|
|
|
|
|
const cbor_widths =[ |
|
this.cbor_w1, |
|
this.cbor_w2, |
|
this.cbor_w4, |
|
this.cbor_w8]; |
|
|
|
for (let w=0; w< 4; w++) { |
|
const i = 24+w, cbor_wN = cbor_widths[w]; |
|
jmp[0x00 | i] = cbor_wN(this.as_pos_int); |
|
jmp[0x20 | i] = cbor_wN(this.as_neg_int); |
|
jmp[0x40 | i] = cbor_wN(this.as_bytes); |
|
jmp[0x60 | i] = cbor_wN(this.as_utf8); |
|
jmp[0x80 | i] = cbor_wN(this.as_list); |
|
jmp[0xa0 | i] = cbor_wN(this.as_map); |
|
jmp[0xc0 | i] = cbor_wN(this.as_tag);} |
|
|
|
|
|
// streaming data types |
|
jmp[0x5f] = ctx => this.as_stream(ctx, ctx.types.bytes_stream()); |
|
jmp[0x7f] = ctx => this.as_stream(ctx, ctx.types.utf8_stream()); |
|
jmp[0x9f] = ctx => this.as_stream(ctx, ctx.types.list_stream()); |
|
jmp[0xbf] = ctx => this.as_pair_stream(ctx, ctx.types.map_stream()); |
|
|
|
// semantic tag |
|
|
|
// primitives |
|
jmp[0xf4] = () => false; |
|
jmp[0xf5] = () => true; |
|
jmp[0xf6] = () => null; |
|
jmp[0xf7] = () => {}; // undefined |
|
jmp[0xf8] = this.cbor_w1(this.as_simple_repr); |
|
jmp[0xf9] = this.as_float16; |
|
jmp[0xfa] = this.as_float32; |
|
jmp[0xfb] = this.as_float64; |
|
//jmp[0xfc] = undefined |
|
//jmp[0xfd] = undefined |
|
//jmp[0xfe] = undefined |
|
jmp[0xff] = () => cbor_break_sym; |
|
|
|
return jmp} |
|
|
|
|
|
, // simple values |
|
|
|
as_pos_int: (ctx, value) => value, |
|
as_neg_int: (ctx, value) => -1 - value, |
|
as_simple_repr: (ctx, key) => `simple(${key})`, |
|
|
|
bind_simple_dispatch(simple_lut) { |
|
if ('function' !== typeof simple_lut.get) { |
|
throw new TypeError('Expected a simple_value Map') } |
|
|
|
return (ctx, key) => simple_lut.get(key)} |
|
|
|
|
|
, build_tags_lut(tags) { |
|
let lut = new Map(); |
|
|
|
let q = [tags]; |
|
while (0 !== q.length) { |
|
let tip = q.pop(); |
|
|
|
if (Array.isArray(tip)) { |
|
q.push(... tip);} |
|
|
|
else if (tip[cbor_decode_sym]) { |
|
tip[cbor_decode_sym](lut, cbor_accum);} |
|
|
|
else if ('function' === typeof tip) { |
|
tip(lut, cbor_accum);} |
|
|
|
else { |
|
for (let [k,v] of tip.entries()) { |
|
lut.set(k,v);} } } |
|
|
|
return lut} |
|
|
|
|
|
, // Subclass responsibility: cbor size/value interpreters |
|
// cbor_tiny(as_type) :: return function w0_as(ctx, type_b) :: |
|
// cbor_w1(as_type) :: return function w1_as(ctx) :: |
|
// cbor_w2(as_type) :: return function w2_as(ctx) :: |
|
// cbor_w4(as_type) :: return function w4_as(ctx) :: |
|
// cbor_w8(as_type) :: return function w8_as(ctx) :: |
|
|
|
// Subclass responsibility: basic types |
|
// as_bytes(ctx, len) :: |
|
// as_utf8(ctx, len) :: |
|
// as_list(ctx, len) :: |
|
// as_map(ctx, len) :: |
|
|
|
// Subclass responsibility: streaming types |
|
// as_stream(ctx, accum) :: |
|
// as_pair_stream(ctx, accum) :: |
|
|
|
// Subclass responsibility: floating point primitives |
|
// as_float16(ctx) :: return ctx.types.float16(...) |
|
// as_float32(ctx) :: |
|
// as_float64(ctx) :: |
|
|
|
|
|
// Subclass responsibility: tag values |
|
};// bind_tag_dispatch(tags_lut) :: |
|
|
|
const _cbor_jmp_sync ={ |
|
__proto__: _cbor_jmp_base |
|
|
|
, // cbor size/value interpreters |
|
cbor_tiny(as_type) { |
|
return function w0_as(ctx, type_b) { |
|
return as_type(ctx, type_b & 0x1f) } } |
|
|
|
, cbor_w1(as_type) { |
|
return function w1_as(ctx) { |
|
const idx = ctx.move(1); |
|
return as_type(ctx, ctx.u8[idx]) } } |
|
|
|
, cbor_w2(as_type) { |
|
return function w2_as(ctx) { |
|
const u8 = ctx.u8, idx = ctx.move(2); |
|
return as_type(ctx, (u8[idx] << 8) | u8[idx+1]) } } |
|
|
|
, cbor_w4(as_type) { |
|
return function w4_as(ctx) { |
|
const u8 = ctx.u8, idx = ctx.move(4); |
|
return as_type(ctx, ctx.types.u32(u8, idx)) } } |
|
|
|
, cbor_w8(as_type) { |
|
return function w8_as(ctx) { |
|
const u8 = ctx.u8, idx = ctx.move(8); |
|
return as_type(ctx, ctx.types.u64(u8, idx)) } } |
|
|
|
|
|
, // basic types |
|
|
|
as_bytes(ctx, len) { |
|
const u8 = ctx.u8, idx = ctx.move(len); |
|
return ctx.types.bytes( |
|
u8.subarray(idx, idx + len)) } |
|
|
|
, as_utf8(ctx, len) { |
|
const u8 = ctx.u8, idx = ctx.move(len); |
|
return ctx.types.utf8( |
|
u8.subarray(idx, idx + len)) } |
|
|
|
, as_list(ctx, len) { |
|
const {res, accum, done} = ctx.types.list(len); |
|
for (let i=0; i<len; i++) { |
|
accum(res, i, ctx.next_value()); } |
|
|
|
return undefined !== done ? done(res) : res} |
|
|
|
, as_map(ctx, len) { |
|
const {res, accum, done} = ctx.types.map(len); |
|
for (let i=0; i<len; i++) { |
|
const key = ctx.next_value(); |
|
const value = ctx.next_value(); |
|
accum(res, key, value); } |
|
|
|
return undefined !== done ? done(res) : res} |
|
|
|
|
|
, // streaming |
|
|
|
as_stream(ctx, {res, accum, done}) { |
|
let i = 0; |
|
while (true) { |
|
const value = ctx.next_value(); |
|
if (cbor_break_sym === value) { |
|
return undefined !== done ? done(res) : res} |
|
|
|
accum(res, i++, value); } } |
|
|
|
, as_pair_stream(ctx, {res, accum, done}) { |
|
while (true) { |
|
const key = ctx.next_value(); |
|
if (cbor_break_sym === key) { |
|
return undefined !== done ? done(res) : res} |
|
|
|
accum(res, key, ctx.next_value()); } } |
|
|
|
|
|
, // floating point primitives |
|
|
|
as_float16(ctx) { |
|
const u8 = ctx.u8, idx = ctx.move(2); |
|
return ctx.types.float16( |
|
u8.subarray(idx, idx+2)) } |
|
|
|
, as_float32(ctx) { |
|
const u8 = ctx.u8, idx = ctx.move(4); |
|
return ctx.types.float32(u8, idx)} |
|
|
|
, as_float64(ctx) { |
|
const u8 = ctx.u8, idx = ctx.move(8); |
|
return ctx.types.float64(u8, idx)} |
|
|
|
|
|
, // tag values |
|
|
|
bind_tag_dispatch(tags_lut) { |
|
if ('function' !== typeof tags_lut.get) { |
|
throw new TypeError('Expected a tags Map') } |
|
|
|
return function(ctx, tag) { |
|
const tag_handler = tags_lut.get(tag); |
|
if (tag_handler) { |
|
let res = tag_handler(ctx, tag); |
|
if ('object' === typeof res) { |
|
return res.custom_tag(ctx, tag)} |
|
|
|
const body = ctx.next_value(); |
|
return undefined === res ? body : res(body)} |
|
|
|
return { |
|
__proto__: cbor_tagged_proto, |
|
tag, body: ctx.next_value()} } } }; |
|
|
|
class CBORDecoderBasic extends CBORDecoderBase { |
|
// decode(u8) :: |
|
static decode(u8) { |
|
return new this().decode(u8)} |
|
|
|
// *iter_decode(u8) :: |
|
static iter_decode(u8) { |
|
return new this().iter_decode(u8)} |
|
|
|
_bind_cbor_jmp(options, jmp) { |
|
return _cbor_jmp_sync.bind_jmp(options, jmp)} |
|
|
|
_bind_u8ctx(types, jmp, unknown) { |
|
return (this.U8DecodeCtx || U8SyncDecodeCtx) |
|
.subclass(types, jmp, unknown)} } |
|
|
|
CBORDecoderBasic.compile({ |
|
types: decode_types}); |
|
|
|
|
|
class CBORDecoder extends CBORDecoderBasic {} |
|
|
|
CBORDecoder.compile({ |
|
types: decode_types, |
|
tags: basic_tags(new Map()),}); |
|
|
|
const {decode, iter_decode} = new CBORDecoder(); |
|
|
|
export default decode; |
|
export { CBORDecoder, CBORDecoderBasic, _cbor_jmp_base, _cbor_jmp_sync, decode as cbor_decode, iter_decode as cbor_iter_decode, decode, iter_decode }; |