Source: encoding/protobuf-tlv.js

  1. /**
  2. * Copyright (C) 2014-2018 Regents of the University of California.
  3. * @author: Jeff Thompson <jefft0@remap.ucla.edu>
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Lesser General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. * A copy of the GNU Lesser General Public License is in the file COPYING.
  18. */
  19. /** @ignore */
  20. var TlvEncoder = require('./tlv/tlv-encoder.js').TlvEncoder; /** @ignore */
  21. var TlvDecoder = require('./tlv/tlv-decoder.js').TlvDecoder; /** @ignore */
  22. var Blob = require('../util/blob.js').Blob; /** @ignore */
  23. var Name = require('../name.js').Name; /** @ignore */
  24. /**
  25. * ProtobufTlv has static methods to encode and decode an Protobuf Message
  26. * object as NDN-TLV. The Protobuf tag value is used as the TLV type code. A
  27. * Protobuf message is encoded/decoded as a nested TLV encoding. Protobuf types
  28. * uint32, uint64 and enum are encoded/decoded as TLV nonNegativeInteger. (It is
  29. * an error if an enum value is negative.) Protobuf types bytes and string are
  30. * encoded/decoded as TLV bytes. The Protobuf type bool is encoded/decoded as a
  31. * TLV boolean (a zero length value for True, omitted for False). The Protobuf
  32. * type double is encoded/decoded as an 8-byte little-endian IEEE 754 double.
  33. * Other Protobuf types are an error.
  34. *
  35. * Protobuf has no "outer" message type, so you need to put your TLV message
  36. * inside an outer "typeless" message.
  37. * @constructor
  38. */
  39. var ProtobufTlv = function ProtobufTlv()
  40. {
  41. };
  42. exports.ProtobufTlv = ProtobufTlv;
  43. // Load ProtoBuf.Reflect.Message.Field dynamically so that protobufjs is optional.
  44. ProtobufTlv._Field = null;
  45. ProtobufTlv.establishField = function()
  46. {
  47. if (ProtobufTlv._Field === null) {
  48. try {
  49. // Using protobuf.min.js in the browser.
  50. ProtobufTlv._Field = dcodeIO.ProtoBuf.Reflect.Message.Field;
  51. }
  52. catch (ex) {
  53. // Using protobufjs in node.
  54. ProtobufTlv._Field = require("protobufjs").Reflect.Message.Field;
  55. }
  56. }
  57. }
  58. /**
  59. * Encode the Protobuf message object as NDN-TLV. This calls
  60. * message.encodeAB() to ensure that all required fields are present and
  61. * raises an exception if not. (This does not use the result of toArrayBuffer().)
  62. * @param {ProtoBuf.Builder.Message} message The Protobuf message object.
  63. * @param {ProtoBuf.Reflect.T} descriptor The reflection descriptor for the
  64. * message. For example, if the message is of type "MyNamespace.MyMessage" then
  65. * the descriptor is builder.lookup("MyNamespace.MyMessage").
  66. * @return {Blob} The encoded buffer in a Blob object.
  67. */
  68. ProtobufTlv.encode = function(message, descriptor)
  69. {
  70. ProtobufTlv.establishField();
  71. message.encodeAB();
  72. var encoder = new TlvEncoder();
  73. ProtobufTlv._encodeMessageValue(message, descriptor, encoder);
  74. return new Blob(encoder.getOutput(), false);
  75. };
  76. /**
  77. * Decode the input as NDN-TLV and update the fields of the Protobuf message
  78. * object.
  79. * @param {ProtoBuf.Builder.Message} message The Protobuf message object. This
  80. * does not first clear the object.
  81. * @param {ProtoBuf.Reflect.T} descriptor The reflection descriptor for the
  82. * message. For example, if the message is of type "MyNamespace.MyMessage" then
  83. * the descriptor is builder.lookup("MyNamespace.MyMessage").
  84. * @param {Blob|Buffer} input The buffer with the bytes to decode.
  85. */
  86. ProtobufTlv.decode = function(message, descriptor, input)
  87. {
  88. ProtobufTlv.establishField();
  89. // If input is a blob, get its buf().
  90. var decodeBuffer = typeof input === 'object' && input instanceof Blob ?
  91. input.buf() : input;
  92. var decoder = new TlvDecoder(decodeBuffer);
  93. ProtobufTlv._decodeMessageValue
  94. (message, descriptor, decoder, decodeBuffer.length);
  95. };
  96. ProtobufTlv._encodeMessageValue = function(message, descriptor, encoder)
  97. {
  98. var fields = descriptor.getChildren(ProtobufTlv._Field);
  99. // Encode the fields backwards.
  100. for (var iField = fields.length - 1; iField >= 0; --iField) {
  101. var field = fields[iField];
  102. var tlvType = field.id;
  103. var values;
  104. if (field.repeated)
  105. values = message[field.name];
  106. else {
  107. if (message[field.name] != null)
  108. // Make a singleton list.
  109. values = [message[field.name]];
  110. else
  111. continue;
  112. }
  113. // Encode the values backwards.
  114. for (var iValue = values.length - 1; iValue >= 0; --iValue) {
  115. var value = values[iValue];
  116. if (field.type.name == "message") {
  117. var saveLength = encoder.getLength();
  118. // Encode backwards.
  119. ProtobufTlv._encodeMessageValue(value, field.resolvedType, encoder);
  120. encoder.writeTypeAndLength(tlvType, encoder.getLength() - saveLength);
  121. }
  122. else if (field.type.name == "uint32" ||
  123. field.type.name == "uint64")
  124. encoder.writeNonNegativeIntegerTlv(tlvType, value);
  125. else if (field.type.name == "enum") {
  126. if (value < 0)
  127. throw new Error("ProtobufTlv.encode: ENUM value may not be negative");
  128. encoder.writeNonNegativeIntegerTlv(tlvType, value);
  129. }
  130. else if (field.type.name == "bytes") {
  131. var buffer = value.toBuffer();
  132. if (buffer.length == undefined)
  133. // We are not running in Node.js, so assume we are using the dcodeIO
  134. // browser implementation based on ArrayBuffer.
  135. buffer = new Uint8Array(value.toArrayBuffer());
  136. encoder.writeBlobTlv(tlvType, buffer);
  137. }
  138. else if (field.type.name == "string")
  139. // Use Blob to convert.
  140. encoder.writeBlobTlv(tlvType, new Blob(value, false).buf());
  141. else if (field.type.name == "bool") {
  142. if (value)
  143. encoder.writeTypeAndLength(tlvType, 0);
  144. }
  145. else if (field.type.name == "double") {
  146. var encoding = new Buffer(8);
  147. encoding.writeDoubleLE(value, 0);
  148. encoder.writeBlobTlv(tlvType, encoding);
  149. }
  150. else
  151. throw new Error("ProtobufTlv.encode: Unknown field type");
  152. }
  153. }
  154. };
  155. ProtobufTlv._decodeMessageValue = function(message, descriptor, decoder, endOffset)
  156. {
  157. var fields = descriptor.getChildren(ProtobufTlv._Field);
  158. for (var iField = 0; iField < fields.length; ++iField) {
  159. var field = fields[iField];
  160. var tlvType = field.id;
  161. if (!field.required && !decoder.peekType(tlvType, endOffset))
  162. continue;
  163. if (field.repeated) {
  164. while (decoder.peekType(tlvType, endOffset)) {
  165. if (field.type.name == "message") {
  166. var innerEndOffset = decoder.readNestedTlvsStart(tlvType);
  167. var value = new (field.resolvedType.build())();
  168. message.add(field.name, value);
  169. ProtobufTlv._decodeMessageValue
  170. (value, field.resolvedType, decoder, innerEndOffset);
  171. decoder.finishNestedTlvs(innerEndOffset);
  172. }
  173. else
  174. message.add
  175. (field.name,
  176. ProtobufTlv._decodeFieldValue(field, tlvType, decoder, endOffset));
  177. }
  178. }
  179. else {
  180. if (field.type.name == "message") {
  181. var innerEndOffset = decoder.readNestedTlvsStart(tlvType);
  182. var value = new (field.resolvedType.build())();
  183. message.set(field.name, value);
  184. ProtobufTlv._decodeMessageValue
  185. (value, field.resolvedType, decoder, innerEndOffset);
  186. decoder.finishNestedTlvs(innerEndOffset);
  187. }
  188. else
  189. message.set
  190. (field.name,
  191. ProtobufTlv._decodeFieldValue(field, tlvType, decoder, endOffset));
  192. }
  193. }
  194. };
  195. /**
  196. * This is a helper for _decodeMessageValue. Decode a single field and return
  197. * the value. Assume the field.type.name is not "message".
  198. */
  199. ProtobufTlv._decodeFieldValue = function(field, tlvType, decoder, endOffset)
  200. {
  201. if (field.type.name == "uint32" ||
  202. field.type.name == "uint64" ||
  203. field.type.name == "enum")
  204. return decoder.readNonNegativeIntegerTlv(tlvType);
  205. else if (field.type.name == "bytes")
  206. return decoder.readBlobTlv(tlvType);
  207. else if (field.type.name == "string")
  208. return decoder.readBlobTlv(tlvType).toString();
  209. else if (field.type.name == "bool")
  210. return decoder.readBooleanTlv(tlvType, endOffset);
  211. else if (field.type.name == "double")
  212. return decoder.readBlobTlv(tlvType).readDoubleLE(0);
  213. else
  214. throw new Error("ProtobufTlv.decode: Unknown field type");
  215. };
  216. /**
  217. * Return a Name made from the component array in a Protobuf message object,
  218. * assuming that it was defined with "repeated bytes". For example:
  219. * message Name {
  220. * repeated bytes component = 8;
  221. * }
  222. * @param {Array} componentArray The array from the Protobuf message object
  223. * representing the "repeated bytes" component array.
  224. * @return A new Name.
  225. */
  226. ProtobufTlv.toName = function(componentArray)
  227. {
  228. var name = new Name();
  229. for (var i = 0; i < componentArray.length; ++i)
  230. name.append
  231. (new Blob(new Buffer(componentArray[i].toBinary(), "binary"), false));
  232. return name;
  233. };