Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SenML-CBOR Encoder/Decoder based on upokecenter CBOR-Java and use it by default #937

Merged
merged 3 commits into from
Dec 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions leshan-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ Contributors:
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
</dependency>
<dependency>
<groupId>com.upokecenter</groupId>
<artifactId>cbor</artifactId>
</dependency>

<!-- test dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import org.eclipse.leshan.core.node.codec.tlv.LwM2mNodeTlvDecoder;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.util.Validate;
import org.eclipse.leshan.senml.cbor.jackson.SenMLCborJacksonEncoderDecoder;
import org.eclipse.leshan.senml.cbor.upokecenter.SenMLCborUpokecenterEncoderDecoder;
import org.eclipse.leshan.senml.json.jackson.SenMLJsonJacksonEncoderDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -60,7 +60,7 @@ public static Map<ContentFormat, NodeDecoder> getDefaultDecoders(boolean support
decoders.put(ContentFormat.TEXT, new LwM2mNodeTextDecoder());
decoders.put(ContentFormat.OPAQUE, new LwM2mNodeOpaqueDecoder());
decoders.put(ContentFormat.SENML_JSON, new LwM2mNodeSenMLDecoder(new SenMLJsonJacksonEncoderDecoder()));
decoders.put(ContentFormat.SENML_CBOR, new LwM2mNodeSenMLDecoder(new SenMLCborJacksonEncoderDecoder()));
decoders.put(ContentFormat.SENML_CBOR, new LwM2mNodeSenMLDecoder(new SenMLCborUpokecenterEncoderDecoder()));

// tlv
LwM2mNodeTlvDecoder tlvDecoder = new LwM2mNodeTlvDecoder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.eclipse.leshan.core.node.codec.tlv.LwM2mNodeTlvEncoder;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.util.Validate;
import org.eclipse.leshan.senml.cbor.jackson.SenMLCborJacksonEncoderDecoder;
import org.eclipse.leshan.senml.cbor.upokecenter.SenMLCborUpokecenterEncoderDecoder;
import org.eclipse.leshan.senml.json.jackson.SenMLJsonJacksonEncoderDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -54,7 +54,7 @@ public static Map<ContentFormat, NodeEncoder> getDefaultEncoders(boolean support
encoders.put(ContentFormat.TEXT, new LwM2mNodeTextEncoder());
encoders.put(ContentFormat.OPAQUE, new LwM2mNodeOpaqueEncoder());
encoders.put(ContentFormat.SENML_JSON, new LwM2mNodeSenMLEncoder(new SenMLJsonJacksonEncoderDecoder()));
encoders.put(ContentFormat.SENML_CBOR, new LwM2mNodeSenMLEncoder(new SenMLCborJacksonEncoderDecoder()));
encoders.put(ContentFormat.SENML_CBOR, new LwM2mNodeSenMLEncoder(new SenMLCborUpokecenterEncoderDecoder()));

// tlv
LwM2mNodeTlvEncoder tlvDecoder = new LwM2mNodeTlvEncoder();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*******************************************************************************
* Copyright (c) 2020 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.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.senml.cbor.upokecenter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;

import org.eclipse.leshan.core.model.ResourceModel.Type;
import org.eclipse.leshan.core.util.Base64;
import org.eclipse.leshan.core.util.datatype.ULong;
import org.eclipse.leshan.core.util.json.JsonException;
import org.eclipse.leshan.senml.SenMLException;
import org.eclipse.leshan.senml.SenMLPack;
import org.eclipse.leshan.senml.SenMLRecord;

import com.upokecenter.cbor.CBORObject;
import com.upokecenter.cbor.CBORType;
import com.upokecenter.numbers.EInteger;

public class SenMLCborPackSerDes {

public SenMLPack deserializeFromCbor(Collection<CBORObject> objects) throws SenMLException {
try {
SenMLPack senMLPack = new SenMLPack();
for (CBORObject o : objects) {
SenMLRecord record = new SenMLRecord();

CBORObject bn = o.get(-2);
if (bn != null && bn.getType() == CBORType.TextString)
record.setBaseName(bn.AsString());

CBORObject bt = o.get(-3);
if (bt != null && bt.isNumber())
record.setBaseTime(bt.AsInt64());

CBORObject n = o.get(0);
if (n != null && n.getType() == CBORType.TextString)
record.setName(n.AsString());

CBORObject t = o.get(6);
if (t != null && t.isNumber())
record.setTime(t.AsInt64());

CBORObject v = o.get(2);
boolean hasValue = false;
if (v != null && v.isNumber()) {
if (v.isIntegral()) {
if (v.isNegative()) {
record.setFloatValue(v.AsInt64());
} else {
EInteger eInteger = v.AsEInteger();
if (eInteger.CanFitInInt64()) {
record.setFloatValue(eInteger.ToInt64Unchecked());
} else {
// There is maybe a better way to do that.
record.setFloatValue(ULong.valueOf(v.AsEInteger().toString()));
}
}
} else {
record.setFloatValue(v.AsDoubleValue());
}
hasValue = true;
}

CBORObject vb = o.get(4);
if (vb != null && vb.getType() == CBORType.Boolean) {
record.setBooleanValue(vb.AsBoolean());
hasValue = true;
}

CBORObject vs = o.get(3);
if (vs != null && vs.getType() == CBORType.TextString) {
record.setStringValue(vs.AsString());
hasValue = true;
}

CBORObject vlo = o.get("vlo");
if (vlo != null && vlo.getType() == CBORType.TextString) {
record.setObjectLinkValue(vlo.AsString());
hasValue = true;
}

CBORObject vd = o.get(8);
if (vd != null && vd.getType() == CBORType.TextString) {
record.setOpaqueValue(Base64.decodeBase64(vd.AsString()));
hasValue = true;
}

if (!hasValue)
throw new JsonException("Invalid SenML record : record must have a value (v,vb,vlo,vd,vs) : %s", o);

senMLPack.addRecord(record);
}
return senMLPack;
} catch (IllegalArgumentException | IllegalStateException e) {
throw new SenMLException(e, "Unable to serialize SenML in CBOR");
}

}

public byte[] serializeToCbor(SenMLPack pack) throws SenMLException {
try (OutputStream stream = new ByteArrayOutputStream()) {
CBORObject cborArray = CBORObject.NewArray();
for (SenMLRecord record : pack.getRecords()) {
CBORObject cborRecord = newMap();
if (record.getBaseName() != null && !record.getBaseName().isEmpty()) {
cborRecord.Add(-2, record.getBaseName());
}

if (record.getBaseTime() != null) {
cborRecord.Add(-3, record.getBaseTime());
}

if (record.getName() != null && !record.getName().isEmpty()) {
cborRecord.Add(0, record.getName());
}

if (record.getTime() != null) {
cborRecord.Add(6, record.getTime());
}

Type type = record.getType();
if (type != null) {
switch (record.getType()) {
case FLOAT:
Number value = record.getFloatValue();
if (value instanceof Byte) {
cborRecord.Add(2, value.byteValue());
} else if (value instanceof Short) {
cborRecord.Add(2, value.shortValue());
} else if (value instanceof Integer) {
cborRecord.Add(2, value.intValue());
} else if (value instanceof Long) {
cborRecord.Add(2, value.longValue());
} else if (value instanceof BigInteger) {
// probably not supported, not tested
cborRecord.Add(2, value);
}
// unsigned integer
else if (value instanceof ULong) {
// There is maybe a better way to do that.
cborRecord.Add(2, EInteger.FromString(value.toString()));
}
// floating-point
else if (value instanceof Float) {
cborRecord.Add(2, value.floatValue());
} else if (value instanceof Double) {
cborRecord.Add(2, value.doubleValue());
} else if (value instanceof BigDecimal) {
// probably not supported, not tested
cborRecord.Add(2, value);
}
break;
case BOOLEAN:
cborRecord.Add(4, record.getBooleanValue());
break;
case OBJLNK:
cborRecord.Add("vlo", record.getObjectLinkValue());
break;
case OPAQUE:
cborRecord.Add(8, record.getOpaqueValue());
case STRING:
cborRecord.Add(3, record.getStringValue());
break;
default:
break;
}
}
cborArray.Add(cborRecord);
}
return cborArray.EncodeToBytes();
} catch (IllegalArgumentException | IllegalStateException | IOException e) {
throw new SenMLException(e, "Unable to serialize SenML in CBOR");
}
}

CBORObject newMap() {
return CBORObject.NewMap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*******************************************************************************
* Copyright (c) 2020 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.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.senml.cbor.upokecenter;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashMap;

import org.eclipse.leshan.senml.SenMLDecoder;
import org.eclipse.leshan.senml.SenMLEncoder;
import org.eclipse.leshan.senml.SenMLException;
import org.eclipse.leshan.senml.SenMLPack;

import com.upokecenter.cbor.CBORException;
import com.upokecenter.cbor.CBORObject;
import com.upokecenter.cbor.CBORType;

/**
* Helper for encoding/decoding SenML CBOR using <a href="https://github.com/peteroupc/CBOR-Java">"upokecenter"
* CBOR-Java</a>
*/
public class SenMLCborUpokecenterEncoderDecoder implements SenMLDecoder, SenMLEncoder {
private final SenMLCborPackSerDes serDes;

public SenMLCborUpokecenterEncoderDecoder() {
this(false);
}

/**
* @param keepingInsertionOrder Set it to true allows to keep insertion order at serialization. This is a kind of
* HACK using reflection which could make testing easier but could bring performance penalty
*
* @see <a href="https://github.com/peteroupc/CBOR-Java/issues/13">CBOR-Java#13 issue</a>
*/
public SenMLCborUpokecenterEncoderDecoder(boolean keepingInsertionOrder) {
if (keepingInsertionOrder) {
try {
final Constructor<CBORObject> constructor = CBORObject.class.getDeclaredConstructor(int.class,
Object.class);
constructor.setAccessible(true);
serDes = new SenMLCborPackSerDes() {
@Override
CBORObject newMap() {
try {
return constructor.newInstance(5, new LinkedHashMap<CBORObject, CBORObject>());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(
"Unable to use Java reflection to create CBORMap keeping insertion order", e);
}
}
};
} catch (NoSuchMethodException | SecurityException e) {
throw new IllegalStateException("Unable to use Java reflection to keep insertion order", e);
}
} else {
serDes = new SenMLCborPackSerDes();
}
}

@Override
public byte[] toSenML(SenMLPack pack) throws SenMLException {
if (pack == null)
return null;
return serDes.serializeToCbor(pack);
}

@Override
public SenMLPack fromSenML(byte[] data) throws SenMLException {
try {
CBORObject cborObject = CBORObject.DecodeFromBytes(data);
if (cborObject.getType() != CBORType.Array) {
throw new SenMLException("Unable to parse SenML CBOR: Array expected but was %s", cborObject.getType());
}
return serDes.deserializeFromCbor(cborObject.getValues());
} catch (CBORException e) {
throw new SenMLException("Unable to parse SenML CBOR.", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@
import org.eclipse.leshan.senml.SenMLRecord;

public abstract class AbstractSenMLTest {
protected void givenResourceWithFloatValue(SenMLPack pack, String n, Number value) {

private void givenResourceWithFloatValue(SenMLPack pack, String n, Number value) {
SenMLRecord elt = new SenMLRecord();
elt.setName(n);
elt.setFloatValue(value);
pack.addRecord(elt);
}

protected void givenResourceWithStringValue(SenMLPack pack, String n, String value) {
private void givenResourceWithStringValue(SenMLPack pack, String n, String value) {
givenResourceWithStringValue(pack, null, n, value);
}

protected void givenResourceWithStringValue(SenMLPack pack, String bn, String n, String value) {
private void givenResourceWithStringValue(SenMLPack pack, String bn, String n, String value) {
SenMLRecord elt = new SenMLRecord();
if (bn != null) {
elt.setBaseName(bn);
Expand Down Expand Up @@ -57,12 +58,24 @@ protected String givenSenMLJsonExample() {
b.append("{\"n\":\"9\",\"v\":100},");
b.append("{\"n\":\"10\",\"v\":15},");
b.append("{\"n\":\"11/0\",\"v\":0},");
b.append("{\"n\":\"13\",\"v\":1.3674912E9},");
b.append("{\"n\":\"13\",\"v\":1367491215},");
b.append("{\"n\":\"14\",\"vs\":\"+02:00\"},");
b.append("{\"n\":\"16\",\"vs\":\"U\"}]");
return b.toString();
}

/**
* Example of CBOR payload request to Device Object of the LwM2M example client (Read /3/0)
*
* @return JSON payload
*/
protected String givenSenMLCborExample() {
return "90a321652f332f302f00613003744f70656e204d6f62696c6520416c6c69616e6365a200613103764c696768747765696768"
+ "74204d324d20436c69656e74a20061320369333435303030313233a20061330363312e30a20063362f300201a20063362f310205a20063372f"
+ "3002190ed8a20063372f3102191388a20063382f3002187da20063382f3102190384a2006139021864a200623130020fa2006431312f300200"
+ "a200623133021a5182428fa20062313403662b30323a3030a200623136036155";
}

protected SenMLPack givenDeviceObjectInstance() {
SenMLPack pack = new SenMLPack();

Expand Down
Loading