com.guardtime.ksi.tlv.TLVElement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ksi-common Show documentation
Show all versions of ksi-common Show documentation
KSI Java SDK common module
/*
* Copyright 2013-2016 Guardtime, Inc.
*
* This file is part of the Guardtime client SDK.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES, CONDITIONS, OR OTHER LICENSES OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
* "Guardtime" and "KSI" are trademarks or registered trademarks of
* Guardtime, Inc., and no license to trademarks is granted; Guardtime
* reserves and retains all trademark rights.
*/
package com.guardtime.ksi.tlv;
import java.io.*;
import java.nio.charset.CharacterCodingException;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import com.guardtime.ksi.hashing.DataHash;
import com.guardtime.ksi.hashing.HashAlgorithm;
import com.guardtime.ksi.util.Base16;
import com.guardtime.ksi.util.Util;
/**
* This class represents the Type-Length-Value (TLV) element. The TLV scheme is used to encode both the KSI data
* structures and also protocol data units.
*
* For space efficiency, two TLV encodings are used: - A 16-bit TLV (TLV16) encodes a 13-bit type and 16-bit
* length (and can thus contain at most 65535 octets of data in the value part).
- An 8-bit TLV (TLV8) encodes a 5-bit
* type and 8-bit length (at most 255 octets of value data).
TLV header contains 3 flags: - 16-bit flag.
* TLV8 and TLV16 are distinguished by the `16-Bit' flag in the first octet of the type field
- The non-critical flag.
*
- The Forward Unknown flag.
*/
public final class TLVElement {
public static final int MAX_TLV16_CONTENT_LENGTH = 0xFFFF;
/**
* TLV 16 bit flag
*/
private boolean inputTlv16;
/**
* Non-critical flag
*/
private boolean nonCritical;
/**
* The Forward Unknown flag
*/
private boolean forwarded;
/**
* The type tags are given in hexadecimal: 4 digits for global (long) and 2 digits for local (short) tlv types.
*/
private int type;
private List children = new LinkedList();
private byte[] content = new byte[0];
public TLVElement(boolean nonCritical, boolean forwarded, int type) {
this(false, nonCritical, forwarded, type);
}
public TLVElement(boolean inputTlv16, boolean nonCritical, boolean forwarded, int type) {
this.inputTlv16 = inputTlv16;
this.nonCritical = nonCritical;
this.forwarded = forwarded;
this.type = type;
}
/**
* Converts bytes to TLV element
*
* @param bytes
* - byte array to convert
* @return instance of {@link TLVElement}
* @throws MultipleTLVElementException
* - Thrown if the outer most layer is composed of more than one TLV.
* @throws TLVParserException
* @deprecated use {@link TLVElement#create(byte[])}.
*/
@Deprecated
public static TLVElement createFromBytes(byte[] bytes) throws TLVParserException {
return create(bytes);
}
/**
* Creates TLVElement form byte array.
*/
public static TLVElement create(byte[] bytes) throws TLVParserException {
TLVInputStream input = null;
try {
input = new TLVInputStream(new ByteArrayInputStream(bytes));
TLVElement element = input.readElement();
if (input.hasNextElement()) {
throw new MultipleTLVElementException();
}
return element;
} catch (IOException e) {
throw new TLVParserException("Reading TLV bytes failed", e);
} finally {
Util.closeQuietly(input);
}
}
/**
* Static factory method for creating TLV element with {@link Long} content. TLV element nonCritical and forwarded
* flags are set to false.
*/
public static TLVElement create(int type, long value) throws TLVParserException {
TLVElement element = create(type);
element.setLongContent(value);
return element;
}
/**
* Static factory method for creating TLV element with {@link Date} content. TLV element nonCritical and forwarded
* flags are set to false.
*/
public static TLVElement create(int type, Date value) throws TLVParserException {
return create(type, value.getTime() / 1000);
}
/**
* Static factory method for creating TLV element with {@link DataHash} content. TLV element nonCritical and forwarded
* flags are set to false.
*/
public static TLVElement create(int type, DataHash value) throws TLVParserException {
TLVElement element = create(type);
element.setDataHashContent(value);
return element;
}
/**
* Static factory method for creating TLV element with {@link String} content. TLV element nonCritical and forwarded
* flags are set to false.
*/
public static TLVElement create(int type, String value) throws TLVParserException {
TLVElement element = create(type);
element.setStringContent(value);
return element;
}
private static TLVElement create(int type) throws TLVParserException {
return new TLVElement(false, false, type);
}
/**
* This method is used to convert TLV element content data to actual java {@link Long} object.
*
* @return decoded unsigned Long. Value is between 0 - {@link Integer#MAX_VALUE}
* @throws TLVParserException
* - content contains leading zeros or content contains more than 63 unsigned bits
*/
public final Long getDecodedLong() throws TLVParserException {
byte[] data = getContent();
if (data.length > 1 && data[0] == 0) {
throw new TLVParserException("Integer encoding cannot contain leading zeros");
}
if (data.length > 8 || data.length == 8 && data[0] < 0) {
throw new TLVParserException("Integers of at most 63 unsigned bits supported by this implementation");
}
int ofs = 0;
long t = 0;
for (int i = 0; i < data.length; ++i) {
t = (t << 8) | ((long) data[ofs + i] & 0xff);
}
return t;
}
/**
* This method is used to convert TLV element content data to UTF-8 string.
*
* @return decoded instance of string
* @throws TLVParserException
* - content string isn't null terminated or is malformed UTF-8 data.
*/
public final String getDecodedString() throws TLVParserException {
byte[] data = getContent();
if (!(data.length > 0 && data[data.length - 1] == '\0')) {
throw new TLVParserException("String must be null terminated");
}
try {
return Util.decodeString(data, 0, data.length - 1);
} catch (CharacterCodingException e) {
throw new TLVParserException("Malformed UTF-8 data", e);
}
}
/**
* This method is used to convert TLV element content data to {@link DataHash} object.
*
* @return decoded instance of data hash
* @throws TLVParserException
* - content can not be decoded to data hash
*/
public final DataHash getDecodedDataHash() throws TLVParserException {
byte[] content = getContent();
if (DataHash.isDataHash(content)) {
return new DataHash(content);
}
throw new TLVParserException("Invalid DataHash content");
}
/**
* This method is used to get Data object from TLV element.
*
* @return decoded date object
* @throws TLVParserException
* - content can not be decoded to date object
*/
public final Date getDecodedDate() throws TLVParserException {
return new Date(getDecodedLong() * 1000);
}
/**
* This method is used to get HashAlgorithm form TLV element
*
* @return instance of {@link HashAlgorithm}
*/
public HashAlgorithm getDecodedHashAlgorithm() throws TLVParserException {
int algorithmId = getDecodedLong().intValue();
if (HashAlgorithm.isHashAlgorithmId(algorithmId)) {
return HashAlgorithm.getById(algorithmId);
}
throw new TLVParserException("Unknown hash algorithm with id " + algorithmId);
}
/**
* Returns the TLV content. If TLV does not include content then empty array is returned.
*
* @return byte array including TLV element content
* @throws TLVParserException
*/
public byte[] getContent() throws TLVParserException {
byte[] content = this.content;
if (!children.isEmpty()) {
for (TLVElement child : children) {
content = Util.join(content, child.encodeHeader());
content = Util.join(content, child.getContent());
}
}
return content;
}
/**
* Sets the value to TLV element content
*
* @param content
* value to set
*/
public void setContent(byte[] content) throws TLVParserException {
assertActualContentLengthIsInTLVLimits(content.length);
this.content = content;
}
/**
* Encodes the instance of {@link String}. TLV encoded string is always terminated with a zero octet.
*
* @param s
* - string to decode
*/
public void setStringContent(String s) throws TLVParserException {
if (s != null) {
setContent(Util.toByteArray(s + '\0'));
} else {
setContent(new byte[]{'\0'});
}
}
public void setLongContent(long value) throws TLVParserException {
setContent(Util.encodeUnsignedLong(value));
}
public void setDataHashContent(DataHash dataHash) throws TLVParserException {
setContent(dataHash.getImprint());
}
/**
* Returns the first child element with specified tag. If tag doesn't exist then null is returned
*
* @param tag
* tag to search.
* @return the first instance of {@link TLVElement} with specified tag or null when the child element with specified
* tag doesn't exist.
*/
public TLVElement getFirstChildElement(int tag) {
for (TLVElement element : children) {
if (tag == element.getType()) {
return element;
}
}
return null;
}
/**
* Returns the first child element. If current element doesn't contain child elements then null is returned.
*/
public TLVElement getFirstChildElement() {
if (children.isEmpty()) {
return null;
}
return children.get(0);
}
/**
* Returns the last child element. If current element doesn't contain child elements then null is returned.
*/
public TLVElement getLastChildElement() {
if (children.isEmpty()) {
return null;
}
return children.get(children.size()-1);
}
/**
* Returns all the tags with the specified tag.
*
* @param tag
* tag to search.
* @return the List of {@link TLVElement}'s with specified tag or empty List
*/
public List getChildElements(int tag) {
List elements = new LinkedList();
for (TLVElement element : children) {
if (tag == element.getType()) {
elements.add(element);
}
}
return elements;
}
public List getChildElements() {
return children;
}
public List getChildElements(int... tags) {
List elements = new LinkedList();
for (TLVElement element : children) {
for (int tag : tags) {
if (tag == element.getType()) {
elements.add(element);
}
}
}
return elements;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
/**
* @return true if the TLVElement has type or length over TLV8 maximums.
* @deprecated Use {@link #isOutputTlv16}
*/
@Deprecated
public boolean isTlv16() {
return isOutputTlv16();
}
public boolean isOutputTlv16() {
return getType() > TLVInputStream.TYPE_MASK || (getContentLength() > TLVInputStream.BYTE_MAX);
}
public boolean isInputTlv16() {
return this.inputTlv16;
}
public boolean isNonCritical() {
return nonCritical;
}
public boolean isForwarded() {
return forwarded;
}
/**
* Encodes TLV header.
*
* @return byte array containing encoded TLV header
* @throws TLVParserException
* when TLV header encoding fails or I/O error occurs
*/
public byte[] encodeHeader() throws TLVParserException {
DataOutputStream out = null;
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
out = new DataOutputStream(byteArrayOutputStream);
int dataLength = getContentLength();
boolean tlv16 = isOutputTlv16();
int firstByte = (tlv16 ? TLVInputStream.TLV16_FLAG : 0) + (isNonCritical() ? TLVInputStream.NON_CRITICAL_FLAG : 0)
+ (isForwarded() ? TLVInputStream.FORWARD_FLAG : 0);
if (tlv16) {
firstByte = firstByte | (getType() >>> TLVInputStream.BYTE_BITS) & TLVInputStream.TYPE_MASK;
out.writeByte(firstByte);
out.writeByte(getType());
if (dataLength < 1) {
out.writeShort(0);
} else {
out.writeShort(dataLength);
}
} else {
firstByte = firstByte | getType() & TLVInputStream.TYPE_MASK;
out.writeByte(firstByte);
if (dataLength < 1) {
out.writeByte(0);
} else {
out.writeByte(dataLength);
}
}
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new TLVParserException("TLV header encoding failed", e);
} finally {
Util.closeQuietly(out);
}
}
/**
* Returns the length of the TLV element content.
*/
public int getContentLength() {
int contentLength = content.length;
if (!children.isEmpty()) {
for (TLVElement element : children) {
contentLength += element.getHeaderLength();
contentLength += element.getContentLength();
}
}
return contentLength;
}
public int getHeaderLength() {
return isOutputTlv16() ? 4 : 2;
}
/**
* Replaces first element with given one.
*
* @param childToBeReplaced
* tlv element to be replaced
* @param newChild
* new tlv element
*/
public void replace(TLVElement childToBeReplaced, TLVElement newChild) {
for (int i = 0; i < children.size(); i++) {
if (children.get(i).equals(childToBeReplaced)) {
children.set(i, newChild);
return;
}
}
}
public void remove(TLVElement elementToRemoved) {
children.remove(elementToRemoved);
}
public void addChildElement(TLVElement element) throws TLVParserException {
this.children.add(element);
assertActualContentLengthIsInTLVLimits(getContentLength());
}
public void addFirstChildElement(TLVElement element) throws TLVParserException {
this.children.add(0, element);
assertActualContentLengthIsInTLVLimits(getContentLength());
}
/**
* Writes the encoded TLV element to the specified output stream .
*
* @param out
* the output stream to which to write the TLV element data.
* @throws TLVParserException
* I/O error occurred or TLV encoding failed.
*/
public void writeTo(OutputStream out) throws TLVParserException {
try {
assertActualContentLengthIsInTLVLimits(getContentLength());
out.write(encodeHeader());
out.write(getContent());
} catch (IOException e) {
throw new TLVParserException("Writing TLV element (" + convertHeader() + ") to output stream failed", e);
}
}
private void assertActualContentLengthIsInTLVLimits(int contentLength) throws TLVParserException {
if (contentLength > MAX_TLV16_CONTENT_LENGTH) {
throw new TLVParserException("TLV16 should never contain more than " + MAX_TLV16_CONTENT_LENGTH + " bytes of content, but this one contains " + contentLength + " bytes.");
}
}
public byte[] getEncoded() throws TLVParserException {
return Util.join(encodeHeader(), getContent());
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(convertHeader());
builder.append(":");
if (children.isEmpty()) {
builder.append(Base16.encode(content));
} else {
for (TLVElement element : children) {
builder.append(element.toString());
}
}
return builder.toString();
}
private String convertHeader() {
StringBuilder builder = new StringBuilder("TLV[0x");
builder.append(Integer.toHexString(this.type));
if (isNonCritical()) {
builder.append(",N");
}
if (isForwarded()) {
builder.append(",F");
}
builder.append("]");
return builder.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TLVElement that = (TLVElement) o;
if (nonCritical != that.nonCritical) return false;
if (forwarded != that.forwarded) return false;
if (type != that.type) return false;
if (children != null ? !children.equals(that.children) : that.children != null) return false;
return Arrays.equals(content, that.content);
}
@Override
public int hashCode() {
int result = (nonCritical ? 1 : 0);
result = 31 * result + (forwarded ? 1 : 0);
result = 31 * result + type;
result = 31 * result + (children != null ? children.hashCode() : 0);
result = 31 * result + (content != null ? Arrays.hashCode(content) : 0);
return result;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy