UNPKG

protobuf.js

Version:

a pure javascript protocol buffer encoding implementation, written specifically for riak

308 lines (264 loc) 10.2 kB
var long = require('long'); var varint = require('./lib/varint'); function Protobuf(schema) { this.schema = schema; } Protobuf.prototype._findField = function (message, tag) { var field, key; for (key in this.schema.messages[message].fields) { if (this.schema.messages[message].fields[key].tag === tag) { field = this.schema.messages[message].fields[key]; field.name = key; } } return field; }; Protobuf.prototype.decode = function (message, data) { if (!Buffer.isBuffer(data)) return new Error('Data must be a buffer'); if (!this.schema.messages[message]) return new Error('Unknown message'); var mc, type, tag, field, value, repeated, meta, low, high; var enums = this.schema.messages[message].enums; var position = 0; var length = data.length; var result = {}; while (position < length) { mc = varint.read(data, position); type = mc.value & 0x07; tag = mc.value >> 3; position += mc.length; field = this._findField(message, tag); if (!field) return new Error('Encountered unknown message tag'); repeated = field.rule === 'repeated'; if (!result.hasOwnProperty(field.name) && repeated) result[field.name] = []; switch (field.type) { case 'uint32': case 'bool': // read varint value = varint.read(data, position); position += value.length; value = value.value; // coerce to boolean if correct if (field.type === 'bool') { value = Boolean(value); } break; case 'int32': value = varint.read(data, position, true); position += value.length; value = value.value; break; case 'sint32': // read zigzag encoded varint value = varint.read(data, position, true); position += value.length; value = varint.dezigzag(value.value); break; case 'uint64': // read 64 bit varint value = varint.read64(data, position); position += value.length; value = value.value; break; case 'int64': value = varint.read64(data, position, true); position += value.length; value = value.value; break; case 'sint64': // read zigzag encoded 64 bit varint value = varint.read64(data, position, true); position += value.length; value = varint.dezigzag64(value.value); break; case 'fixed64': case 'sfixed64': case 'double': // read 64 bit number low = data.readUInt32LE(position); high = data.readUInt32LE(position + 4); value = new long(low, high, field.type !== 'sfixed64'); position += 8; break; case 'bytes': case 'string': // read raw bytes meta = varint.read(data, position); position += meta.length; value = new Buffer(meta.value); data.copy(value, 0, position, position + meta.value); position += meta.value; // stringify raw bytes if string if (field.type === 'string') value = value.toString(); break; case 'fixed32': case 'sfixed32': case 'float': // read 32 bit number value = data.readInt32LE(position); position += 4; break; default: // check if it's an enum if (enums && enums[field.type]) { value = varint.read(data, position); position += value.length; value = value.value; } else { // decode embedded message meta = varint.read(data, position); position += meta.length; value = this.decode(field.type, data.slice(position, position + meta.value)); position += meta.value; } break; } if (repeated) { result[field.name].push(value); } else { result[field.name] = value; } } return result; }; Protobuf.prototype.encode = function (message, data, preserve) { if (!this.schema.messages[message]) return new Error('Unknown message'); var self = this; var repeated, value; var result = null; var position = 0; var fields = this.schema.messages[message].fields; var enums = this.schema.messages[message].enums; function encodeField(key, item) { if (item === undefined) { return; } switch (fields[key].type) { case 'int32': case 'uint32': case 'sint32': case 'bool': if (fields[key].type === 'bool') { value = Number(!!item); } else if (fields[key].type === 'sint32') { value = varint.zigzag(item); } else { value = item; } position += varint.write(result, fields[key].tag << 3, position); position += varint.write(result, value, position); break; case 'int64': case 'uint64': case 'sint64': if (typeof item === 'number') { value = long.fromNumber(item, fields[key].type === 'uint64'); } else if (typeof item === 'string') { value = long.fromString(item, fields[key].type === 'uint64'); } else { value = item; } if (fields[key].type === 'sint64') { value = varint.zigzag64(value); } position += varint.write(result, fields[key].tag << 3, position); position += varint.write64(result, value, position); break; case 'fixed64': case 'sfixed64': case 'double': if (typeof item === 'number') { item = long.fromNumber(item, fields[key].type !== 'sfixed64'); } else if (typeof item === 'string') { item = long.fromString(item, fields[key].type !== 'sfixed64'); } position += varint.write(result, (fields[key].tag << 3) + 1, position); if (fields[key].type === 'sfixed64') { if (result) { result.writeInt32LE(item.getLowBitsUnsigned(), position); } position += 4; if (result) { result.writeInt32LE(item.getHighBitsUnsigned(), position); } position += 4; } else { if (result) { result.writeInt32LE(item.getLowBits(), position); } position += 4; if (result) { result.writeInt32LE(item.getHighBits(), position); } position += 4; } break; case 'fixed32': case 'sfixed32': case 'float': if (typeof item !== 'number') { item = Number(item); } position += varint.write(result, (fields[key].tag << 3) + 5, position); if (result) { result.writeInt32LE(item, position); } position += 4; break; case 'bytes': case 'string': if (!Buffer.isBuffer(item)) { if (typeof item !== 'string') { item = String(item); } value = new Buffer(item, 'utf8'); } else { value = item; } position += varint.write(result, (fields[key].tag << 3) + 2, position); position += varint.write(result, value.length, position); if (result) { value.copy(result, position); } position += value.length; break; default: if (enums && enums[fields[key].type]) { value = item; position += varint.write(result, fields[key].tag << 3, position); position += varint.write(result, value, position); } else { value = self.encode(fields[key].type, item); position += varint.write(result, (fields[key].tag << 3) + 2, position); position += varint.write(result, value.length, position); if (result) { value.copy(result, position); } position += value.length; } break; } } function walkFields() { var key; for (key in data) { if (!fields[key]) return new Error('Unknown field'); repeated = fields[key].rule === 'repeated'; if (repeated) { if (!Array.isArray(data[key])) { data[key] = [data[key]]; } data[key].forEach(function (item) { encodeField(key, item); }); } else { encodeField(key, data[key]); } } } walkFields(); result = new Buffer(position); position = 0; walkFields(); return result; }; module.exports = Protobuf;