/**
 * Copyright (C) 2020 Regents of the University of California.
 * @author: <NAME> <<EMAIL>>
 *
 * 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.
 */

package net.named_data.jndn.encoding;

import java.nio.ByteBuffer;
import java.util.Random;
import net.named_data.jndn.ComponentType;
import net.named_data.jndn.ContentType;
import net.named_data.jndn.ControlParameters;
import net.named_data.jndn.ControlResponse;
import net.named_data.jndn.Data;
import net.named_data.jndn.DelegationSet;
import net.named_data.jndn.DigestSha256Signature;
import net.named_data.jndn.Exclude;
import net.named_data.jndn.RegistrationOptions;
import net.named_data.jndn.GenericSignature;
import net.named_data.jndn.HmacWithSha256Signature;
import net.named_data.jndn.Interest;
import net.named_data.jndn.KeyLocator;
import net.named_data.jndn.KeyLocatorType;
import net.named_data.jndn.MetaInfo;
import net.named_data.jndn.Name;
import net.named_data.jndn.Sha256WithEcdsaSignature;
import net.named_data.jndn.Sha256WithRsaSignature;
import net.named_data.jndn.Signature;
import net.named_data.jndn.encoding.tlv.Tlv;
import net.named_data.jndn.encoding.tlv.TlvDecoder;
import net.named_data.jndn.encoding.tlv.TlvEncoder;
import net.named_data.jndn.encrypt.EncryptedContent;
import net.named_data.jndn.encrypt.algo.EncryptAlgorithmType;
import net.named_data.jndn.lp.IncomingFaceId;
import net.named_data.jndn.lp.LpPacket;
import net.named_data.jndn.NetworkNack;
import net.named_data.jndn.encrypt.Schedule;
import net.named_data.jndn.lp.CongestionMark;
import net.named_data.jndn.security.ValidityPeriod;
import net.named_data.jndn.util.Blob;

/**
 * A Tlv0_3WireFormat implements the WireFormat interface for encoding and
 * decoding with the NDN-TLV wire format, version 0.3.
 */
