All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.named_data.jndn.encoding.ProtobufTlv Maven / Gradle / Ivy

Go to download

jNDN is a new implementation of a Named Data Networking client library written in Java. It is wire format compatible with the new NDN-TLV encoding, with NDNx and PARC's CCNx.

There is a newer version: 0.25
Show newest version
/**
 * Copyright (C) 2014-2017 Regents of the University of California.
 * @author: Jeff Thompson 
 *
 * 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 .
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

package net.named_data.jndn.encoding;

import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.Type;
import com.google.protobuf.Message;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.List;
import net.named_data.jndn.Name;
import net.named_data.jndn.encoding.tlv.TlvDecoder;
import net.named_data.jndn.encoding.tlv.TlvEncoder;
import net.named_data.jndn.util.Blob;

/**
 * ProtobufTlv has static methods to encode and decode an Protobuf Message
 * object as NDN-TLV. The Protobuf tag value is used as the TLV type code. A
 * Protobuf message is encoded/decoded as a nested TLV encoding. Protobuf types
 * uint32, uint64 and enum are encoded/decoded as TLV nonNegativeInteger. (It is
 * an error if an enum value is negative.) Protobuf types bytes and string are
 * encoded/decoded as TLV bytes. The Protobuf type bool is encoded/decoded as a
 * TLV boolean (a zero length value for True, omitted for False). Other Protobuf
 * types are an error.
 *
 * Protobuf has no "outer" message type, so you need to put your TLV message
 * inside an outer "typeless" message.
 */
