UNPKG

cbor

Version:

Encode and parse CBOR documents.

259 lines (224 loc) 6.74 kB
stream = require 'stream' url = require 'url' bignumber = require 'bignumber.js' BufferStream = require '../lib/BufferStream' Tagged = require '../lib/tagged' Simple = require '../lib/simple' constants = require './constants' # TODO: replace these with constants and add unit tests to verify MT = constants.MT NUM_BYTES = constants.NUM_BYTES TAG = constants.TAG SHIFT32 = Math.pow 2, 32 DOUBLE = (MT.SIMPLE_FLOAT<<5)|NUM_BYTES.EIGHT TRUE = (MT.SIMPLE_FLOAT<<5)|constants.SIMPLE.TRUE FALSE = (MT.SIMPLE_FLOAT<<5)|constants.SIMPLE.FALSE UNDEFINED = (MT.SIMPLE_FLOAT<<5)|constants.SIMPLE.UNDEFINED NULL = (MT.SIMPLE_FLOAT<<5)|constants.SIMPLE.NULL # A Readable stream of CBOR bytes. Call `write` to get JSON objects translated # into the stream. module.exports = class Encoder extends stream.Readable # Create an encoder # @param options [Object] options for the encoder # @option options genTypes [Array] array of pairs of `type`, `function(Encoder)` # for semantic types to be encoded. (default: [Array, arrayFunc, # Date, dateFunc, Buffer, bufferFunc, RegExp, regexFunc, # url.Url, urlFunc, bignumber, bignumberFunc] constructor: (options={})-> super options @bs = new BufferStream options @going = false @sendEOF = false @semanticTypes = [ Array, @_packArray Date, @_packDate Buffer, @_packBuffer RegExp, @_packRegexp url.Url, @_packUrl bignumber, @_packBigNumber ] addTypes = options.genTypes ? [] for typ,i in addTypes by 2 @addSemanticType typ, addTypes[i+1] # Add an encoding function to the list of supported semantic types. This is # useful for objects for which you can't add an encodeCBOR method # @return [function(Encoder)] if this type already exists in the semantic type # list, replace that function with the new one, and return the old one. # Return null if this is a new type. addSemanticType: (type, fun) -> for typ,i in @semanticTypes by 2 if typ == type [old, @semanticTypes[i+1]] = [@semanticTypes[i+1], fun] return old @semanticTypes.push type, fun null # @nodoc _read: (size)-> @going = true while @going x = @bs.read() if x.length @going = @push x else if @sendEOF @going = @push null break # @nodoc _packNaN: ()-> @bs.write 'f97e00', 'hex' # Half-NaN # @nodoc _packInfinity: (obj)-> half = if obj < 0 then 'f9fc00' else 'f97c00' @bs.write half, 'hex' # @nodoc _packFloat: (obj)-> # TODO: see if we can write smaller ones. @bs.writeUInt8 DOUBLE @bs.writeDoubleBE obj # @nodoc _packInt: (obj,mt)-> mt = mt << 5 switch when obj < 24 then @bs.writeUInt8 mt|obj when obj <= 0xff @bs.writeUInt8 mt|NUM_BYTES.ONE @bs.writeUInt8 obj when obj <= 0xffff @bs.writeUInt8 mt|NUM_BYTES.TWO @bs.writeUInt16BE obj when obj <= 0xffffffff @bs.writeUInt8 mt|NUM_BYTES.FOUR @bs.writeUInt32BE obj when obj < 0x20000000000000 @bs.writeUInt8 mt|NUM_BYTES.EIGHT @bs.writeUInt32BE Math.floor(obj / SHIFT32) @bs.writeUInt32BE (obj % SHIFT32) else @_packFloat obj # @nodoc _packNumber: (obj)-> switch when isNaN(obj) then @_packNaN obj when !isFinite(obj) then @_packInfinity obj when Math.round(obj) == obj #int if obj<0 @_packInt -obj-1, MT.NEG_INT else @_packInt obj, MT.POS_INT else @_packFloat obj # @nodoc _packString: (obj)-> len = Buffer.byteLength obj, 'utf8' @_packInt len, MT.UTF8_STRING @bs.writeString obj, len, 'utf8' # @nodoc _packBoolean: (obj)-> @bs.writeUInt8 if obj then TRUE else FALSE # @nodoc _packUndefined: (obj)-> @bs.writeUInt8 UNDEFINED # @nodoc _packArray: (gen, obj)-> len = obj.length @_packInt len, MT.ARRAY for x in obj @_pack x # @nodoc _packTag: (tag)-> @_packInt tag, MT.TAG # @nodoc _packDate: (gen, obj)-> @_packTag TAG.DATE_EPOCH @_pack obj / 1000 # @nodoc _packBuffer: (gen, obj)-> @_packInt obj.length, MT.BYTE_STRING @bs.append obj # @nodoc _packRegexp: (gen, obj)-> @_packTag TAG.REGEXP @_pack obj.source # @nodoc _packUrl: (gen, obj)-> @_packTag TAG.URI @_pack obj.format() # @nodoc _packBigint: (obj)-> if obj.isNegative() obj = obj.negated().minus(1) tag = TAG.NEG_BIGINT else tag = TAG.POS_BIGINT str = obj.toString(16) if str.length % 2 str = '0'+str buf = new Buffer str, 'hex' @_packTag tag @_packBuffer this, buf, @bs # @nodoc _packBigNumber: (gen, obj)-> if obj.isNaN() return @_packNaN() unless obj.isFinite() return @_packInfinity if obj.isNegative() then -Infinity else Infinity # if integer, just write a bigint. if obj.c.length < (obj.e + 2) return @_packBigint obj @_packTag TAG.DECIMAL_FRAC @_packInt 2, MT.ARRAY slide = new bignumber obj @_packInt slide.e, MT.POS_INT slide.e = slide.c.length - 1 @_packBigint slide # @nodoc _packObject: (obj)-> unless obj then return @bs.writeUInt8 NULL for typ,i in @semanticTypes by 2 if obj instanceof typ return @semanticTypes[i+1].call(this, this, obj) f = obj.encodeCBOR if typeof f == 'function' return f.call(obj, this) keys = Object.keys obj len = keys.length @_packInt len, MT.MAP for k in keys @_pack k @_pack obj[k] # @nodoc _pack: (obj)-> switch typeof(obj) when 'number' then @_packNumber obj when 'string' then @_packString obj when 'boolean' then @_packBoolean obj when 'undefined' then @_packUndefined obj when 'object' then @_packObject obj else # e.g. function throw new Error('Unknown type: ' + typeof(obj)); # Encode one or more JavaScript objects into the stream. # @param objs... [Object+] the objects to encode write: (objs...)-> for o in objs @_pack o if @going x = @bs.read() if x.length @going = @push x # Encode zero or more JavaScript objects into the stream, then end the stream. # @param objs... [Object*] the objects to encode end: (objs...)-> if objs.length then @write objs... if @going # assert.ok(@bs.length == 0) @going = @push null else @sendEOF = true # Encode one or more JavaScript objects, and return a Buffer containing the # CBOR bytes. # @param objs... [Object+] the objects to encode @encode: (objs...)-> g = new Encoder g.end objs... g.read()