Source: encoding/binary-xml-wire-format.js

/**
 * Copyright (C) 2013-2015 Regents of the University of California.
 * @author: Jeff Thompson <[email protected]>
 *
 * 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.
 */

var Blob = require('../util/blob.js').Blob;
var NDNProtocolDTags = require('../util/ndn-protoco-id-tags.js').NDNProtocolDTags;
var BinaryXMLEncoder = require('./binary-xml-encoder.js').BinaryXMLEncoder;
var BinaryXMLDecoder = require('./binary-xml-decoder.js').BinaryXMLDecoder;
var WireFormat = require('./wire-format.js').WireFormat;
var Name = require('../name.js').Name;
var Exclude = require('../exclude.js').Exclude;
var Sha256WithRsaSignature = require('../sha256-with-rsa-signature.js').Sha256WithRsaSignature;
var MetaInfo = require('../meta-info.js').MetaInfo;
var PublisherPublicKeyDigest = require('../publisher-public-key-digest.js').PublisherPublicKeyDigest;
var DataUtils = require('./data-utils.js').DataUtils;
var KeyLocatorType = require('../key-locator.js').KeyLocatorType;

/**
 * A BinaryXmlWireFormat implements the WireFormat interface for encoding and decoding in binary XML.
 * @constructor
 */
var BinaryXmlWireFormat = function BinaryXmlWireFormat()
{
  // Inherit from WireFormat.
  WireFormat.call(this);

  if (!WireFormat.ENABLE_NDNX)
    throw new Error
      ("BinaryXmlWireFormat (NDNx) is deprecated. To enable while you upgrade your code to use NDN-TLV, set WireFormat.ENABLE_NDNX = true");
};

exports.BinaryXmlWireFormat = BinaryXmlWireFormat;

// Default object.
BinaryXmlWireFormat.instance = null;

/**
 * Encode interest as Binary XML and return the encoding.
 * @param {Name} interest The Name to encode.
 * @returns {Blobl} A Blob containing the encoding.
 */
BinaryXmlWireFormat.prototype.encodeName = function(name)
{
  var encoder = new BinaryXMLEncoder();
  name.to_ndnb(encoder);
  return new Blob(encoder.getReducedOstream(), false);
};

/**
 * Decode input as a Binary XML name and set the fields of the Name object.
 * @param {Name} name The Name object whose fields are updated.
 * @param {Buffer} input The buffer with the bytes to decode.
 */
BinaryXmlWireFormat.prototype.decodeName = function(name, input)
{
  var decoder = new BinaryXMLDecoder(input);
  name.from_ndnb(decoder);
};

/**
 * Encode interest as Binary XML and return the encoding.
 * @param {Interest} interest The Interest to encode.
 * @returns {object} An associative array with fields
 * (encoding, signedPortionBeginOffset, signedPortionEndOffset) where encoding
 * is a Blob containing the encoding, signedPortionBeginOffset is the offset in
 * the encoding of the beginning of the signed portion, and
 * signedPortionEndOffset is the offset in the encoding of the end of the signed
 * portion. The signed portion starts from the first name component and ends
 * just before the final name component (which is assumed to be a signature for
 * a signed interest).
 */
BinaryXmlWireFormat.prototype.encodeInterest = function(interest)
{
  var encoder = new BinaryXMLEncoder();
  var offsets = BinaryXmlWireFormat.encodeInterest(interest, encoder);
  return { encoding: new Blob(encoder.getReducedOstream(), false),
           signedPortionBeginOffset: offsets.signedPortionBeginOffset,
           signedPortionEndOffset: offsets.signedPortionEndOffset };
};

/**
 * Decode input as a Binary XML interest and set the fields of the interest object.
 * @param {Interest} interest The Interest object whose fields are updated.
 * @param {Buffer} input The buffer with the bytes to decode.
 * @returns {object} An associative array with fields
 * (signedPortionBeginOffset, signedPortionEndOffset) where
 * signedPortionBeginOffset is the offset in the encoding of the beginning of
 * the signed portion, and signedPortionEndOffset is the offset in the encoding
 * of the end of the signed portion. The signed portion starts from the first
 * name component and ends just before the final name component (which is
 * assumed to be a signature for a signed interest).
 */