public class ProtobufTlv {
  /**
   * Encode the Protobuf message object as NDN-TLV.
   * @param message The Protobuf message object. This calls
   * message.IsInitialized() to ensure that all required fields are present and
   * throws an exception if not.
   * @return The encoded buffer in a Blob object.
   */
  public static Blob
  encode(Message message)
  {
    if (!message.isInitialized())
      throw new Error
        ("Message fields are not initialized: " + message.getInitializationErrorString());

    TlvEncoder encoder = new TlvEncoder(256);

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

  /**
   * Decode the input as NDN-TLV and update the fields of the Protobuf message
   * object.
   * @param message The Protobuf Message builder object. This does not first
   * clear the object.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @throws EncodingException For invalid encoding.
   */
  public static void
  decode(Message.Builder message, ByteBuffer input) throws EncodingException
  {
    TlvDecoder decoder = new TlvDecoder(input);
    decodeMessageValue(message, decoder, input.remaining());
  }

  public static void
  decode(Message.Builder message, Blob input) throws EncodingException
  {
    decode(message, input.buf());
  }

  /**
   * Return a Name made from the component array in a Protobuf message object,
   * assuming that it was defined with "repeated bytes". For example:
   * message Name {
   *   repeated bytes component = 8;
   * }
   * (See the TestEncodeDecodeFibEntry example.)
   * @param nameMessage The Protobuf message object containing the "repeated
   * bytes" component array.
   * @return A new name.
   */
  public static Name
  toName(Message nameMessage)
  {
    Name name = new Name();
    Descriptor descriptor = nameMessage.getDescriptorForType();
    FieldDescriptor field = (FieldDescriptor)descriptor.getFields().get(0);

    for (int i = 0; i < nameMessage.getRepeatedFieldCount(field); ++i)
      name.append(new Blob
        (((ByteString)nameMessage.getRepeatedField(field, i)).asReadOnlyByteBuffer(),
         true));

    return name;
  }

  private static void
  encodeMessageValue(Message message, TlvEncoder encoder)
  {
    // Note: We can't use ListFields because it sorts by field number.
    Descriptor descriptor = message.getDescriptorForType();
    // Go in reverse so that we encode backwards.
    List fields = descriptor.getFields();
    for (int i = fields.size() - 1; i >= 0; --i) {
      FieldDescriptor field = (FieldDescriptor)fields.get(i);

      int tlvType = field.getNumber();

      int valueCount = 0;
      if (field.isRepeated())
        valueCount = message.getRepeatedFieldCount(field);
      else {
        if (message.hasField(field))
          valueCount = 1;
      }

      // Reverse so that we encode backwards.
      for (int iValue = valueCount - 1; iValue >= 0; --iValue) {
        Object value;
        if (field.isRepeated())
          value = message.getRepeatedField(field, iValue);
        else
          value = message.getField(field);

        if (field.getType() == Type.MESSAGE) {
          int saveLength = encoder.getLength();

          // Encode backwards.
          encodeMessageValue((Message)value, encoder);
          encoder.writeTypeAndLength(tlvType, encoder.getLength() - saveLength);
        }
        else if (field.getType() == Type.UINT32)
          encoder.writeNonNegativeIntegerTlv(tlvType, (Integer)value);
        else if (field.getType() == Type.UINT64)
          encoder.writeNonNegativeIntegerTlv(tlvType, (Long)value);
        else if (field.getType() == Type.ENUM) {
          int intValue = ((EnumValueDescriptor)value).getNumber();
          if (intValue < 0)
            throw new Error("ProtobufTlv.encode: ENUM value may not be negative");
          encoder.writeNonNegativeIntegerTlv(tlvType, intValue);
        }
        else if (field.getType() == Type.BYTES)
          encoder.writeBlobTlv(tlvType, ((ByteString)value).asReadOnlyByteBuffer());
        else if (field.getType() == Type.STRING)
          // Use Blob to UTF-8 encode and get a ByteBuffer.
          encoder.writeBlobTlv(tlvType, new Blob((String)value).buf());
        else if (field.getType() == Type.BOOL) {
          if ((boolean)(Boolean)value)
            encoder.writeTypeAndLength(tlvType, 0);
        }
        else
          throw new Error("ProtobufTlv.encode: Unknown field type");
      }
    }
  }

  private static void
  decodeMessageValue(Message.Builder message, TlvDecoder decoder, int endOffset)
    throws EncodingException
  {
    Descriptor descriptor = message.getDescriptorForType();

    for (FieldDescriptor field : descriptor.getFields()) {
      int tlvType = field.getNumber();

      if (field.isOptional() && !decoder.peekType(tlvType, endOffset))
          continue;

      if (field.isRepeated()) {
        while (decoder.peekType(tlvType, endOffset)) {
          if (field.getType() == Type.MESSAGE) {
            Message.Builder innerMessage = message.newBuilderForField(field);

            int innerEndOffset = decoder.readNestedTlvsStart(tlvType);
            decodeMessageValue(innerMessage, decoder, innerEndOffset);
            decoder.finishNestedTlvs(innerEndOffset);

            message.addRepeatedField(field, innerMessage.build());
          }
          else
            message.addRepeatedField
              (field, decodeFieldValue(field, tlvType, decoder, endOffset));
        }
      }
      else {
        if (field.getType() == Type.MESSAGE) {
          Message.Builder innerMessage = message.newBuilderForField(field);

          int innerEndOffset = decoder.readNestedTlvsStart(tlvType);
          decodeMessageValue(innerMessage, decoder, innerEndOffset);
          decoder.finishNestedTlvs(innerEndOffset);

          message.setField(field, innerMessage.build());
        }
        else
          message.setField
            (field, decodeFieldValue(field, tlvType, decoder, endOffset));
      }
    }
  }

  /**
   * This is a helper for decodeMessageValue. Decode a single field and return
   * the value. Assume the field type is not Type.MESSAGE.
   */
  private static Object
  decodeFieldValue
    (FieldDescriptor field, int tlvType, TlvDecoder decoder, int endOffset)
      throws EncodingException
  {
    if (field.getType() == Type.UINT32)
      return (int)decoder.readNonNegativeIntegerTlv(tlvType);
    else if (field.getType() == Type.UINT64)
      return decoder.readNonNegativeIntegerTlv(tlvType);
    else if (field.getType() == Type.ENUM)
      return field.getEnumType().findValueByNumber
        ((int)decoder.readNonNegativeIntegerTlv(tlvType));
    else if (field.getType() == Type.BYTES)
      return ByteString.copyFrom(decoder.readBlobTlv(tlvType));
    else if (field.getType() == Type.STRING) {
      try {
        ByteBuffer byteBuffer = decoder.readBlobTlv(tlvType);
        // Use Blob to get the byte array.
        return new String(new Blob(byteBuffer, false).getImmutableArray(), "UTF-8");
      } catch (UnsupportedEncodingException ex) {
        // We don't expect this to happen.
        throw new Error("UTF-8 decoder not supported: " + ex.getMessage());
      }
    }
    else if (field.getType() == Type.BOOL)
      return decoder.readBooleanTlv(tlvType, endOffset);
    else
      throw new Error("ProtobufTlv.decode: Unknown field type");
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy