org.eclipse.leshan.tlv.TlvDecoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of leshan-all Show documentation
Show all versions of leshan-all Show documentation
A LWM2M client and server based on Californium (CoAP) all in one.
The newest version!
/*******************************************************************************
* Copyright (c) 2013-2015 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Sierra Wireless - initial API and implementation
*******************************************************************************/
package org.eclipse.leshan.tlv;
import java.math.BigInteger;
import java.nio.Buffer;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.tlv.Tlv.TlvType;
import org.eclipse.leshan.util.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TlvDecoder {
private static final Logger LOG = LoggerFactory.getLogger(TlvDecoder.class);
public static Tlv[] decode(ByteBuffer input) throws TlvException {
try {
List tlvs = new ArrayList<>();
while (input.remaining() > 0) {
input.order(ByteOrder.BIG_ENDIAN);
// decode type
int typeByte = input.get() & 0xFF;
TlvType type;
switch (typeByte & 0b1100_0000) {
case 0b0000_0000:
type = TlvType.OBJECT_INSTANCE;
break;
case 0b0100_0000:
type = TlvType.RESOURCE_INSTANCE;
break;
case 0b1000_0000:
type = TlvType.MULTIPLE_RESOURCE;
break;
case 0b1100_0000:
type = TlvType.RESOURCE_VALUE;
break;
default:
throw new TlvException("unknown type: " + (typeByte & 0b1100_0000));
}
// decode identifier
int identifier;
try {
if ((typeByte & 0b0010_0000) == 0) {
identifier = input.get() & 0xFF;
} else {
identifier = input.getShort() & 0xFFFF;
}
} catch (BufferUnderflowException e) {
throw new TlvException("Invalid 'identifier' length", e);
}
LOG.trace("decoding {} {}", type, identifier);
// decode length
int length;
int lengthType = typeByte & 0b0001_1000;
try {
switch (lengthType) {
case 0b0000_0000:
// 2 bit length
length = typeByte & 0b0000_0111;
break;
case 0b0000_1000:
// 8 bit length
length = input.get() & 0xFF;
break;
case 0b0001_0000:
// 16 bit length
length = input.getShort() & 0xFFFF;
break;
case 0b0001_1000:
// 24 bit length
int b = input.get() & 0x000000FF;
int s = input.getShort() & 0x0000FFFF;
length = (b << 16) | s;
break;
default:
throw new TlvException("unknown length type: " + (typeByte & 0b0001_1000));
}
} catch (BufferUnderflowException e) {
throw new TlvException("Invalid 'length' length", e);
}
LOG.trace("length: {} (length type: {})", length, lengthType);
// decode value
if (type == TlvType.RESOURCE_VALUE || type == TlvType.RESOURCE_INSTANCE) {
try {
byte[] payload = new byte[length];
input.get(payload);
tlvs.add(new Tlv(type, null, payload, identifier));
if (LOG.isTraceEnabled()) {
LOG.trace("payload value: {}", Hex.encodeHexString(payload));
}
} catch (BufferOverflowException e) {
throw new TlvException("Invalid 'value' length", e);
}
} else {
try {
// create a view of the contained TLVs
ByteBuffer slice = input.slice();
// HACK the cast is necessary for binary backward compatibility bug introduce in Java 9
// https://github.com/apache/felix/pull/114
((Buffer) slice).limit(length);
Tlv[] children = decode(slice);
// skip the children, it will be decoded by the view
// HACK the cast is necessary for binary backward compatibility bug introduce in Java 9
// https://github.com/apache/felix/pull/114
((Buffer) input).position(((Buffer) input).position() + length);
Tlv tlv = new Tlv(type, children, null, identifier);
tlvs.add(tlv);
} catch (IllegalArgumentException e) {
throw new TlvException("Invalid 'value' length", e);
}
}
}
return tlvs.toArray(new Tlv[] {});
} catch (TlvException ex) {
String printHexBinary = Hex.encodeHexString(input.array());
throw new TlvException("Impossible to parse TLV: \n" + printHexBinary, ex);
} catch (RuntimeException ex) {
String printHexBinary = Hex.encodeHexString(input.array());
throw new TlvException("Unexpected TLV parse error: \n" + printHexBinary, ex);
}
}
/**
* Decodes a byte array into string value.
*/
public static String decodeString(byte[] value) {
return new String(value, StandardCharsets.UTF_8);
}
/**
* Decodes a byte array into a boolean value.
*/
public static boolean decodeBoolean(byte[] value) throws TlvException {
if (value.length == 1) {
if (value[0] == 0) {
return false;
} else if (value[0] == 1) {
return true;
} else {
LOG.warn("Boolean value should be encoded as integer with value 0 or 1, not {}", value[0]);
return false;
}
}
throw new TlvException("Invalid length for a boolean value: " + value.length);
}
/**
* Decodes a byte array into a date value.
*/
public static Date decodeDate(byte[] value) throws TlvException {
BigInteger bi = new BigInteger(value);
if (value.length <= 8) {
return new Date(bi.longValue() * 1000L);
} else {
throw new TlvException("Invalid length for a time value: " + value.length);
}
}
/**
* Decodes a byte array into a objlnk value.
*/
public static ObjectLink decodeObjlnk(byte[] value) throws TlvException {
ByteBuffer bff = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
bff.put(value);
int val1 = bff.getShort(0) & 0xFFFF;
int val2 = bff.getShort(2) & 0xFFFF;
return new ObjectLink(val1, val2);
}
/**
* Decodes a byte array into an integer value.
*/
public static Number decodeInteger(byte[] value) throws TlvException {
BigInteger bi = new BigInteger(value);
if (value.length == 1) {
return bi.byteValue();
} else if (value.length <= 2) {
return bi.shortValue();
} else if (value.length <= 4) {
return bi.intValue();
} else if (value.length <= 8) {
return bi.longValue();
} else {
throw new TlvException("Invalid length for an integer value: " + value.length);
}
}
/**
* Decodes a byte array into a float value.
*/
public static Number decodeFloat(byte[] value) throws TlvException {
ByteBuffer floatBb = ByteBuffer.wrap(value);
if (value.length == 4) {
return floatBb.getFloat();
} else if (value.length == 8) {
return floatBb.getDouble();
} else {
throw new TlvException("Invalid length for a float value: " + value.length);
}
}
}