diffusion
Version:
Diffusion JavaScript client
274 lines (231 loc) • 7.54 kB
JavaScript
var BufferOutputStream = require('io/buffer-output-stream');
var Int64Impl = require('data/primitive/int64-impl');
var consts = require('cbor/consts');
var additional = consts.additional,
types = consts.types;
function Encoder(initial) {
var bos = initial || new BufferOutputStream();
var self = this;
function writeByte(b) {
bos.write(b);
}
function writeBuffer(buf, offset, length) {
offset = offset || 0;
length = length === undefined ? buf.length : length;
bos.writeMany(buf, offset, length);
}
function writeUint16(v) {
writeByte((v >> 8) & 0xff);
writeByte(v & 0xff);
}
function writeUint32(v) {
writeUint16((v >> 16) & 0xffff);
writeUint16(v & 0xffff);
}
function writeUint64(v) {
writeUint32(Math.floor(v / 4294967296));
writeUint32(v % 4294967296);
}
function writeBreakHeader(type) {
writeByte(type << 5 | additional.BREAK);
}
function writeToken(type, val) {
var first = type << 5;
if (val < 24) {
writeByte(first | val);
} else if (val < 256) {
writeByte(first | 24);
writeByte(val);
} else if (val < 65536) {
writeByte(first | 25);
writeUint16(val);
} else if (val < 4294967296) {
writeByte(first | 26);
writeUint32(val);
} else {
writeByte(first | 27);
writeUint64(val);
}
}
function writeNumber(val) {
// Determine if the number is int or not
if (val.toString().indexOf(".") > -1) {
// Because Numbers are 53 bits, always write non-integers as doubles
// Can't bit-twiddle to figure out how to encode, as bitwise operations
// will coerce to a 32-bit integer under the hood.
var buf = new Buffer(8);
buf.writeDoubleBE(val, 0);
writeByte((types.SIMPLE << 5) | additional.DOUBLE_PRECISION);
writeBuffer(buf);
} else {
if (val < 0) {
writeToken(types.INT, -1 - val);
} else {
writeToken(types.UINT, val);
}
}
}
function writeString(val) {
var buf = new Buffer(val, 'utf-8');
writeToken(types.STRING, buf.length);
writeBuffer(buf);
}
function writeBinary(val, offset, length) {
length = length === undefined ? val.length : length;
writeToken(types.BYTES, length);
writeBuffer(val, offset, length);
}
function writeBoolean(val) {
if (val) {
writeToken(types.SIMPLE, additional.TRUE);
} else {
writeToken(types.SIMPLE, additional.FALSE);
}
}
function writeUndefined() {
writeToken(types.SIMPLE, additional.UNDEFINED);
}
function writeNull() {
writeToken(types.SIMPLE, additional.NULL);
}
function writeObject(val) {
var keys = Object.keys(val);
self.startObject(keys.length);
for (var i = 0; i < keys.length; ++i) {
self.encode(keys[i]);
self.encode(val[keys[i]]);
}
}
function writeArray(val) {
self.startArray(val.length);
for (var i = 0; i < val.length; ++i) {
self.encode(val[i]);
}
}
/**
* Write a BREAK stop code. This is not constrained by any pre-written strucutural context.
*
* @returns {Encoder} This encoder
*/
this.break = function () {
writeBreakHeader(types.SIMPLE);
return self;
};
/**
* Write the header for an array.
* <P>
* Data may be subsequently written. No constraints are applied to subsequent data, i.e the header for a
* fixed-length array may be immediately followed by a break flag
*
* @param {Number} [length] - The length of the array. If not provided, this array will be considered
* indefinite-length
*
* @returns {Encoder} This encoder
*/
this.startArray = function (length) {
if (length === undefined) {
writeBreakHeader(types.ARRAY);
} else {
writeToken(types.ARRAY, length);
}
return self;
};
/**
* Write the header for an object.
* <P>
* Data may be subsequently written. No constraints are applied to subsequent data, i.e the header for a
* fixed-length object may be immediately followed by a break flag
*
* @param {Number} [length] - The number of keys in the object. If not provided, this object will be considered
* indefinite-length
*
* @returns {Encoder} This encoder
*/
this.startObject = function (length) {
if (length === undefined) {
writeBreakHeader(types.MAP);
} else {
writeToken(types.MAP, length);
}
return self;
};
/**
* Write an {@link Int64Impl} value.
*
* @param {Int64Impl} val - the int64 value to encode
* @returns {Encoder} this encoder
*/
this.writeInt64 = function(val) {
if (val.isNegative()) {
writeByte((types.INT << 5) | 27);
} else {
writeByte((types.UINT << 5) | 27);
}
writeBuffer(val.toBuffer());
return self;
};
/** Write a {@link Double} value.
*
* @param {Double} val - the double value to encode
* @return {Encoder} this encoder
*/
this.writeDouble = function(val) {
var buf = new Buffer(8);
buf.writeDoubleBE(val, 0);
writeByte((types.SIMPLE << 5) | additional.DOUBLE_PRECISION);
writeBuffer(buf);
return self;
};
/**
* Encode a value to a CBOR representation.
* <P>
* If a buffer is provided, optional offset & length parameters may be provided to
* dictate which bytes are to be copied.
*
* @param {*} value - The value to encode
* @param {Number} [offset=0] - The offset to use if encoding a buffer
* @param {Number} [length=value.length] - The length to use if encoding a buffer
*
* @returns {Encoder} This encoder
*/
this.encode = function (value, offset, length) {
if (value instanceof Date) {
writeString(value.toJSON());
}
else if (value === null) {
writeNull();
} else if (value === undefined) {
writeUndefined();
} else if (value === true) {
writeBoolean(true);
} else if (value === false) {
writeBoolean(false);
} else if (Int64Impl.isPrototypeOf(value)) {
self.writeInt64(value);
} else {
switch (typeof value) {
case 'string' :
writeString(value);
break;
case 'number' :
writeNumber(value);
break;
case 'object' :
if (Buffer.isBuffer(value)) {
writeBinary(value, offset, length);
} else if (Array.isArray(value)) {
writeArray(value);
} else {
writeObject(value);
}
}
}
return self;
};
this.flush = function () {
var res = bos.getBuffer();
bos = new BufferOutputStream();
return res;
};
}
module.exports = Encoder;