UNPKG

ndn-js

Version:

A JavaScript client library for Named Data Networking

254 lines (233 loc) 9.36 kB
/** * Copyright (C) 2014-2019 Regents of the University of California. * @author: Jeff Thompson <jefft0@remap.ucla.edu> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * A copy of the GNU Lesser General Public License is in the file COPYING. */ /** @ignore */ var TlvEncoder = require('./tlv/tlv-encoder.js').TlvEncoder; /** @ignore */ var TlvDecoder = require('./tlv/tlv-decoder.js').TlvDecoder; /** @ignore */ var Blob = require('../util/blob.js').Blob; /** @ignore */ var Name = require('../name.js').Name; /** @ignore */ /** * ProtobufTlv has static methods to encode and decode an Protobuf Message * object as NDN-TLV. The Protobuf tag value is used as the TLV type code. A * Protobuf message is encoded/decoded as a nested TLV encoding. Protobuf types * uint32, uint64 and enum are encoded/decoded as TLV nonNegativeInteger. (It is * an error if an enum value is negative.) Protobuf types bytes and string are * encoded/decoded as TLV bytes. The Protobuf type bool is encoded/decoded as a * TLV boolean (a zero length value for True, omitted for False). The Protobuf * type double is encoded/decoded as an 8-byte little-endian IEEE 754 double. * Other Protobuf types are an error. * * Protobuf has no "outer" message type, so you need to put your TLV message * inside an outer "typeless" message. * @constructor */ var ProtobufTlv = function ProtobufTlv() { }; exports.ProtobufTlv = ProtobufTlv; // Load ProtoBuf.Reflect.Message.Field dynamically so that protobufjs is optional. ProtobufTlv._Field = null; ProtobufTlv.establishField = function() { if (ProtobufTlv._Field === null) { try { // Using protobuf.min.js in the browser. ProtobufTlv._Field = dcodeIO.ProtoBuf.Reflect.Message.Field; } catch (ex) { // Using protobufjs in node. ProtobufTlv._Field = require("protobufjs").Reflect.Message.Field; } } } /** * Encode the Protobuf message object as NDN-TLV. This calls * message.encodeAB() to ensure that all required fields are present and * raises an exception if not. (This does not use the result of toArrayBuffer().) * @param {ProtoBuf.Builder.Message} message The Protobuf message object. * @param {ProtoBuf.Reflect.T} descriptor The reflection descriptor for the * message. For example, if the message is of type "MyNamespace.MyMessage" then * the descriptor is builder.lookup("MyNamespace.MyMessage"). * @return {Blob} The encoded buffer in a Blob object. */ ProtobufTlv.encode = function(message, descriptor) { ProtobufTlv.establishField(); message.encodeAB(); var encoder = new TlvEncoder(); ProtobufTlv._encodeMessageValue(message, descriptor, encoder); return new Blob(encoder.getOutput(), false); }; /** * Decode the input as NDN-TLV and update the fields of the Protobuf message * object. * @param {ProtoBuf.Builder.Message} message The Protobuf message object. This * does not first clear the object. * @param {ProtoBuf.Reflect.T} descriptor The reflection descriptor for the * message. For example, if the message is of type "MyNamespace.MyMessage" then * the descriptor is builder.lookup("MyNamespace.MyMessage"). * @param {Blob|Buffer} input The buffer with the bytes to decode. */ ProtobufTlv.decode = function(message, descriptor, input) { ProtobufTlv.establishField(); // If input is a blob, get its buf(). var decodeBuffer = typeof input === 'object' && input instanceof Blob ? input.buf() : input; var decoder = new TlvDecoder(decodeBuffer); ProtobufTlv._decodeMessageValue (message, descriptor, decoder, decodeBuffer.length); }; ProtobufTlv._encodeMessageValue = function(message, descriptor, encoder) { var fields = descriptor.getChildren(ProtobufTlv._Field); // Encode the fields backwards. for (var iField = fields.length - 1; iField >= 0; --iField) { var field = fields[iField]; var tlvType = field.id; var values; if (field.repeated) values = message[field.name]; else { if (message[field.name] != null) // Make a singleton list. values = [message[field.name]]; else continue; } // Encode the values backwards. for (var iValue = values.length - 1; iValue >= 0; --iValue) { var value = values[iValue]; if (field.type.name == "message") { var saveLength = encoder.getLength(); // Encode backwards. ProtobufTlv._encodeMessageValue(value, field.resolvedType, encoder); encoder.writeTypeAndLength(tlvType, encoder.getLength() - saveLength); } else if (field.type.name == "uint32" || field.type.name == "uint64") encoder.writeNonNegativeIntegerTlv(tlvType, value); else if (field.type.name == "enum") { if (value < 0) throw new Error("ProtobufTlv.encode: ENUM value may not be negative"); encoder.writeNonNegativeIntegerTlv(tlvType, value); } else if (field.type.name == "bytes") { var buffer = value.toBuffer(); if (buffer.length == undefined) // We are not running in Node.js, so assume we are using the dcodeIO // browser implementation based on ArrayBuffer. buffer = new Uint8Array(value.toArrayBuffer()); encoder.writeBlobTlv(tlvType, buffer); } else if (field.type.name == "string") // Use Blob to convert. encoder.writeBlobTlv(tlvType, new Blob(value, false).buf()); else if (field.type.name == "bool") { if (value) encoder.writeTypeAndLength(tlvType, 0); } else if (field.type.name == "double") { var encoding = new Buffer(8); encoding.writeDoubleLE(value, 0); encoder.writeBlobTlv(tlvType, encoding); } else throw new Error("ProtobufTlv.encode: Unknown field type"); } } }; ProtobufTlv._decodeMessageValue = function(message, descriptor, decoder, endOffset) { var fields = descriptor.getChildren(ProtobufTlv._Field); for (var iField = 0; iField < fields.length; ++iField) { var field = fields[iField]; var tlvType = field.id; if (!field.required && !decoder.peekType(tlvType, endOffset)) continue; if (field.repeated) { while (decoder.peekType(tlvType, endOffset)) { if (field.type.name == "message") { var innerEndOffset = decoder.readNestedTlvsStart(tlvType); var value = new (field.resolvedType.build())(); message.add(field.name, value); ProtobufTlv._decodeMessageValue (value, field.resolvedType, decoder, innerEndOffset); decoder.finishNestedTlvs(innerEndOffset); } else message.add (field.name, ProtobufTlv._decodeFieldValue(field, tlvType, decoder, endOffset)); } } else { if (field.type.name == "message") { var innerEndOffset = decoder.readNestedTlvsStart(tlvType); var value = new (field.resolvedType.build())(); message.set(field.name, value); ProtobufTlv._decodeMessageValue (value, field.resolvedType, decoder, innerEndOffset); decoder.finishNestedTlvs(innerEndOffset); } else message.set (field.name, ProtobufTlv._decodeFieldValue(field, tlvType, decoder, endOffset)); } } }; /** * This is a helper for _decodeMessageValue. Decode a single field and return * the value. Assume the field.type.name is not "message". */ ProtobufTlv._decodeFieldValue = function(field, tlvType, decoder, endOffset) { if (field.type.name == "uint32" || field.type.name == "uint64" || field.type.name == "enum") return decoder.readNonNegativeIntegerTlv(tlvType); else if (field.type.name == "bytes") return decoder.readBlobTlv(tlvType); else if (field.type.name == "string") return decoder.readBlobTlv(tlvType).toString(); else if (field.type.name == "bool") return decoder.readBooleanTlv(tlvType, endOffset); else if (field.type.name == "double") return decoder.readBlobTlv(tlvType).readDoubleLE(0); else throw new Error("ProtobufTlv.decode: Unknown field type"); }; /** * Return a Name made from the component array in a Protobuf message object, * assuming that it was defined with "repeated bytes". For example: * message Name { * repeated bytes component = 8; * } * @param {Array} componentArray The array from the Protobuf message object * representing the "repeated bytes" component array. * @return A new Name. */ ProtobufTlv.toName = function(componentArray) { var name = new Name(); for (var i = 0; i < componentArray.length; ++i) name.append (new Blob(new Buffer(componentArray[i].toBinary(), "binary"), false)); return name; };