fcoin/lib/utils/protobuf.js
2017-02-03 22:47:26 -08:00

357 lines
7.3 KiB
JavaScript

/*!
* protobuf.js - protobufs for bcoin
* Copyright (c) 2016-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
/**
* @module utils/protobuf
*/
var util = require('../utils/util');
var assert = require('assert');
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
/*
* Constants
*/
var wireType = {
VARINT: 0,
FIXED64: 1,
DELIMITED: 2,
START_GROUP: 3,
END_GROUP: 4,
FIXED32: 5
};
/**
* ProtoReader
* @constructor
*/
function ProtoReader(data, zeroCopy) {
if (!(this instanceof ProtoReader))
return new ProtoReader(data, zeroCopy);
BufferReader.call(this, data, zeroCopy);
}
util.inherits(ProtoReader, BufferReader);
ProtoReader.prototype.readVarint = function readVarint() {
var result = exports.readVarint(this.data, this.offset);
this.offset += result.size;
return result.value;
};
ProtoReader.prototype.readFieldValue = function readFieldValue(tag, opt) {
var field = this.readField(tag, opt);
if (!field)
return -1;
assert(field.value != null);
return field.value;
};
ProtoReader.prototype.readFieldU64 = function readFieldU64(tag, opt) {
var field = this.readField(tag, opt);
if (!field)
return -1;
assert(field.type === wireType.VARINT || field.type === wireType.FIXED64);
return field.value;
};
ProtoReader.prototype.readFieldU32 = function readFieldU32(tag, opt) {
var field = this.readField(tag, opt);
if (!field)
return -1;
assert(field.type === wireType.VARINT || field.type === wireType.FIXED32);
return field.value;
};
ProtoReader.prototype.readFieldBytes = function readFieldBytes(tag, opt) {
var field = this.readField(tag, opt);
if (!field)
return null;
assert(field.data);
return field.data;
};
ProtoReader.prototype.readFieldString = function readFieldString(tag, opt, enc) {
var field = this.readField(tag, opt);
if (!field)
return null;
assert(field.data);
return field.data.toString(enc || 'utf8');
};
ProtoReader.prototype.nextTag = function nextTag() {
var field;
if (this.left() === 0)
return -1;
field = this.readField();
this.seek(-field.size);
return field.tag;
};
ProtoReader.prototype.readField = function readField(tag, opt) {
var offset = this.offset;
var header = this.readVarint();
var field = new Field(header);
var inner;
if (tag != null && field.tag !== tag) {
assert(opt, 'Non-optional field not present.');
this.offset = offset;
return null;
}
switch (field.type) {
case wireType.VARINT:
field.value = this.readVarint();
break;
case wireType.FIXED64:
field.value = this.readU64();
break;
case wireType.DELIMITED:
field.data = this.readVarBytes();
break;
case wireType.START_GROUP:
field.group = [];
for (;;) {
inner = this.readField();
if (inner.type === wireType.END_GROUP)
break;
field.group.push(inner);
}
break;
case wireType.END_GROUP:
assert(false, 'Unexpected end group.');
break;
case wireType.FIXED32:
field.value = this.readU32();
break;
default:
assert(false, 'Bad wire type.');
break;
}
field.size = this.offset - offset;
return field;
};
/**
* ProtoWriter
* @constructor
*/
function ProtoWriter() {
if (!(this instanceof ProtoWriter))
return new ProtoWriter();
BufferWriter.call(this);
}
util.inherits(ProtoWriter, BufferWriter);
ProtoWriter.prototype.writeVarint = function writeVarint(num) {
var size = exports.sizeVarint(num);
var value;
// Avoid an extra allocation until
// we make bufferwriter more hackable.
// More insanity here...
switch (size) {
case 6:
value = exports.slipVarint(num);
this.writeU32BE(value / 0x10000 | 0);
this.writeU16BE(value & 0xffff);
break;
case 5:
value = exports.slipVarint(num);
this.writeU32BE(value / 0x100 | 0);
this.writeU8(value & 0xff);
break;
case 4:
value = exports.slipVarint(num);
this.writeU32BE(value);
break;
case 3:
value = exports.slipVarint(num);
this.writeU16BE(value >> 8);
this.writeU8(value & 0xff);
break;
case 2:
value = exports.slipVarint(num);
this.writeU16BE(value);
break;
case 1:
value = exports.slipVarint(num);
this.writeU8(value);
break;
default:
value = new Buffer(size);
exports.writeVarint(value, num, 0);
this.writeBytes(value);
break;
}
};
ProtoWriter.prototype.writeFieldVarint = function writeFieldVarint(tag, value) {
var header = (tag << 3) | wireType.VARINT;
this.writeVarint(header);
this.writeVarint(value);
};
ProtoWriter.prototype.writeFieldU64 = function writeFieldU64(tag, value) {
assert(util.isSafeInteger(value));
this.writeFieldVarint(tag, value);
};
ProtoWriter.prototype.writeFieldU32 = function writeFieldU32(tag, value) {
assert(value <= 0xffffffff);
this.writeFieldVarint(tag, value);
};
ProtoWriter.prototype.writeFieldBytes = function writeFieldBytes(tag, data) {
var header = (tag << 3) | wireType.DELIMITED;
this.writeVarint(header);
this.writeVarint(data.length);
this.writeBytes(data);
};
ProtoWriter.prototype.writeFieldString = function writeFieldString(tag, data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc || 'utf8');
this.writeFieldBytes(tag, data);
};
/*
* Encoding
*/
exports.readVarint = function readVarint(data, off) {
var num = 0;
var ch = 0x80;
var size = 0;
while (ch & 0x80) {
if (off >= data.length) {
num = 0;
break;
}
ch = data[off++];
// Optimization for javascript insanity.
switch (size) {
case 0:
case 1:
case 2:
case 3:
num += (ch & 0x7f) << (7 * size);
break;
case 4:
num += (ch & 0x7f) * (1 << (7 * size));
break;
default:
num += (ch & 0x7f) * Math.pow(2, 7 * size);
break;
}
size++;
assert(size < 7, 'Number exceeds 2^53-1.');
}
return new Varint(size, num);
};
exports.writeVarint = function writeVarint(data, num, off) {
var ch;
assert(util.isSafeInteger(num), 'Number exceeds 2^53-1.');
do {
assert(off < data.length);
ch = num & 0x7f;
num -= num % 0x80;
num /= 0x80;
if (num !== 0)
ch |= 0x80;
data[off] = ch;
off++;
} while (num > 0);
return off;
};
exports.slipVarint = function slipVarint(num) {
var data = 0;
var size = 0;
var ch;
assert(util.isSafeInteger(num), 'Number exceeds 2^53-1.');
do {
assert(size < 7);
ch = num & 0x7f;
num -= num % 0x80;
num /= 0x80;
if (num !== 0)
ch |= 0x80;
data *= 256;
data += ch;
size++;
} while (num > 0);
return data;
};
exports.sizeVarint = function sizeVarint(num) {
var size = 0;
assert(util.isSafeInteger(num), 'Number exceeds 2^53-1.');
do {
num -= num % 0x80;
num /= 0x80;
size++;
} while (num > 0);
return size;
};
/*
* Helpers
*/
function Field(header) {
this.tag = header >>> 3;
this.type = header & 7;
this.size = 0;
this.value = 0;
this.data = null;
this.group = null;
}
function Varint(size, value) {
this.size = size;
this.value = value;
}
/*
* Expose
*/
exports.ProtoReader = ProtoReader;
exports.ProtoWriter = ProtoWriter;