BinaryXmlWireFormat.prototype.decodeInterest = function(interest, input)
{
  var decoder = new BinaryXMLDecoder(input);
  return BinaryXmlWireFormat.decodeInterest(interest, decoder);
};

/**
 * Encode data as Binary XML and return the encoding and signed offsets.
 * @param {Data} data The Data object to encode.
 * @returns {object} An associative array with fields
 * (encoding, signedPortionBeginOffset, signedPortionEndOffset) where encoding
 * is a Blob containing the encoding, signedPortionBeginOffset is the offset in
 * the encoding of the beginning of the signed portion, and
 * signedPortionEndOffset is the offset in the encoding of the end of the
 * signed portion.
 */
BinaryXmlWireFormat.prototype.encodeData = function(data)
{
  var encoder = new BinaryXMLEncoder(1500);
  var result = BinaryXmlWireFormat.encodeData(data, encoder);
  result.encoding = new Blob(encoder.getReducedOstream(), false);
  return result;
};

/**
 * @deprecated Use encodeData(data).
 */
BinaryXmlWireFormat.prototype.encodeContentObject = function(data)
{
  return this.encodeData(data);
};

/**
 * Decode input as a Binary XML data packet, set the fields in the data object, and return
 * the signed offsets.
 * @param {Data} data The Data object whose fields are updated.
 * @param {Buffer} input The buffer with the bytes to decode.
 * @returns {object} An associative array with fields
 * (signedPortionBeginOffset, signedPortionEndOffset) where
 * signedPortionBeginOffset is the offset in the encoding of the beginning of
 * the signed portion, and signedPortionEndOffset is the offset in the encoding
 * of the end of the signed portion.
 */
BinaryXmlWireFormat.prototype.decodeData = function(data, input)
{
  var decoder = new BinaryXMLDecoder(input);
  return BinaryXmlWireFormat.decodeData(data, decoder);
};

/**
 * @deprecated Use decodeData(data, input).
 */
BinaryXmlWireFormat.prototype.decodeContentObject = function(data, input)
{
  this.decodeData(data, input);
};

/**
 * Get a singleton instance of a BinaryXmlWireFormat.  Assuming that the default
 * wire format was set with
 * WireFormat.setDefaultWireFormat(BinaryXmlWireFormat.get()), you can check if
 * this is the default wire encoding with
 * if WireFormat.getDefaultWireFormat() == BinaryXmlWireFormat.get().
 * @returns {BinaryXmlWireFormat} The singleton instance.
 */
BinaryXmlWireFormat.get = function()
{
  if (BinaryXmlWireFormat.instance === null)
    BinaryXmlWireFormat.instance = new BinaryXmlWireFormat();
  return BinaryXmlWireFormat.instance;
};

/**
 * Encode the interest by calling the operations on the encoder.
 * @param {Interest} interest
 * @param {BinaryXMLEncoder} encoder
 * @returns {object} An associative array with fields
 * (signedPortionBeginOffset, signedPortionEndOffset) where
 * signedPortionBeginOffset is the offset in the encoding of the beginning of
 * the signed portion, and signedPortionEndOffset is the offset in the encoding
 * of the end of the signed portion. The signed portion starts from the first
 * name component and ends just before the final name component (which is
 * assumed to be a signature for a signed interest).
 */