public class Tlv0_3WireFormat extends WireFormat {
  /**
   * Encode name in NDN-TLV and return the encoding.
   * @param name The Name object to encode.
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeName(Name name)
  {
    TlvEncoder encoder = new TlvEncoder(256);
    encodeName(name, new int[1], new int[1], encoder);
    return new Blob(encoder.getOutput(), false);
  }

  /**
   * Decode input as a name in NDN-TLV and set the fields of the Name object.
   * @param name The Name object whose fields are updated.
   * @param input The input buffer to decode.  This reads from position() to limit(), but does not change the position.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding.
   */
  public void
  decodeName(Name name, ByteBuffer input, boolean copy) throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);
    decodeName(name, new int[1], new int[1], decoder, copy);
  }

  /**
   * Encode interest using NDN-TLV and return the encoding.
   * @param interest The Interest object to encode.
   * @param signedPortionBeginOffset Return the offset in the encoding of the
   * beginning 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).
   * @param signedPortionEndOffset Return 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).
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeInterest
    (Interest interest, int[] signedPortionBeginOffset, int[] signedPortionEndOffset)
  {
    if (!interest.getDidSetCanBePrefix_() && !didCanBePrefixWarning_) {
      System.out.println
        ("WARNING: The default CanBePrefix will change. See Interest.setDefaultCanBePrefix() for details.");
      didCanBePrefixWarning_ = true;
    }

    return encodeInterestV03
      (interest, signedPortionBeginOffset, signedPortionEndOffset);
  }

  /**
   * Decode input as an interest in NDN-TLV and set the fields of the interest
   * object.
   * @param interest The Interest object whose fields are updated.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param signedPortionBeginOffset Return the offset in the encoding of the
   * beginning 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).
   * @param signedPortionEndOffset Return 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).
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding.
   */
  public void
  decodeInterest
    (Interest interest, ByteBuffer input, int[] signedPortionBeginOffset,
     int[] signedPortionEndOffset, boolean copy) throws EncodingException
  {
    try {
      decodeInterestV03
        (interest, input, signedPortionBeginOffset, signedPortionEndOffset,
         copy);
    } catch (Exception exceptionV03) {
      try {
        // Failed to decode as format v0.3. Try to decode as v0.2.
        decodeInterestV02
          (interest, input, signedPortionBeginOffset, signedPortionEndOffset,
           copy);
      } catch (Exception ex) {
        // Ignore the exception decoding as format v0.2 and throw the exception
        // from trying to decode as format as format v0.3.
        throw exceptionV03;
      }
    }
  }

  /**
   * Do the work of decodeInterest to decode strictly as format v0.2.
   */
  private void
  decodeInterestV02
    (Interest interest, ByteBuffer input, int[] signedPortionBeginOffset,
     int[] signedPortionEndOffset, boolean copy) throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);

    int endOffset = decoder.readNestedTlvsStart(Tlv.Interest);
    decodeName
      (interest.getName(), signedPortionBeginOffset,signedPortionEndOffset,
       decoder, copy);
    if (decoder.peekType(Tlv.Selectors, endOffset))
      decodeSelectors(interest, decoder, copy);
    else {
      // Set selectors to none.
      interest.setMinSuffixComponents(-1);
      interest.setMaxSuffixComponents(-1);
      interest.getKeyLocator().clear();
      interest.getExclude().clear();
      interest.setChildSelector(-1);
      interest.setMustBeFresh(false);
    }
    // Require a Nonce, but don't force it to be 4 bytes.
    ByteBuffer nonce = decoder.readBlobTlv(Tlv.Nonce);
    interest.setInterestLifetimeMilliseconds
      (decoder.readOptionalNonNegativeIntegerTlv(Tlv.InterestLifetime, endOffset));

    if (decoder.peekType(Tlv.ForwardingHint, endOffset)) {
      int forwardingHintEndOffset = decoder.readNestedTlvsStart
        (Tlv.ForwardingHint);
      decodeDelegationSet
        (interest.getForwardingHint(), forwardingHintEndOffset, decoder, copy);
      decoder.finishNestedTlvs(forwardingHintEndOffset);
    }

    if (decoder.peekType(Tlv.Data, endOffset)) {
      // Get the bytes of the Link TLV.
      int linkBeginOffset = decoder.getOffset();
      int linkEndOffset = decoder.readNestedTlvsStart(Tlv.Data);
      decoder.seek(linkEndOffset);

      interest.setLinkWireEncoding
        (new Blob(decoder.getSlice(linkBeginOffset, linkEndOffset), copy), this);
    }
    else
      interest.unsetLink();
    interest.setSelectedDelegationIndex
      ((int)decoder.readOptionalNonNegativeIntegerTlv
       (Tlv.SelectedDelegation, endOffset));
    if (interest.getSelectedDelegationIndex() >= 0 && !interest.hasLink())
      throw new EncodingException
        ("Interest has a selected delegation, but no link object");

    // Format v0.2 doesn't have application parameters.
    interest.setApplicationParameters(new Blob());

    // Set the nonce last because setting other interest fields clears it.
    interest.setNonce(new Blob(nonce, copy));

    decoder.finishNestedTlvs(endOffset);
  }

  /**
   * Encode data in NDN-TLV and return the encoding.
   * @param data The Data object to encode.
   * @param signedPortionBeginOffset Return the offset in the encoding of the
   * beginning of the signed portion by setting signedPortionBeginOffset[0].
   * If you are not encoding in order to sign, you can call encodeData(data) to
   * ignore this returned value.
   * @param signedPortionEndOffset Return the offset in the encoding of the end
   * of the signed portion by setting signedPortionEndOffset[0].
   * If you are not encoding in order to sign, you can call encodeData(data) to
   * ignore this returned value.
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeData
    (Data data, int[] signedPortionBeginOffset, int[] signedPortionEndOffset)
  {
    TlvEncoder encoder = new TlvEncoder(1500);
    int saveLength = encoder.getLength();

    // Encode backwards.
    encoder.writeBlobTlv
      (Tlv.SignatureValue, (data.getSignature()).getSignature().buf());
    int signedPortionEndOffsetFromBack = encoder.getLength();

    encodeSignatureInfo(data.getSignature(), encoder);
    encoder.writeBlobTlv(Tlv.Content, data.getContent().buf());
    encodeMetaInfo(data.getMetaInfo(), encoder);
    encodeName(data.getName(), new int[1], new int[1], encoder);
    int signedPortionBeginOffsetFromBack = encoder.getLength();

    encoder.writeTypeAndLength(Tlv.Data, encoder.getLength() - saveLength);

    signedPortionBeginOffset[0] =
      encoder.getLength() - signedPortionBeginOffsetFromBack;
    signedPortionEndOffset[0] =
      encoder.getLength() - signedPortionEndOffsetFromBack;
    return new Blob(encoder.getOutput(), false);
  }

  /**
   * Decode input as a data packet in NDN-TLV and set the fields in the data
   * object.
   * @param data The Data object whose fields are updated.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param signedPortionBeginOffset Return the offset in the input buffer of
   * the beginning of the signed portion by setting signedPortionBeginOffset[0].
   * If you are not decoding in order to verify, you can call
   * decodeData(data, input) to ignore this returned value.
   * @param signedPortionEndOffset Return the offset in the input buffer of the
   * end of the signed portion by setting signedPortionEndOffset[0]. If you are
   * not decoding in order to verify, you can call decodeData(data, input) to
   * ignore this returned value.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding.
   */
  public void
  decodeData
    (Data data, ByteBuffer input, int[] signedPortionBeginOffset,
     int[] signedPortionEndOffset, boolean copy) throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);

    int endOffset = decoder.readNestedTlvsStart(Tlv.Data);
    signedPortionBeginOffset[0] = decoder.getOffset();

    decodeName(data.getName(), new int[1], new int[1], decoder, copy);
    if (decoder.peekType(Tlv.MetaInfo, endOffset))
      decodeMetaInfo(data.getMetaInfo(), decoder, copy);
    else
      data.getMetaInfo().clear();
    data.setContent
      (new Blob(decoder.readOptionalBlobTlv(Tlv.Content, endOffset), copy));
    decodeSignatureInfo(data, decoder, copy);

    signedPortionEndOffset[0] = decoder.getOffset();
    data.getSignature().setSignature
      (new Blob(decoder.readBlobTlv(Tlv.SignatureValue), copy));

    decoder.finishNestedTlvs(endOffset);
  }

  /**
   * Encode controlParameters in NDN-TLV and return the encoding.
   * @param controlParameters The ControlParameters object to encode.
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeControlParameters(ControlParameters controlParameters)
  {
    TlvEncoder encoder = new TlvEncoder(256);
    encodeControlParameters(controlParameters, encoder);
    return new Blob(encoder.getOutput(), false);
  }

  /**
   * Decode input as a control parameters in NDN-TLV and set the fields of the
   * controlParameters object.
   * @param controlParameters The ControlParameters object whose fields are
   * updated.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding
   */
  public void
  decodeControlParameters
    (ControlParameters controlParameters, ByteBuffer input, boolean copy)
    throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);
    decodeControlParameters(controlParameters, decoder, copy);
  }

  /**
   * Encode controlResponse in NDN-TLV and return the encoding.
   * @param controlResponse The ControlResponse object to encode.
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeControlResponse(ControlResponse controlResponse)
  {
    TlvEncoder encoder = new TlvEncoder(256);
    int saveLength = encoder.getLength();

    // Encode backwards.

    // Encode the body.
    if (controlResponse.getBodyAsControlParameters() != null)
      encodeControlParameters
        (controlResponse.getBodyAsControlParameters(), encoder);

    encoder.writeBlobTlv(Tlv.NfdCommand_StatusText,
      new Blob(controlResponse.getStatusText()).buf());
    encoder.writeNonNegativeIntegerTlv
      (Tlv.NfdCommand_StatusCode, controlResponse.getStatusCode());

    encoder.writeTypeAndLength
      (Tlv.NfdCommand_ControlResponse, encoder.getLength() - saveLength);

    return new Blob(encoder.getOutput(), false);
  }

  /**
   * Decode input as a control parameters in NDN-TLV and set the fields of the
   * controlResponse object.
   * @param controlResponse The ControlResponse object whose fields are
   * updated.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding
   */
  public void
  decodeControlResponse
    (ControlResponse controlResponse, ByteBuffer input, boolean copy)
    throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);
    int endOffset = decoder.readNestedTlvsStart(Tlv.NfdCommand_ControlResponse);

    controlResponse.setStatusCode
      ((int)decoder.readNonNegativeIntegerTlv(Tlv.NfdCommand_StatusCode));
    // Set copy false since we just immediately get a string.
    Blob statusText = new Blob
      (decoder.readBlobTlv(Tlv.NfdCommand_StatusText), false);
    controlResponse.setStatusText(statusText.toString());

    // Decode the body.
    if (decoder.peekType(Tlv.ControlParameters_ControlParameters, endOffset)) {
      controlResponse.setBodyAsControlParameters(new ControlParameters());
      // Decode into the existing ControlParameters to avoid copying.
      decodeControlParameters
        (controlResponse.getBodyAsControlParameters(), decoder, copy);
    }
    else
      controlResponse.setBodyAsControlParameters(null);

    // The NFD management protocol is at the application-layer, so skip
    // unrecognized type codes.
    decoder.finishNestedTlvs(endOffset, true);
  }

  /**
   * Encode signature as a SignatureInfo in NDN-TLV and return the encoding.
   * @param signature An object of a subclass of Signature to encode.
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeSignatureInfo(Signature signature)
  {
    TlvEncoder encoder = new TlvEncoder(256);
    encodeSignatureInfo(signature, encoder);

    return new Blob(encoder.getOutput(), false);
  }

  private static class SimpleSignatureHolder implements SignatureHolder {
    public Data setSignature(Signature signature)
    {
      signature_ = signature;
      return null;
    }

    public Signature getSignature()
    {
      return signature_;
    }

    private Signature signature_;
  }

  /**
   * Decode signatureInfo as an NDN-TLV signature info and signatureValue as the
   * related NDN-TLV SignatureValue, and return a new object which is a subclass
   * of Signature.
   * @param signatureInfo The signature info input buffer to decode. This reads
   * from position() to limit(), but does not change the position.
   * @param signatureValue The signature value input buffer to decode. This reads
   * from position() to limit(), but does not change the position.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @return A new object which is a subclass of Signature.
   * @throws EncodingException For invalid encoding.
   */
  public Signature
  decodeSignatureInfoAndValue
    (ByteBuffer signatureInfo, ByteBuffer signatureValue, boolean copy)
    throws EncodingException
  {
    // Use a SignatureHolder to imitate a Data object for _decodeSignatureInfo.
    SimpleSignatureHolder signatureHolder = new SimpleSignatureHolder();
    TlvDecoder decoder = new TlvDecoder(signatureInfo);
    decodeSignatureInfo(signatureHolder, decoder, copy);

    decoder = new TlvDecoder(signatureValue);
    signatureHolder.getSignature().setSignature
      (new Blob(decoder.readBlobTlv(Tlv.SignatureValue), copy));

    return signatureHolder.getSignature();
  }

  /**
   * Encode the signatureValue in the Signature object as a SignatureValue (the
   * signature bits) in NDN-TLV and return the encoding.
   * @param signature An object of a subclass of Signature with the signature
   * value to encode.
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeSignatureValue(Signature signature)
  {
    TlvEncoder encoder = new TlvEncoder(256);
    encoder.writeBlobTlv(Tlv.SignatureValue, signature.getSignature().buf());

    return new Blob(encoder.getOutput(), false);
  }

  /**
   * Decode input as an NDN-TLV LpPacket and set the fields of the lpPacket object.
   * @param lpPacket The LpPacket object whose fields are updated.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding.
   */
  public void
  decodeLpPacket
    (LpPacket lpPacket, ByteBuffer input, boolean copy) throws EncodingException
  {
    lpPacket.clear();

    TlvDecoder decoder = new TlvDecoder(input);
    int endOffset = decoder.readNestedTlvsStart(Tlv.LpPacket_LpPacket);

    while (decoder.getOffset() < endOffset) {
      // Imitate TlvDecoder.readTypeAndLength.
      int fieldType = decoder.readVarNumber();
      int fieldLength = decoder.readVarNumber();
      int fieldEndOffset = decoder.getOffset() + fieldLength;
      if (fieldEndOffset > input.limit())
        throw new EncodingException("TLV length exceeds the buffer length");

      if (fieldType == Tlv.LpPacket_Fragment) {
        // Set the fragment to the bytes of the TLV value.
        lpPacket.setFragmentWireEncoding
          (new Blob(decoder.getSlice(decoder.getOffset(), fieldEndOffset), copy));
        decoder.seek(fieldEndOffset);

        // The fragment is supposed to be the last field.
        break;
      }
      else if (fieldType == Tlv.LpPacket_Nack) {
        NetworkNack networkNack = new NetworkNack();
        int code = (int)decoder.readOptionalNonNegativeIntegerTlv
          (Tlv.LpPacket_NackReason, fieldEndOffset);
        // The enum numeric values are the same as this wire format, so use as is.
        if (code < 0 || code == NetworkNack.Reason.NONE.getNumericType())
          // This includes an omitted NackReason.
          networkNack.setReason(NetworkNack.Reason.NONE);
        else if (code == NetworkNack.Reason.CONGESTION.getNumericType())
          networkNack.setReason(NetworkNack.Reason.CONGESTION);
        else if (code == NetworkNack.Reason.DUPLICATE.getNumericType())
          networkNack.setReason(NetworkNack.Reason.DUPLICATE);
        else if (code == NetworkNack.Reason.NO_ROUTE.getNumericType())
          networkNack.setReason(NetworkNack.Reason.NO_ROUTE);
        else {
          // Unrecognized reason.
          networkNack.setReason(NetworkNack.Reason.OTHER_CODE);
          networkNack.setOtherReasonCode(code);
        }

        lpPacket.addHeaderField(networkNack);
      }
      else if (fieldType == Tlv.LpPacket_IncomingFaceId) {
        IncomingFaceId incomingFaceId = new IncomingFaceId();
        incomingFaceId.setFaceId(decoder.readNonNegativeInteger(fieldLength));
        lpPacket.addHeaderField(incomingFaceId);
      }
      else if (fieldType == Tlv.LpPacket_CongestionMark) {
        CongestionMark congestionMark = new CongestionMark();
        congestionMark.setCongestionMark(decoder.readNonNegativeInteger
          (fieldLength));
        lpPacket.addHeaderField(congestionMark);
      }
      else {
        // Unrecognized field type. The conditions for ignoring are here:
        // http://redmine.named-data.net/projects/nfd/wiki/NDNLPv2
        boolean canIgnore =
          (fieldType >= Tlv.LpPacket_IGNORE_MIN &&
           fieldType <= Tlv.LpPacket_IGNORE_MAX &&
           (fieldType & 0x03) == 0);
        if (!canIgnore)
          throw new EncodingException("Did not get the expected TLV type");

        // Ignore.
        decoder.seek(fieldEndOffset);
      }

      decoder.finishNestedTlvs(fieldEndOffset);
    }

    decoder.finishNestedTlvs(endOffset);
  }

  /**
   * Encode delegationSet as a sequence of NDN-TLV Delegation, and return the
   * encoding. Note that the sequence of Delegation does not have an outer TLV
   * type and length because it is intended to use the type and length of a Data
   * packet's Content.
   * @param delegationSet The DelegationSet object to encode.
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeDelegationSet(DelegationSet delegationSet)
  {
    TlvEncoder encoder = new TlvEncoder(256);
    encodeDelegationSet(delegationSet, encoder);

    return new Blob(encoder.getOutput(), false);
  }

  /**
   * Decode input as a sequence of NDN-TLV Delegation and set the fields of the
   * delegationSet object. Note that the sequence of Delegation does not have an
   * outer TLV type and length because it is intended to use the type and length
   * of a Data packet's Content. This ignores any elements after the sequence
   * of Delegation and before input.limit().
   * @param delegationSet The DelegationSet object whose fields are updated.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding.
   */
  public void
  decodeDelegationSet
    (DelegationSet delegationSet, ByteBuffer input, boolean copy)
    throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);
    decodeDelegationSet(delegationSet, input.limit(), decoder, copy);
  }

  /**
   * Encode the EncryptedContent v1 in NDN-TLV and return the encoding.
   * @param encryptedContent The EncryptedContent object to encode.
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeEncryptedContent(EncryptedContent encryptedContent)
  {
    TlvEncoder encoder = new TlvEncoder(256);
    int saveLength = encoder.getLength();

    // Encode backwards.
    encoder.writeBlobTlv
      (Tlv.Encrypt_EncryptedPayload, encryptedContent.getPayload().buf());
    encoder.writeOptionalBlobTlv
      (Tlv.Encrypt_InitialVector, encryptedContent.getInitialVector().buf());
    // Assume the algorithmType value is the same as the TLV type.
    encoder.writeNonNegativeIntegerTlv
      (Tlv.Encrypt_EncryptionAlgorithm,
       encryptedContent.getAlgorithmType().getNumericType());
    Tlv0_3WireFormat.encodeKeyLocator
      (Tlv.KeyLocator, encryptedContent.getKeyLocator(), encoder);

    encoder.writeTypeAndLength
      (Tlv.Encrypt_EncryptedContent, encoder.getLength() - saveLength);

    return new Blob(encoder.getOutput(), false);
  }

  /**
   * Decode input as a EncryptedContent v1 in NDN-TLV and set the fields of the
   * encryptedContent object.
   * @param encryptedContent The EncryptedContent object whose fields are
   * updated.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding
   */
  public void
  decodeEncryptedContent
    (EncryptedContent encryptedContent, ByteBuffer input, boolean copy)
    throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);
    int endOffset = decoder.readNestedTlvsStart
      (Tlv.Encrypt_EncryptedContent);

    encryptedContent.clear();
    Tlv0_3WireFormat.decodeKeyLocator
      (Tlv.KeyLocator, encryptedContent.getKeyLocator(), decoder, copy);

    int algorithmType = (int)decoder.readNonNegativeIntegerTlv
       (Tlv.Encrypt_EncryptionAlgorithm);
    if (algorithmType == EncryptAlgorithmType.AesEcb.getNumericType())
      encryptedContent.setAlgorithmType(EncryptAlgorithmType.AesEcb);
    else if (algorithmType == EncryptAlgorithmType.AesCbc.getNumericType())
      encryptedContent.setAlgorithmType(EncryptAlgorithmType.AesCbc);
    else if (algorithmType == EncryptAlgorithmType.RsaPkcs.getNumericType())
      encryptedContent.setAlgorithmType(EncryptAlgorithmType.RsaPkcs);
    else if (algorithmType == EncryptAlgorithmType.RsaOaep.getNumericType())
      encryptedContent.setAlgorithmType(EncryptAlgorithmType.RsaOaep);
    else
      throw new EncodingException
        ("Unrecognized EncryptionAlgorithm code " + algorithmType);

    encryptedContent.setInitialVector
      (new Blob(decoder.readOptionalBlobTlv
        (Tlv.Encrypt_InitialVector, endOffset), copy));
    encryptedContent.setPayload
      (new Blob(decoder.readBlobTlv(Tlv.Encrypt_EncryptedPayload), copy));

    decoder.finishNestedTlvs(endOffset);
  }

  /**
   * Encode the EncryptedContent v2 (used in Name-based Access Control v2) in
   * NDN-TLV and return the encoding.
   * See https://github.com/named-data/name-based-access-control/blob/new/docs/spec.rst .
   * @param encryptedContent The EncryptedContent object to encode.
   * @return A Blob containing the encoding.
   */
  public Blob
  encodeEncryptedContentV2(EncryptedContent encryptedContent)
  {
    TlvEncoder encoder = new TlvEncoder(256);
    int saveLength = encoder.getLength();

    // Encode backwards.
    if (encryptedContent.getKeyLocator().getType() == KeyLocatorType.KEYNAME)
      Tlv0_3WireFormat.encodeName
        (encryptedContent.getKeyLocator().getKeyName(), new int[1], new int[1],
         encoder);
    encoder.writeOptionalBlobTlv
      (Tlv.Encrypt_EncryptedPayloadKey, encryptedContent.getPayloadKey().buf());
    encoder.writeOptionalBlobTlv
      (Tlv.Encrypt_InitialVector, encryptedContent.getInitialVector().buf());
    encoder.writeBlobTlv
      (Tlv.Encrypt_EncryptedPayload, encryptedContent.getPayload().buf());

    encoder.writeTypeAndLength
      (Tlv.Encrypt_EncryptedContent, encoder.getLength() - saveLength);

    return new Blob(encoder.getOutput(), false);
  }

  /**
   * Decode input as an EncryptedContent v2 (used in Name-based Access Control
   * v2) in NDN-TLV and set the fields of the encryptedContent object.
   * See https://github.com/named-data/name-based-access-control/blob/new/docs/spec.rst .
   * @param encryptedContent The EncryptedContent object whose fields are
   * updated.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding.
   */
  public void
  decodeEncryptedContentV2
    (EncryptedContent encryptedContent, ByteBuffer input, boolean copy)
    throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);
    int endOffset = decoder.readNestedTlvsStart
      (Tlv.Encrypt_EncryptedContent);

    encryptedContent.clear();
    encryptedContent.setPayload
      (new Blob(decoder.readBlobTlv(Tlv.Encrypt_EncryptedPayload), copy));
    encryptedContent.setInitialVector
      (new Blob(decoder.readOptionalBlobTlv
        (Tlv.Encrypt_InitialVector, endOffset), copy));
    encryptedContent.setPayloadKey
      (new Blob(decoder.readOptionalBlobTlv
        (Tlv.Encrypt_EncryptedPayloadKey, endOffset), copy));

    if (decoder.peekType(Tlv.Name, endOffset)) {
      decodeName
        (encryptedContent.getKeyLocator().getKeyName(), new int[1], new int[1],
         decoder, copy);
      encryptedContent.getKeyLocator().setType(KeyLocatorType.KEYNAME);
    }

    decoder.finishNestedTlvs(endOffset);
  }

  /**
   * Get a singleton instance of a Tlv0_3WireFormat.  To always use the
   * preferred version NDN-TLV, you should use TlvWireFormat.get().
   * @return The singleton instance.
   */
  public static Tlv0_3WireFormat
  get()
  {
    return instance_;
  }

  /**
   * Encode the name component to the encoder as NDN-TLV. This handles different
   * component types such as ImplicitSha256DigestComponent.
   * @param component The name component to encode.
   * @param encoder The TlvEncoder to receive the encoding.
   */
  private static void
  encodeNameComponent(Name.Component component, TlvEncoder encoder)
  {
    int type;
    if (component.getType() == ComponentType.OTHER_CODE)
      type = component.getOtherTypeCode();
    else
      // The enum values are the same as the TLV type codes.
      type = component.getType().getNumericType();

    encoder.writeBlobTlv(type, component.getValue().buf());
  }

  /**
   * Decode the name component as NDN-TLV and return the component. This handles
   * different component types such as ImplicitSha256DigestComponent.
   * @param decoder The decoder with the input to decode.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @return A new Name.Component.
   * @throws EncodingException
   */
  private static Name.Component
  decodeNameComponent(TlvDecoder decoder, boolean copy) throws EncodingException
  {
    int savePosition = decoder.getOffset();
    int type = decoder.readVarNumber();
    // Restore the position.
    decoder.seek(savePosition);

    Blob value = new Blob(decoder.readBlobTlv(type), copy);
    if (type == Tlv.ImplicitSha256DigestComponent)
      return Name.Component.fromImplicitSha256Digest(value);
    else if (type == Tlv.ParametersSha256DigestComponent)
      return Name.Component.fromParametersSha256Digest(value);
    else if (type == Tlv.NameComponent)
      return new Name.Component(value);
    else
      // Unrecognized type code.
      return new Name.Component(value, ComponentType.OTHER_CODE, type);
  }

  /**
   * Encode the name as NDN-TLV to the encoder.
   * @param name The name to encode.
   * @param signedPortionBeginOffset Return the offset in the encoding of the
   * beginning 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).
   * @param signedPortionEndOffset Return 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).
   * @param encoder The TlvEncoder to receive the encoding.
   */
  public static void
  encodeName
    (Name name, int[] signedPortionBeginOffset, int[] signedPortionEndOffset,
     TlvEncoder encoder)
  {
    int saveLength = encoder.getLength();

    // Encode the components backwards.
    int signedPortionEndOffsetFromBack = 0;
    for (int i = name.size() - 1; i >= 0; --i) {
      encodeNameComponent(name.get(i), encoder);
      if (i == name.size() - 1)
          signedPortionEndOffsetFromBack = encoder.getLength();
    }

    int signedPortionBeginOffsetFromBack = encoder.getLength();
    encoder.writeTypeAndLength(Tlv.Name, encoder.getLength() - saveLength);

    signedPortionBeginOffset[0] =
      encoder.getLength() - signedPortionBeginOffsetFromBack;
    if (name.size() == 0)
        // There is no "final component", so set signedPortionEndOffset
        //   arbitrarily.
        signedPortionEndOffset[0] = signedPortionBeginOffset[0];
    else
        signedPortionEndOffset[0] =
          encoder.getLength() - signedPortionEndOffsetFromBack;
  }

  /**
   * Decode the name as NDN-TLV and set the fields in name.
   * @param name The name object whose fields are set.
   * @param signedPortionBeginOffset Return the offset in the encoding of the
   * beginning 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).
   * If you are not decoding in order to verify, you can ignore this returned value.
   * @param signedPortionEndOffset Return 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).
   * If you are not decoding in order to verify, you can ignore this returned value.
   * @param decoder The decoder with the input to decode.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException
   */
  public static void
  decodeName
    (Name name, int[] signedPortionBeginOffset, int[] signedPortionEndOffset,
     TlvDecoder decoder, boolean copy) throws EncodingException
  {
    name.clear();

    int endOffset = decoder.readNestedTlvsStart(Tlv.Name);

    signedPortionBeginOffset[0] = decoder.getOffset();
    // In case there are no components, set signedPortionEndOffset arbitrarily.
    signedPortionEndOffset[0] = signedPortionBeginOffset[0];

    while (decoder.getOffset() < endOffset) {
      signedPortionEndOffset[0] = decoder.getOffset();
      name.append(decodeNameComponent(decoder, copy));
    }

    decoder.finishNestedTlvs(endOffset);
  }

  /**
   * Encode the interest selectors.  If no selectors are written, do not output
   * a  Selectors TLV.
   */
  protected static void
  encodeSelectors(Interest interest, TlvEncoder encoder)
  {
    int saveLength = encoder.getLength();

    // TODO: Throw error if the interest speficies V02 fields.

    // Encode backwards.
    if (interest.getMustBeFresh())
      encoder.writeTypeAndLength(Tlv.MustBeFresh, 0);
    encoder.writeOptionalNonNegativeIntegerTlv(
      Tlv.ChildSelector, interest.getChildSelector());
    if (interest.getExclude().size() > 0)
      encodeExclude(interest.getExclude(), encoder);

    if (interest.getKeyLocator().getType() != KeyLocatorType.NONE)
      encodeKeyLocator
        (Tlv.PublisherPublicKeyLocator, interest.getKeyLocator(), encoder);

    encoder.writeOptionalNonNegativeIntegerTlv(
      Tlv.MaxSuffixComponents, interest.getMaxSuffixComponents());
    encoder.writeOptionalNonNegativeIntegerTlv(
      Tlv.MinSuffixComponents, interest.getMinSuffixComponents());

    // Only output the type and length if values were written.
    if (encoder.getLength() != saveLength)
      encoder.writeTypeAndLength(Tlv.Selectors, encoder.getLength() - saveLength);
  }

  private static void
  decodeSelectors
    (Interest interest, TlvDecoder decoder, boolean copy) throws EncodingException
  {
    int endOffset = decoder.readNestedTlvsStart(Tlv.Selectors);

    interest.setMinSuffixComponents((int)decoder.readOptionalNonNegativeIntegerTlv
      (Tlv.MinSuffixComponents, endOffset));
    interest.setMaxSuffixComponents((int)decoder.readOptionalNonNegativeIntegerTlv
      (Tlv.MaxSuffixComponents, endOffset));

    if (decoder.peekType(Tlv.PublisherPublicKeyLocator, endOffset))
      decodeKeyLocator
        (Tlv.PublisherPublicKeyLocator, interest.getKeyLocator(), decoder, copy);
    else
      interest.getKeyLocator().clear();

    if (decoder.peekType(Tlv.Exclude, endOffset))
      decodeExclude(interest.getExclude(), decoder, copy);
    else
      interest.getExclude().clear();

    interest.setChildSelector((int)decoder.readOptionalNonNegativeIntegerTlv
      (Tlv.ChildSelector, endOffset));
    interest.setMustBeFresh(decoder.readBooleanTlv(Tlv.MustBeFresh, endOffset));

    decoder.finishNestedTlvs(endOffset);
  }

  private static void
  encodeExclude(Exclude exclude, TlvEncoder encoder)
  {
    int saveLength = encoder.getLength();

    // TODO: Do we want to order the components (except for ANY)?
    // Encode the entries backwards.
    for (int i = exclude.size() - 1; i >= 0; --i) {
      Exclude.Entry entry = exclude.get(i);

      if (entry.getType() == Exclude.Type.ANY)
        encoder.writeTypeAndLength(Tlv.Any, 0);
      else
        encodeNameComponent(entry.getComponent(), encoder);
    }

    encoder.writeTypeAndLength(Tlv.Exclude, encoder.getLength() - saveLength);
  }

  private static void
  decodeExclude
    (Exclude exclude, TlvDecoder decoder, boolean copy) throws EncodingException
  {
    int endOffset = decoder.readNestedTlvsStart(Tlv.Exclude);

    exclude.clear();
    while (decoder.getOffset() < endOffset) {
      if (decoder.peekType(Tlv.Any, endOffset)) {
        // Read past the Any TLV.
        decoder.readBooleanTlv(Tlv.Any, endOffset);
        exclude.appendAny();
      }
      else
        exclude.appendComponent(decodeNameComponent(decoder, copy));
    }

    decoder.finishNestedTlvs(endOffset);
  }

  private static void
  encodeKeyLocator(int type, KeyLocator keyLocator, TlvEncoder encoder)
  {
    int saveLength = encoder.getLength();

    // Encode backwards.
    if (keyLocator.getType() != KeyLocatorType.NONE) {
      if (keyLocator.getType() == KeyLocatorType.KEYNAME)
        encodeName(keyLocator.getKeyName(), new int[1], new int[1], encoder);
      else if (keyLocator.getType() == KeyLocatorType.KEY_LOCATOR_DIGEST &&
               keyLocator.getKeyData().size() > 0)
        encoder.writeBlobTlv(Tlv.KeyLocatorDigest, keyLocator.getKeyData().buf());
      else
        throw new Error("Unrecognized KeyLocatorType " + keyLocator.getType());
    }

    encoder.writeTypeAndLength(type, encoder.getLength() - saveLength);
  }

  private static void
  decodeKeyLocator
    (int expectedType, KeyLocator keyLocator, TlvDecoder decoder, boolean copy)
    throws EncodingException
  {
    int endOffset = decoder.readNestedTlvsStart(expectedType);

    keyLocator.clear();

    if (decoder.getOffset() == endOffset)
      // The KeyLocator is omitted, so leave the fields as none.
      return;

    if (decoder.peekType(Tlv.Name, endOffset)) {
      // KeyLocator is a Name.
      keyLocator.setType(KeyLocatorType.KEYNAME);
      decodeName(keyLocator.getKeyName(), new int[1], new int[1], decoder, copy);
    }
    else if (decoder.peekType(Tlv.KeyLocatorDigest, endOffset)) {
      // KeyLocator is a KeyLocatorDigest.
      keyLocator.setType(KeyLocatorType.KEY_LOCATOR_DIGEST);
      keyLocator.setKeyData
        (new Blob(decoder.readBlobTlv(Tlv.KeyLocatorDigest), copy));
    }
    else
      throw new EncodingException
        ("decodeKeyLocator: Unrecognized key locator type");

    decoder.finishNestedTlvs(endOffset);
  }

  private static void
  encodeValidityPeriod(ValidityPeriod validityPeriod, TlvEncoder encoder)
  {
    int saveLength = encoder.getLength();

    // Encode backwards.
    encoder.writeBlobTlv(Tlv.ValidityPeriod_NotAfter,
      new Blob(Schedule.toIsoString(validityPeriod.getNotAfter())).buf());
    encoder.writeBlobTlv(Tlv.ValidityPeriod_NotBefore,
      new Blob(Schedule.toIsoString(validityPeriod.getNotBefore())).buf());

    encoder.writeTypeAndLength
      (Tlv.ValidityPeriod_ValidityPeriod, encoder.getLength() - saveLength);
  }

  private static void
  decodeValidityPeriod
    (ValidityPeriod validityPeriod, TlvDecoder decoder) throws EncodingException
  {
    int endOffset = decoder.readNestedTlvsStart(Tlv.ValidityPeriod_ValidityPeriod);

    validityPeriod.clear();

    // Set copy false since we just immediately get the string.
    Blob isoString = new Blob
      (decoder.readBlobTlv(Tlv.ValidityPeriod_NotBefore), false);
    double notBefore = Schedule.fromIsoString("" + isoString);
    isoString = new Blob
      (decoder.readBlobTlv(Tlv.ValidityPeriod_NotAfter), false);
    double notAfter = Schedule.fromIsoString("" + isoString);

    validityPeriod.setPeriod(notBefore, notAfter);

    decoder.finishNestedTlvs(endOffset);
  }

  /**
   * An internal method to encode signature as the appropriate form of
   * SignatureInfo in NDN-TLV.
   * @param signature An object of a subclass of Signature to encode.
   * @param encoder The TlvEncoder to receive the encoding.
   */
  private static void
  encodeSignatureInfo(Signature signature, TlvEncoder encoder)
  {
    if (signature instanceof GenericSignature) {
      // Handle GenericSignature separately since it has the entire encoding.
      Blob encoding = ((GenericSignature)signature).getSignatureInfoEncoding();

      // Do a test decoding to sanity check that it is valid TLV.
      try {
        TlvDecoder decoder = new TlvDecoder(encoding.buf());
        int endOffset = decoder.readNestedTlvsStart(Tlv.SignatureInfo);
        decoder.readNonNegativeIntegerTlv(Tlv.SignatureType);
        // Skip unrecognized TLVs, even if they have a critical type code.
        decoder.finishNestedTlvs(endOffset, true);
      } catch (EncodingException ex) {
        throw new Error
          ("The GenericSignature encoding is not a valid NDN-TLV SignatureInfo: " +
           ex.getMessage());
      }

      encoder.writeBuffer(encoding.buf());
      return;
    }

    int saveLength = encoder.getLength();

    // Encode backwards.
    if (signature instanceof Sha256WithRsaSignature) {
      if (((Sha256WithRsaSignature)signature).getValidityPeriod().hasPeriod())
        encodeValidityPeriod
          (((Sha256WithRsaSignature)signature).getValidityPeriod(), encoder);
      encodeKeyLocator
        (Tlv.KeyLocator, ((Sha256WithRsaSignature)signature).getKeyLocator(),
         encoder);
      encoder.writeNonNegativeIntegerTlv
        (Tlv.SignatureType, Tlv.SignatureType_SignatureSha256WithRsa);
    }
    else if (signature instanceof Sha256WithEcdsaSignature) {
      if (((Sha256WithEcdsaSignature)signature).getValidityPeriod().hasPeriod())
        encodeValidityPeriod
          (((Sha256WithEcdsaSignature)signature).getValidityPeriod(), encoder);
      encodeKeyLocator
        (Tlv.KeyLocator, ((Sha256WithEcdsaSignature)signature).getKeyLocator(),
         encoder);
      encoder.writeNonNegativeIntegerTlv
        (Tlv.SignatureType, Tlv.SignatureType_SignatureSha256WithEcdsa);
    }
    else if (signature instanceof HmacWithSha256Signature) {
      encodeKeyLocator
        (Tlv.KeyLocator, ((HmacWithSha256Signature)signature).getKeyLocator(),
         encoder);
      encoder.writeNonNegativeIntegerTlv
        (Tlv.SignatureType, Tlv.SignatureType_SignatureHmacWithSha256);
    }
    else if (signature instanceof DigestSha256Signature)
      encoder.writeNonNegativeIntegerTlv
        (Tlv.SignatureType, Tlv.SignatureType_DigestSha256);
    else
      throw new Error("encodeSignatureInfo: Unrecognized Signature object type");

    encoder.writeTypeAndLength
      (Tlv.SignatureInfo, encoder.getLength() - saveLength);
  }

  private static void
  decodeSignatureInfo
    (SignatureHolder signatureHolder, TlvDecoder decoder, boolean copy)
    throws EncodingException
  {
    int beginOffset = decoder.getOffset();
    int endOffset = decoder.readNestedTlvsStart(Tlv.SignatureInfo);

    int signatureType = (int)decoder.readNonNegativeIntegerTlv(Tlv.SignatureType);
    if (signatureType == Tlv.SignatureType_SignatureSha256WithRsa) {
        signatureHolder.setSignature(new Sha256WithRsaSignature());
        // Modify the holder's signature object because if we create an object
        //   and set it, then the holder will have to copy all the fields.
        Sha256WithRsaSignature signatureInfo =
          (Sha256WithRsaSignature)signatureHolder.getSignature();
        decodeKeyLocator
          (Tlv.KeyLocator, signatureInfo.getKeyLocator(), decoder, copy);
        if (decoder.peekType(Tlv.ValidityPeriod_ValidityPeriod, endOffset))
          decodeValidityPeriod(signatureInfo.getValidityPeriod(), decoder);
    }
    else if (signatureType == Tlv.SignatureType_SignatureSha256WithEcdsa) {
        signatureHolder.setSignature(new Sha256WithEcdsaSignature());
        Sha256WithEcdsaSignature signatureInfo =
          (Sha256WithEcdsaSignature)signatureHolder.getSignature();
        decodeKeyLocator
          (Tlv.KeyLocator, signatureInfo.getKeyLocator(), decoder, copy);
        if (decoder.peekType(Tlv.ValidityPeriod_ValidityPeriod, endOffset))
          decodeValidityPeriod(signatureInfo.getValidityPeriod(), decoder);
    }
    else if (signatureType == Tlv.SignatureType_SignatureHmacWithSha256) {
        signatureHolder.setSignature(new HmacWithSha256Signature());
        HmacWithSha256Signature signatureInfo =
          (HmacWithSha256Signature)signatureHolder.getSignature();
        decodeKeyLocator
          (Tlv.KeyLocator, signatureInfo.getKeyLocator(), decoder, copy);
    }
    else if (signatureType == Tlv.SignatureType_DigestSha256)
        signatureHolder.setSignature(new DigestSha256Signature());
    else {
      signatureHolder.setSignature(new GenericSignature());
      GenericSignature signatureInfo =
        (GenericSignature)signatureHolder.getSignature();

      // Get the bytes of the SignatureInfo TLV.
      signatureInfo.setSignatureInfoEncoding
        (new Blob(decoder.getSlice(beginOffset, endOffset), copy), signatureType);
      // Skip the remaining TLVs now, allowing unrecognized critical type codes.
      decoder.finishNestedTlvs(endOffset, true);
    }

    decoder.finishNestedTlvs(endOffset);
  }

  private static void
  encodeMetaInfo(MetaInfo metaInfo, TlvEncoder encoder)
  {
    int saveLength = encoder.getLength();

    // Encode backwards.
    ByteBuffer finalBlockIdBuf = metaInfo.getFinalBlockId().getValue().buf();
    if (finalBlockIdBuf != null && finalBlockIdBuf.remaining() > 0) {
      // FinalBlockId has an inner NameComponent.
      int finalBlockIdSaveLength = encoder.getLength();
      encodeNameComponent(metaInfo.getFinalBlockId(), encoder);
      encoder.writeTypeAndLength
        (Tlv.FinalBlockId, encoder.getLength() - finalBlockIdSaveLength);
    }

    encoder.writeOptionalNonNegativeIntegerTlvFromDouble
      (Tlv.FreshnessPeriod, metaInfo.getFreshnessPeriod());
    if (!(metaInfo.getType() == ContentType.BLOB)) {
      // Not the default, so we need to encode the type.
      if (metaInfo.getType() == ContentType.LINK ||
          metaInfo.getType() == ContentType.KEY ||
          metaInfo.getType() == ContentType.NACK)
        // The ContentType enum is set up with the correct integer for
        // each NDN-TLV ContentType.
        encoder.writeNonNegativeIntegerTlv
          (Tlv.ContentType, metaInfo.getType().getNumericType());
      else if (metaInfo.getType() == ContentType.OTHER_CODE)
        encoder.writeNonNegativeIntegerTlv
          (Tlv.ContentType, metaInfo.getOtherTypeCode());
      else
        // We don't expect this to happen.
        throw new Error("unrecognized TLV ContentType");
    }

    encoder.writeTypeAndLength(Tlv.MetaInfo, encoder.getLength() - saveLength);
  }

  private static void
  decodeMetaInfo
    (MetaInfo metaInfo, TlvDecoder decoder, boolean copy) throws EncodingException
  {
    int endOffset = decoder.readNestedTlvsStart(Tlv.MetaInfo);

    // The ContentType enum is set up with the correct integer for each
    // NDN-TLV ContentType.
    int type = (int)decoder.readOptionalNonNegativeIntegerTlv
      (Tlv.ContentType, endOffset);
    if (type < 0 || type == ContentType.BLOB.getNumericType())
      // Default to BLOB if the value is omitted.
      metaInfo.setType(ContentType.BLOB);
    else if (type == ContentType.LINK.getNumericType())
      metaInfo.setType(ContentType.LINK);
    else if (type == ContentType.KEY.getNumericType())
      metaInfo.setType(ContentType.KEY);
    else if (type == ContentType.NACK.getNumericType())
      metaInfo.setType(ContentType.NACK);
    else {
      // Unrecognized content type.
      metaInfo.setType(ContentType.OTHER_CODE);
      metaInfo.setOtherTypeCode(type);
    }

    metaInfo.setFreshnessPeriod
      (decoder.readOptionalNonNegativeIntegerTlv(Tlv.FreshnessPeriod, endOffset));
    if (decoder.peekType(Tlv.FinalBlockId, endOffset)) {
      int finalBlockIdEndOffset = decoder.readNestedTlvsStart(Tlv.FinalBlockId);
      metaInfo.setFinalBlockId(decodeNameComponent(decoder, copy));
      decoder.finishNestedTlvs(finalBlockIdEndOffset);
    }
    else
      metaInfo.setFinalBlockId(null);

    decoder.finishNestedTlvs(endOffset);
  }

  private static void
  encodeControlParameters(ControlParameters controlParameters, TlvEncoder encoder)
  {
    int saveLength = encoder.getLength();

    // Encode backwards.
    encoder.writeOptionalNonNegativeIntegerTlvFromDouble
      (Tlv.ControlParameters_ExpirationPeriod,
       controlParameters.getExpirationPeriod());

    // Encode strategy
    if(controlParameters.getStrategy().size() != 0){
      int strategySaveLength = encoder.getLength();
      encodeName(controlParameters.getStrategy(), new int[1], new int[1],
        encoder);
      encoder.writeTypeAndLength(Tlv.ControlParameters_Strategy,
        encoder.getLength() - strategySaveLength);
    }

    // Encode ForwardingFlags
    int flags = controlParameters.getForwardingFlags().getNfdForwardingFlags();
    if (flags != new RegistrationOptions().getNfdForwardingFlags())
        // The flags are not the default value.
        encoder.writeNonNegativeIntegerTlv(Tlv.ControlParameters_Flags, flags);

    encoder.writeOptionalNonNegativeIntegerTlv
      (Tlv.ControlParameters_Cost, controlParameters.getCost());
    encoder.writeOptionalNonNegativeIntegerTlv
      (Tlv.ControlParameters_Origin, controlParameters.getOrigin());
    encoder.writeOptionalNonNegativeIntegerTlv
      (Tlv.ControlParameters_LocalControlFeature,
       controlParameters.getLocalControlFeature());

    // Encode URI
    if(controlParameters.getUri().length() != 0){
      encoder.writeBlobTlv(Tlv.ControlParameters_Uri,
        new Blob(controlParameters.getUri()).buf());
    }

    encoder.writeOptionalNonNegativeIntegerTlv
      (Tlv.ControlParameters_FaceId, controlParameters.getFaceId());

    // Encode name
    if (controlParameters.getName() != null) {
      encodeName(controlParameters.getName(), new int[1], new int[1], encoder);
    }

    encoder.writeTypeAndLength
      (Tlv.ControlParameters_ControlParameters, encoder.getLength() - saveLength);
  }

  private static void
  decodeControlParameters
    (ControlParameters controlParameters, TlvDecoder decoder, boolean copy)
    throws EncodingException
  {
    controlParameters.clear();

    int endOffset = decoder.readNestedTlvsStart
      (Tlv.ControlParameters_ControlParameters);

    // decode name
    if (decoder.peekType(Tlv.Name, endOffset)) {
      Name name = new Name();
      decodeName(name, new int[1], new int[1], decoder, copy);
      controlParameters.setName(name);
    }

    // decode face ID
    controlParameters.setFaceId
      ((int)decoder.readOptionalNonNegativeIntegerTlv
       (Tlv.ControlParameters_FaceId, endOffset));

    // decode URI
    if (decoder.peekType(Tlv.ControlParameters_Uri, endOffset)) {
      // Set copy false since we just immediately get the string.
      Blob uri = new Blob
        (decoder.readOptionalBlobTlv(Tlv.ControlParameters_Uri, endOffset), false);
      controlParameters.setUri("" + uri);
    }

    decoder.skipOptionalTlv(Tlv.ControlParameters_LocalUri, endOffset);

    // decode integers
    controlParameters.setLocalControlFeature((int) decoder.
      readOptionalNonNegativeIntegerTlv(
        Tlv.ControlParameters_LocalControlFeature, endOffset));
    controlParameters.setOrigin((int) decoder.
      readOptionalNonNegativeIntegerTlv(Tlv.ControlParameters_Origin,
        endOffset));
    controlParameters.setCost((int) decoder.readOptionalNonNegativeIntegerTlv(
      Tlv.ControlParameters_Cost, endOffset));

    decoder.skipOptionalTlv(Tlv.ControlParameters_Capacity, endOffset);
    decoder.skipOptionalTlv(Tlv.ControlParameters_Count, endOffset);
    decoder.skipOptionalTlv
      (Tlv.ControlParameters_BaseCongestionMarkingInterval, endOffset);
    decoder.skipOptionalTlv
      (Tlv.ControlParameters_DefaultCongestionThreshold, endOffset);
    decoder.skipOptionalTlv(Tlv.ControlParameters_Mtu, endOffset);

    // set forwarding flags
    if (decoder.peekType(Tlv.ControlParameters_Flags, endOffset)) {
      RegistrationOptions flags = new RegistrationOptions();
      flags.setNfdForwardingFlags((int) decoder.
        readNonNegativeIntegerTlv(Tlv.ControlParameters_Flags));
      controlParameters.setForwardingFlags(flags);
    }

    decoder.skipOptionalTlv(Tlv.ControlParameters_Mask, endOffset);

    // decode strategy
    if (decoder.peekType(Tlv.ControlParameters_Strategy, endOffset)) {
      int strategyEndOffset = decoder.readNestedTlvsStart
        (Tlv.ControlParameters_Strategy);
      decodeName
        (controlParameters.getStrategy(), new int[1], new int[1], decoder, copy);
      decoder.finishNestedTlvs(strategyEndOffset);
    }

    // decode expiration period
    controlParameters.setExpirationPeriod
      (decoder.readOptionalNonNegativeIntegerTlv
       (Tlv.ControlParameters_ExpirationPeriod, endOffset));

    // The NFD management protocol is at the application-layer, so skip
    // unrecognized type codes.
    decoder.finishNestedTlvs(endOffset, true);
  }

  /**
   * Encode delegationSet to the encoder as a sequence of NDN-TLV Delegation.
   * Note that the sequence of Delegation does not have an outer TLV
   * type and length because (when used in a Link object) it is intended to use
   * the type and length of a Data packet's Content.
   * @param delegationSet The DelegationSet object to encode.
   * @param encoder The TlvEncoder to receive the encoding.
   */
  protected static void
  encodeDelegationSet(DelegationSet delegationSet, TlvEncoder encoder)
  {
    // Encode backwards.
    for (int i = delegationSet.size() - 1; i >= 0; --i) {
      int saveLength = encoder.getLength();

      encodeName(delegationSet.get(i).getName(), new int[1], new int[1], encoder);
      encoder.writeNonNegativeIntegerTlv
        (Tlv.Link_Preference, delegationSet.get(i).getPreference());

      encoder.writeTypeAndLength
        (Tlv.Link_Delegation, encoder.getLength() - saveLength);
    }
  }

  /**
   * Decode input as a sequence of NDN-TLV Delegation and set the fields of the
   * delegationSet object. Note that the sequence of Delegation does not have an
   * outer TLV type and length because (when used in a Link object) it is
   * intended to use the type and length of a Data packet's Content.
   * @param delegationSet The DelegationSet object whose fields are updated.
   * @param endOffset Decode elements up to endOffset in the input. This does
   * not call finishNestedTlvs.
   * @param decoder The decoder with the input to decode.
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding.
   */
  private static void
  decodeDelegationSet
    (DelegationSet delegationSet, int endOffset, TlvDecoder decoder, boolean copy)
    throws EncodingException
  {
    delegationSet.clear();
    while (decoder.getOffset() < endOffset) {
      decoder.readTypeAndLength(Tlv.Link_Delegation);
      int preference = (int)decoder.readNonNegativeIntegerTlv(Tlv.Link_Preference);
      Name name = new Name();
      decodeName(name, new int[1], new int[1], decoder, copy);

      // Add unsorted to preserve the order in the encoding.
      delegationSet.addUnsorted(preference, name);
    }
  }

  /**
   * Encode interest in NDN-TLV format v0.3 and return the encoding.
   * @param interest The Interest object to encode.
   * @param signedPortionBeginOffset Return the offset in the encoding of the
   * beginning 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).
   * @param signedPortionEndOffset Return 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).
   * @return A Blob containing the encoding.
   */
  protected static Blob
  encodeInterestV03
    (Interest interest, int[] signedPortionBeginOffset, int[] signedPortionEndOffset)
  {
    // TODO: Throw an exception if the interest speficies V02 fields.

    TlvEncoder encoder = new TlvEncoder(256);
    int saveLength = encoder.getLength();

    // Encode backwards.
    encoder.writeOptionalBlobTlv
      (Tlv.ApplicationParameters, interest.getApplicationParameters().buf());
    // TODO: HopLimit.
    encoder.writeOptionalNonNegativeIntegerTlvFromDouble
      (Tlv.InterestLifetime, interest.getInterestLifetimeMilliseconds());

    // Encode the Nonce as 4 bytes.
    if (interest.getNonce().size() == 0)
    {
      // This is the most common case. Generate a nonce.
      ByteBuffer nonce = ByteBuffer.allocate(4);
      random_.nextBytes(nonce.array());
      encoder.writeBlobTlv(Tlv.Nonce, nonce);
    }
    else if (interest.getNonce().size() < 4) {
      ByteBuffer nonce = ByteBuffer.allocate(4);
      // Copy existing nonce bytes.
      nonce.put(interest.getNonce().buf());

      // Generate random bytes for remaining bytes in the nonce.
      for (int i = 0; i < 4 - interest.getNonce().size(); ++i)
        nonce.put((byte)random_.nextInt());

      nonce.flip();
      encoder.writeBlobTlv(Tlv.Nonce, nonce);
    }
    else if (interest.getNonce().size() == 4)
      // Use the nonce as-is.
      encoder.writeBlobTlv(Tlv.Nonce, interest.getNonce().buf());
    else
    {
      // Truncate.
      ByteBuffer nonce = interest.getNonce().buf();
      // buf() returns a new ByteBuffer, so we can change its limit.
      nonce.limit(nonce.position() + 4);
      encoder.writeBlobTlv(Tlv.Nonce, nonce);
    }

    if (interest.getForwardingHint().size() > 0) {
      if (interest.getSelectedDelegationIndex() >= 0)
        throw new Error
          ("An Interest may not have a selected delegation when encoding a forwarding hint");
      if (interest.hasLink())
        throw new Error
          ("An Interest may not have a link object when encoding a forwarding hint");

      int forwardingHintSaveLength = encoder.getLength();
      encodeDelegationSet(interest.getForwardingHint(), encoder);
      encoder.writeTypeAndLength
        (Tlv.ForwardingHint, encoder.getLength() - forwardingHintSaveLength);
    }

    if (interest.getMustBeFresh())
      encoder.writeTypeAndLength(Tlv.MustBeFresh, 0);
    if (interest.getCanBePrefix())
      encoder.writeTypeAndLength(Tlv.CanBePrefix, 0);

    int[] tempSignedPortionBeginOffset = new int[1];
    int[] tempSignedPortionEndOffset = new int[1];
    encodeName
      (interest.getName(), tempSignedPortionBeginOffset,
       tempSignedPortionEndOffset, encoder);
    int signedPortionBeginOffsetFromBack =
      encoder.getLength() - tempSignedPortionBeginOffset[0];
    int signedPortionEndOffsetFromBack =
      encoder.getLength() - tempSignedPortionEndOffset[0];

    encoder.writeTypeAndLength(Tlv.Interest, encoder.getLength() - saveLength);
    signedPortionBeginOffset[0] =
      encoder.getLength() - signedPortionBeginOffsetFromBack;
    signedPortionEndOffset[0] =
      encoder.getLength() - signedPortionEndOffsetFromBack;

    return new Blob(encoder.getOutput(), false);
  }

  /**
   * Decode input as an Interest in NDN-TLV format v0.3 and set the fields of
   * the Interest object. This private method is called if the main
   * decodeInterest fails to decode as v0.2. This ignores HopLimit and
   * interprets CanBePrefix using MaxSuffixComponents.
   * @param interest The Interest object whose fields are updated.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param signedPortionBeginOffset Return the offset in the encoding of the
   * beginning 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).
   * @param signedPortionEndOffset Return 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).
   * @param copy If true, copy from the input when making new Blob values. If
   * false, then Blob values share memory with the input, which must remain
   * unchanged while the Blob values are used.
   * @throws EncodingException For invalid encoding.
   */
  private static void
  decodeInterestV03
    (Interest interest, ByteBuffer input, int[] signedPortionBeginOffset,
     int[] signedPortionEndOffset, boolean copy) throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);

    int endOffset = decoder.readNestedTlvsStart(Tlv.Interest);
    decodeName
      (interest.getName(), signedPortionBeginOffset,signedPortionEndOffset,
       decoder, copy);

    // In v0.2 semantics, this calls setMaxSuffixComponents.
    interest.setCanBePrefix(decoder.readBooleanTlv(Tlv.CanBePrefix, endOffset));

    interest.setMustBeFresh(decoder.readBooleanTlv(Tlv.MustBeFresh, endOffset));

    if (decoder.peekType(Tlv.ForwardingHint, endOffset)) {
      int forwardingHintEndOffset = decoder.readNestedTlvsStart
        (Tlv.ForwardingHint);
      decodeDelegationSet
        (interest.getForwardingHint(), forwardingHintEndOffset, decoder, copy);
      decoder.finishNestedTlvs(forwardingHintEndOffset);
    }
    else
      interest.getForwardingHint().clear();

    ByteBuffer nonce = decoder.readOptionalBlobTlv(Tlv.Nonce, endOffset);
    interest.setInterestLifetimeMilliseconds
      (decoder.readOptionalNonNegativeIntegerTlv(Tlv.InterestLifetime, endOffset));

    // Clear the unused fields.
    interest.setMinSuffixComponents(-1);
    interest.getKeyLocator().clear();
    interest.getExclude().clear();
    interest.setChildSelector(-1);
    interest.unsetLink();
    interest.setSelectedDelegationIndex(-1);

    // Ignore the HopLimit.
    decoder.readOptionalBlobTlv(Tlv.HopLimit, endOffset);

    interest.setApplicationParameters
      (new Blob(decoder.readOptionalBlobTlv
                (Tlv.ApplicationParameters, endOffset), copy));

    decoder.readOptionalBlobTlv(Tlv.ApplicationParameters, endOffset);

    // Set the nonce last because setting other interest fields clears it.
    interest.setNonce(new Blob(nonce, copy));

    decoder.finishNestedTlvs(endOffset);
  }

  private static final Random random_ = new Random();
  private static Tlv0_3WireFormat instance_ = new Tlv0_3WireFormat();
  protected static boolean didCanBePrefixWarning_ = false;
}
