/**
 * Copyright (C) 2014-2015 Regents of the University of California.
 * Authors:
 *		Jeff Thompson <jefft0@remap.ucla.edu>
 *		Rafael Teixeira <monoman@gmail.com>
 *
 * 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.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Net.NamedData.Encoding
{
	using System.Security.Cryptography;
	using Net.NamedData;
	using Net.NamedData.Encoding.Tlv;
	using Net.NamedData.Util;
	/**
	 * A Tlv0_1_1WireFormat : the WireFormat interface for encoding and
	 * decoding with the NDN-TLV wire format, version 0.1.1.
	 */
	public class Tlv0_1_1WireFormat : 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();
			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 interest 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.
		 * @ For invalid encoding.
		 */
		public void
		decodeName(Name name, ByteBuffer input)
		{
			TlvDecoder decoder = new TlvDecoder(input);
			decodeName(name, new int[1], new int[1], decoder);
		}
		/**
		 * 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)
		{
			TlvEncoder encoder = new TlvEncoder();
			int saveLength = encoder.getLength();
			// Encode backwards.
			encoder.writeOptionalNonNegativeIntegerTlvFromDouble
			  (TlvTypeCodes.InterestLifetime, interest.getInterestLifetimeMilliseconds());
			encoder.writeOptionalNonNegativeIntegerTlv(TlvTypeCodes.Scope, interest.getScope());
			// 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(TlvTypeCodes.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(TlvTypeCodes.Nonce, nonce);
			} else if (interest.getNonce().size() == 4)
				// Use the nonce as-is.
				encoder.writeBlobTlv(TlvTypeCodes.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(TlvTypeCodes.Nonce, nonce);
			}
			encodeSelectors(interest, encoder);
			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(TlvTypeCodes.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 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).
		 * @ For invalid encoding.
		 */
		public void
		decodeInterest
		  (Interest interest, ByteBuffer input, int[] signedPortionBeginOffset,
		   int[] signedPortionEndOffset)
		{
			TlvDecoder decoder = new TlvDecoder(input);
			int endOffset = decoder.readNestedTlvsStart(TlvTypeCodes.Interest);
			decodeName
			  (interest.getName(), signedPortionBeginOffset, signedPortionEndOffset,
			   decoder);
			if (decoder.peekType(TlvTypeCodes.Selectors, endOffset))
				decodeSelectors(interest, decoder);
			// Require a Nonce, but don't force it to be 4 bytes.
			ByteBuffer nonce = decoder.readBlobTlv(TlvTypeCodes.Nonce);
			interest.setScope((int)decoder.readOptionalNonNegativeIntegerTlv
			  (TlvTypeCodes.Scope, endOffset));
			interest.setInterestLifetimeMilliseconds
			  (decoder.readOptionalNonNegativeIntegerTlv(TlvTypeCodes.InterestLifetime, endOffset));
			// Set the nonce last because setting other interest fields clears it.
			interest.setNonce(new Blob(nonce, true));
			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
			  (TlvTypeCodes.SignatureValue, (data.getSignature()).getSignature().buf());
			int signedPortionEndOffsetFromBack = encoder.getLength();
			encodeSignatureInfo(data.getSignature(), encoder);
			encoder.writeBlobTlv(TlvTypeCodes.Content, data.getContent().buf());
			encodeMetaInfo(data.getMetaInfo(), encoder);
			encodeName(data.getName(), new int[1], new int[1], encoder);
			int signedPortionBeginOffsetFromBack = encoder.getLength();
			encoder.writeTypeAndLength(TlvTypeCodes.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.
		 * @ For invalid encoding.
		 */
		public void
		decodeData
		  (Data data, ByteBuffer input, int[] signedPortionBeginOffset,
		   int[] signedPortionEndOffset)
		{
			TlvDecoder decoder = new TlvDecoder(input);
			int endOffset = decoder.readNestedTlvsStart(TlvTypeCodes.Data);
			signedPortionBeginOffset[0] = decoder.getOffset();
			decodeName(data.getName(), new int[1], new int[1], decoder);
			decodeMetaInfo(data.getMetaInfo(), decoder);
			data.setContent(new Blob(decoder.readBlobTlv(TlvTypeCodes.Content), true));
			decodeSignatureInfo(data, decoder);
			signedPortionEndOffset[0] = decoder.getOffset();
			data.getSignature().setSignature
			  (new Blob(decoder.readBlobTlv(TlvTypeCodes.SignatureValue), true));
			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);
			int saveLength = encoder.getLength();
			// Encode backwards.
			encoder.writeOptionalNonNegativeIntegerTlvFromDouble
			  (TlvTypeCodes.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(TlvTypeCodes.ControlParameters_Strategy,
				  encoder.getLength() - strategySaveLength);
			}
			// Encode ForwardingFlags
			int flags = controlParameters.getForwardingFlags().getNfdForwardingFlags();
			if (flags != new ForwardingFlags().getNfdForwardingFlags())
				// The flags are not the default value.
				encoder.writeNonNegativeIntegerTlv(TlvTypeCodes.ControlParameters_Flags, flags);
			encoder.writeOptionalNonNegativeIntegerTlv
			  (TlvTypeCodes.ControlParameters_Cost, controlParameters.getCost());
			encoder.writeOptionalNonNegativeIntegerTlv
			  (TlvTypeCodes.ControlParameters_Origin, controlParameters.getOrigin());
			encoder.writeOptionalNonNegativeIntegerTlv
			  (TlvTypeCodes.ControlParameters_LocalControlFeature,
			   controlParameters.getLocalControlFeature());
			// Encode URI
			if (!controlParameters.getUri().isEmpty()) {
				encoder.writeBlobTlv(TlvTypeCodes.ControlParameters_Uri,
				  new Blob(controlParameters.getUri()).buf());
			}
			encoder.writeOptionalNonNegativeIntegerTlv
			  (TlvTypeCodes.ControlParameters_FaceId, controlParameters.getFaceId());
			// Encode name
			if (controlParameters.getName().size() != 0) {
				encodeName(controlParameters.getName(), new int[1], new int[1], encoder);
			}
			encoder.writeTypeAndLength
			  (TlvTypeCodes.ControlParameters_ControlParameters, encoder.getLength() - saveLength);
			return new Blob(encoder.getOutput(), false);
		}
		/**
		 * Decode controlParameters in NDN-TLV and return the encoding.
		 * @param controlParameters The ControlParameters object to Encode.
		 * @param input
		 * @ For invalid encoding
		 */
		public void
		decodeControlParameters(ControlParameters controlParameters,
		  ByteBuffer input)
		{
			TlvDecoder decoder = new TlvDecoder(input);
			int endOffset = decoder.
			  readNestedTlvsStart(TlvTypeCodes.ControlParameters_ControlParameters);
			// Decode name
			if (decoder.peekType(TlvTypeCodes.Name, endOffset)) {
				Name name = new Name();
				decodeName(name, new int[1], new int[1], decoder);
				controlParameters.setName(name);
			}
			// Decode face ID
			controlParameters.setFaceId((int)decoder.readOptionalNonNegativeIntegerTlv(TlvTypeCodes.ControlParameters_FaceId, endOffset));
			// Decode URI
			if (decoder.peekType(TlvTypeCodes.ControlParameters_Uri, endOffset)) {
				Blob uri = new Blob(decoder.readOptionalBlobTlv(TlvTypeCodes.ControlParameters_Uri, endOffset), true);
				controlParameters.setUri(uri.toString());
			}
			// Decode integers
			controlParameters.setLocalControlFeature((int)decoder.
			  readOptionalNonNegativeIntegerTlv(
				TlvTypeCodes.ControlParameters_LocalControlFeature, endOffset));
			controlParameters.setOrigin((int)decoder.
			  readOptionalNonNegativeIntegerTlv(TlvTypeCodes.ControlParameters_Origin,
				endOffset));
			controlParameters.setCost((int)decoder.readOptionalNonNegativeIntegerTlv(
			  TlvTypeCodes.ControlParameters_Cost, endOffset));
			// set forwarding flags
			ForwardingFlags flags = new ForwardingFlags();
			flags.setNfdForwardingFlags((int)decoder.
			  readOptionalNonNegativeIntegerTlv(TlvTypeCodes.ControlParameters_Flags,
				endOffset));
			controlParameters.setForwardingFlags(flags);
			// Decode strategy
			if (decoder.peekType(TlvTypeCodes.ControlParameters_Strategy, endOffset)) {
				int strategyEndOffset = decoder.readNestedTlvsStart(TlvTypeCodes.ControlParameters_Strategy);
				decodeName(controlParameters.getStrategy(), new int[1], new int[1], decoder);
				decoder.finishNestedTlvs(strategyEndOffset);
			}
			// Decode expiration period
			controlParameters.setExpirationPeriod((int)decoder.readOptionalNonNegativeIntegerTlv(TlvTypeCodes.ControlParameters_ExpirationPeriod, endOffset));
			decoder.finishNestedTlvs(endOffset);
		}
		/**
		 * Encode signature as a SignatureInfo in NDN-TLV and return the encoding.
		 * @param signature An object of a subclass of AbstractSignature to Encode.
		 * @return A Blob containing the encoding.
		 */
		public Blob
		encodeSignatureInfo(AbstractSignature signature)
		{
			TlvEncoder encoder = new TlvEncoder(256);
			encodeSignatureInfo(signature, encoder);
			return new Blob(encoder.getOutput(), false);
		}
		private class SimpleSignatureHolder : ISignatureHolder
		{
			public ISignatureHolder setSignature(AbstractSignature signature)
			{
				signature_ = signature;
				return this;
			}
			public AbstractSignature getSignature()
			{
				return signature_;
			}
			private AbstractSignature 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 AbstractSignature.
		 * @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.
		 * @return A new object which is a subclass of AbstractSignature.
		 * @ For invalid encoding.
		 */
		public AbstractSignature
		decodeSignatureInfoAndValue
		  (ByteBuffer signatureInfo, ByteBuffer signatureValue)
		{
			// Use a ISignatureHolder to imitate a Data object for _decodeSignatureInfo.
			SimpleSignatureHolder signatureHolder = new SimpleSignatureHolder();
			TlvDecoder decoder = new TlvDecoder(signatureInfo);
			decodeSignatureInfo(signatureHolder, decoder);
			decoder = new TlvDecoder(signatureValue);
			signatureHolder.getSignature().setSignature
			  (new Blob(decoder.readBlobTlv(TlvTypeCodes.SignatureValue), true));
			return signatureHolder.getSignature();
		}
		/**
		 * Encode the signatureValue in the AbstractSignature object as a SignatureValue (the
		 * signature bits) in NDN-TLV and return the encoding.
		 * @param signature An object of a subclass of AbstractSignature with the signature
		 * value to Encode.
		 * @return A Blob containing the encoding.
		 */
		public Blob
		encodeSignatureValue(AbstractSignature signature)
		{
			TlvEncoder encoder = new TlvEncoder(256);
			encoder.writeBlobTlv(TlvTypeCodes.SignatureValue, signature.getSignature().buf());
			return new Blob(encoder.getOutput(), false);
		}
		/**
		 * Get a singleton instance of a Tlv1_0a2WireFormat.  To always use the
		 * preferred version NDN-TLV, you should use TlvWireFormat.get().
		 * @return The singleton instance.
		 */
		public static Tlv0_1_1WireFormat
		get()
		{
			return instance_;
		}
		/**
		 * Encode the name 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.
		 */
		private 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) {
				encoder.writeBlobTlv(TlvTypeCodes.NameComponent, name.get(i).getValue().buf());
				if (i == name.size() - 1)
					signedPortionEndOffsetFromBack = encoder.getLength();
			}
			int signedPortionBeginOffsetFromBack = encoder.getLength();
			encoder.writeTypeAndLength(TlvTypeCodes.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.
		 * @
		 */
		private static void
		decodeName
		  (Name name, int[] signedPortionBeginOffset, int[] signedPortionEndOffset,
		   TlvDecoder decoder)
		{
			name.Clear();
			int endOffset = decoder.readNestedTlvsStart(TlvTypeCodes.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(new Blob(decoder.readBlobTlv(TlvTypeCodes.NameComponent), true));
			}
			decoder.finishNestedTlvs(endOffset);
		}
		/**
		 * Encode the interest selectors.  If no selectors are written, do not output
		 * a  Selectors TLV.
		 */
		private static void
		encodeSelectors(Interest interest, TlvEncoder encoder)
		{
			int saveLength = encoder.getLength();
			// Encode backwards.
			if (interest.getMustBeFresh())
				encoder.writeTypeAndLength(TlvTypeCodes.MustBeFresh, 0);
			encoder.writeOptionalNonNegativeIntegerTlv(
			  TlvTypeCodes.ChildSelector, interest.getChildSelector());
			if (interest.getExclude().size() > 0)
				encodeExclude(interest.getExclude(), encoder);
			if (interest.getKeyLocator().getType() != KeyLocatorType.NONE)
				encodeKeyLocator
				  (TlvTypeCodes.PublisherPublicKeyLocator, interest.getKeyLocator(), encoder);
			else {
				// There is no keyLocator. If there is a publisherPublicKeyDigest, then
				//   Encode as KEY_LOCATOR_DIGEST. (When we remove the deprecated
				//   publisherPublicKeyDigest, we don't need this.)
				if (interest.getPublisherPublicKeyDigest().getPublisherPublicKeyDigest().size() > 0) {
					int savePublisherPublicKeyDigestLength = encoder.getLength();
					encoder.writeBlobTlv
					  (TlvTypeCodes.KeyLocatorDigest,
					   interest.getPublisherPublicKeyDigest().getPublisherPublicKeyDigest().buf());
					encoder.writeTypeAndLength
					  (TlvTypeCodes.KeyLocator, encoder.getLength() - savePublisherPublicKeyDigestLength);
				}
			}
			encoder.writeOptionalNonNegativeIntegerTlv(
			  TlvTypeCodes.MaxSuffixComponents, interest.getMaxSuffixComponents());
			encoder.writeOptionalNonNegativeIntegerTlv(
			  TlvTypeCodes.MinSuffixComponents, interest.getMinSuffixComponents());
			// Only output the type and length if values were written.
			if (encoder.getLength() != saveLength)
				encoder.writeTypeAndLength(TlvTypeCodes.Selectors, encoder.getLength() - saveLength);
		}
		private static void
		decodeSelectors(Interest interest, TlvDecoder decoder)
		{
			int endOffset = decoder.readNestedTlvsStart(TlvTypeCodes.Selectors);
			interest.setMinSuffixComponents((int)decoder.readOptionalNonNegativeIntegerTlv
			  (TlvTypeCodes.MinSuffixComponents, endOffset));
			interest.setMaxSuffixComponents((int)decoder.readOptionalNonNegativeIntegerTlv
			  (TlvTypeCodes.MaxSuffixComponents, endOffset));
			// Initially set publisherPublicKeyDigest to none.
			interest.getPublisherPublicKeyDigest().clear();
			if (decoder.peekType(TlvTypeCodes.PublisherPublicKeyLocator, endOffset)) {
				decodeKeyLocator
				  (TlvTypeCodes.PublisherPublicKeyLocator, interest.getKeyLocator(), decoder);
				if (interest.getKeyLocator().getType() == KeyLocatorType.KEY_LOCATOR_DIGEST) {
					// For backwards compatibility, also set the publisherPublicKeyDigest.
					interest.getPublisherPublicKeyDigest().setPublisherPublicKeyDigest
					  (interest.getKeyLocator().getKeyData());
				}
			} else
				interest.getKeyLocator().clear();
			if (decoder.peekType(TlvTypeCodes.Exclude, endOffset))
				decodeExclude(interest.getExclude(), decoder);
			else
				interest.getExclude().clear();
			interest.setChildSelector((int)decoder.readOptionalNonNegativeIntegerTlv
			  (TlvTypeCodes.ChildSelector, endOffset));
			interest.setMustBeFresh(decoder.readBooleanTlv(TlvTypeCodes.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(TlvTypeCodes.Any, 0);
				else
					encoder.writeBlobTlv
					  (TlvTypeCodes.NameComponent, entry.getComponent().getValue().buf());
			}
			encoder.writeTypeAndLength(TlvTypeCodes.Exclude, encoder.getLength() - saveLength);
		}
		private static void
		decodeExclude(Exclude exclude, TlvDecoder decoder)
		{
			int endOffset = decoder.readNestedTlvsStart(TlvTypeCodes.Exclude);
			exclude.clear();
			while (true) {
				if (decoder.peekType(TlvTypeCodes.NameComponent, endOffset))
					exclude.appendComponent(new Name.Component
					  (new Blob(decoder.readBlobTlv(TlvTypeCodes.NameComponent), true)));
				else if (decoder.readBooleanTlv(TlvTypeCodes.Any, endOffset))
					exclude.appendAny();
				else
					// Else no more entries.
					break;
			}
			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(TlvTypeCodes.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)
		{
			int endOffset = decoder.readNestedTlvsStart(expectedType);
			keyLocator.clear();


Answer: 			if (decoder.getOffset() == endOffset)