BinaryXmlWireFormat.encodeInterest = function(interest, encoder)
{
  encoder.writeElementStartDTag(NDNProtocolDTags.Interest);

  var offsets = interest.getName().to_ndnb(encoder);

  if (null != interest.getMinSuffixComponents())
    encoder.writeDTagElement(NDNProtocolDTags.MinSuffixComponents, interest.getMinSuffixComponents());

  if (null != interest.getMaxSuffixComponents())
    encoder.writeDTagElement(NDNProtocolDTags.MaxSuffixComponents, interest.getMaxSuffixComponents());

  if (interest.getKeyLocator().getType() == KeyLocatorType.KEY_LOCATOR_DIGEST &&
      !interest.getKeyLocator().getKeyData().isNull() &&
      interest.getKeyLocator().getKeyData().size() > 0)
    // There is a KEY_LOCATOR_DIGEST. Use this instead of the publisherPublicKeyDigest.
    encoder.writeDTagElement
      (NDNProtocolDTags.PublisherPublicKeyDigest,
       interest.getKeyLocator().getKeyData());
  else {
    if (null != interest.publisherPublicKeyDigest)
      interest.publisherPublicKeyDigest.to_ndnb(encoder);
  }

  if (null != interest.getExclude())
    interest.getExclude().to_ndnb(encoder);

  if (null != interest.getChildSelector())
    encoder.writeDTagElement(NDNProtocolDTags.ChildSelector, interest.getChildSelector());

  if (interest.DEFAULT_ANSWER_ORIGIN_KIND != interest.getAnswerOriginKind() && interest.getAnswerOriginKind()!=null)
    encoder.writeDTagElement(NDNProtocolDTags.AnswerOriginKind, interest.getAnswerOriginKind());

  if (null != interest.getScope())
    encoder.writeDTagElement(NDNProtocolDTags.Scope, interest.getScope());

  if (null != interest.getInterestLifetimeMilliseconds())
    encoder.writeDTagElement(NDNProtocolDTags.InterestLifetime,
                DataUtils.nonNegativeIntToBigEndian((interest.getInterestLifetimeMilliseconds() / 1000.0) * 4096));

  if (interest.getNonce().size() > 0)
    encoder.writeDTagElement(NDNProtocolDTags.Nonce, interest.getNonce());

  encoder.writeElementClose();
  return offsets;
};

/**
 * Use the decoder to place the result in interest.
 * @param {Interest} interest
 * @param {BinaryXMLDecoder} decoder
 * @returns {object} An associative array with fields
 * (signedPortionBeginOffset, signedPortionEndOffset) where
 * signedPortionBeginOffset is the offset in the encoding of the beginning of
 * the signed portion, and signedPortionEndOffset is the offset in the encoding
 * of the end of the signed portion. The signed portion starts from the first
 * name component and ends just before the final name component (which is
 * assumed to be a signature for a signed interest).
 */
BinaryXmlWireFormat.decodeInterest = function(interest, decoder)
{
  decoder.readElementStartDTag(NDNProtocolDTags.Interest);

  interest.setName(new Name());
  var offsets = interest.getName().from_ndnb(decoder);

  if (decoder.peekDTag(NDNProtocolDTags.MinSuffixComponents))
    interest.setMinSuffixComponents(decoder.readIntegerDTagElement(NDNProtocolDTags.MinSuffixComponents));
  else
    interest.setMinSuffixComponents(null);

  if (decoder.peekDTag(NDNProtocolDTags.MaxSuffixComponents))
    interest.setMaxSuffixComponents(decoder.readIntegerDTagElement(NDNProtocolDTags.MaxSuffixComponents));
  else
    interest.setMaxSuffixComponents(null);

  // Initially clear the keyLocator.
  interest.getKeyLocator().clear();
  if (decoder.peekDTag(NDNProtocolDTags.PublisherPublicKeyDigest)) {
    interest.publisherPublicKeyDigest = new PublisherPublicKeyDigest();
    interest.publisherPublicKeyDigest.from_ndnb(decoder);
  }
  else
    interest.publisherPublicKeyDigest = null;
  if (interest.publisherPublicKeyDigest != null &&
      interest.publisherPublicKeyDigest.publisherPublicKeyDigest != null &&
      interest.publisherPublicKeyDigest.publisherPublicKeyDigest.length > 0) {
    // We keep the deprecated publisherPublicKeyDigest for backwards
    //   compatibility.  Also set the key locator.
    interest.getKeyLocator().setType(KeyLocatorType.KEY_LOCATOR_DIGEST);
    interest.getKeyLocator().setKeyData
      (interest.publisherPublicKeyDigest.publisherPublicKeyDigest);
  }

  if (decoder.peekDTag(NDNProtocolDTags.Exclude)) {
    interest.setExclude(new Exclude());
    interest.getExclude().from_ndnb(decoder);
  }
  else
    interest.setExclude(new Exclude());

  if (decoder.peekDTag(NDNProtocolDTags.ChildSelector))
    interest.setChildSelector(decoder.readIntegerDTagElement(NDNProtocolDTags.ChildSelector));
  else
    interest.setChildSelector(null);

  if (decoder.peekDTag(NDNProtocolDTags.AnswerOriginKind))
    interest.setAnswerOriginKind(decoder.readIntegerDTagElement(NDNProtocolDTags.AnswerOriginKind));
  else
    interest.setAnswerOriginKind(null);

  if (decoder.peekDTag(NDNProtocolDTags.Scope))
    interest.setScope(decoder.readIntegerDTagElement(NDNProtocolDTags.Scope));
  else
    interest.setScope(null);

  if (decoder.peekDTag(NDNProtocolDTags.InterestLifetime))
    interest.setInterestLifetimeMilliseconds(1000.0 * DataUtils.bigEndianToUnsignedInt
               (decoder.readBinaryDTagElement(NDNProtocolDTags.InterestLifetime)) / 4096);
  else
    interest.setInterestLifetimeMilliseconds(null);

  if (decoder.peekDTag(NDNProtocolDTags.Nonce))
    interest.setNonce(decoder.readBinaryDTagElement(NDNProtocolDTags.Nonce));
  else
    interest.setNonce(null);

  decoder.readElementClose();
  return offsets;
};

/**
 * Encode the data by calling the operations on the encoder.
 * @param {Data} data
 * @param {BinaryXMLEncoder} encoder
 * @returns {object} An associative array with fields
 * (signedPortionBeginOffset, signedPortionEndOffset) where
 * signedPortionBeginOffset is the offset in the encoding of the beginning of
 * the signed portion, and signedPortionEndOffset is the offset in the encoding
 * of the end of the signed portion.
 */
BinaryXmlWireFormat.encodeData = function(data, encoder)
{
  //TODO verify name, MetaInfo and Signature is present
  encoder.writeElementStartDTag(data.getElementLabel());

  if (null != data.getSignature())
    data.getSignature().to_ndnb(encoder);

  var signedPortionBeginOffset = encoder.offset;

  if (null != data.getName())
    data.getName().to_ndnb(encoder);

  if (null != data.getMetaInfo())
    // Use getSignatureOrMetaInfoKeyLocator for the transition of moving
    //   the key locator from the MetaInfo to the Signauture object.
    data.getMetaInfo().to_ndnb(encoder, data.getSignatureOrMetaInfoKeyLocator());

  encoder.writeDTagElement(NDNProtocolDTags.Content, data.getContent().buf());

  var signedPortionEndOffset = encoder.offset;

  encoder.writeElementClose();

  return { signedPortionBeginOffset: signedPortionBeginOffset,
           signedPortionEndOffset: signedPortionEndOffset };
};

/**
 * Use the decoder to place the result in data.
 * @param {Data} data
 * @param {BinaryXMLDecoder} decoder
 * @returns {object} An associative array with fields
 * (signedPortionBeginOffset, signedPortionEndOffset) where
 * signedPortionBeginOffset is the offset in the encoding of the beginning of
 * the signed portion, and signedPortionEndOffset is the offset in the encoding
 * of the end of the signed portion.
 */
BinaryXmlWireFormat.decodeData = function(data, decoder)
{
  // TODO VALIDATE THAT ALL FIELDS EXCEPT SIGNATURE ARE PRESENT
  decoder.readElementStartDTag(data.getElementLabel());

  if (decoder.peekDTag(NDNProtocolDTags.Signature)) {
    data.setSignature(new Sha256WithRsaSignature());
    data.getSignature().from_ndnb(decoder);
  }
  else
    data.setSignature(new Sha256WithRsaSignature());

  var signedPortionBeginOffset = decoder.offset;

  data.setName(new Name());
  data.getName().from_ndnb(decoder);

  if (decoder.peekDTag(NDNProtocolDTags.SignedInfo)) {
    data.setMetaInfo(new MetaInfo());
    data.getMetaInfo().from_ndnb(decoder);
    if (data.getMetaInfo().locator != null && data.getSignature() != null)
      // Copy the key locator pointer to the Signature object for the transition
      //   of moving the key locator from the MetaInfo to the Signature object.
      data.getSignature().setKeyLocator(data.getMetaInfo().locator);
  }
  else
    data.setMetaInfo(new MetaInfo());

  data.setContent(decoder.readBinaryDTagElement(NDNProtocolDTags.Content, true));

  var signedPortionEndOffset = decoder.offset;

  decoder.readElementClose();

  return { signedPortionBeginOffset: signedPortionBeginOffset,
           signedPortionEndOffset: signedPortionEndOffset };
};