From d5e5a7d6662f82d6c4dd5f0a857028b7f9994dc3 Mon Sep 17 00:00:00 2001 From: Julien Vermillard Date: Mon, 9 May 2022 15:37:27 +0200 Subject: [PATCH] Add CoreLink datatype support. Signed-off-by: Julien Vermillard Also-by: Simon Bernard --- .../leshan/client/object/LwM2mTestObject.java | 77 +++++++++++++++++++ .../core/node/LwM2mMultipleResource.java | 9 +++ .../core/node/LwM2mResourceInstance.java | 12 +++ .../leshan/core/node/LwM2mSingleResource.java | 13 ++++ .../node/codec/cbor/LwM2mNodeCborDecoder.java | 25 +++++- .../node/codec/cbor/LwM2mNodeCborEncoder.java | 19 ++++- .../node/codec/json/LwM2mNodeJsonDecoder.java | 10 ++- .../node/codec/json/LwM2mNodeJsonEncoder.java | 14 +++- .../codec/senml/LwM2mNodeSenMLDecoder.java | 11 +++ .../codec/senml/LwM2mNodeSenMLEncoder.java | 15 +++- .../node/codec/text/LwM2mNodeTextDecoder.java | 27 ++++++- .../node/codec/text/LwM2mNodeTextEncoder.java | 20 ++++- .../node/codec/tlv/LwM2mNodeTlvDecoder.java | 31 +++++++- .../node/codec/tlv/LwM2mNodeTlvEncoder.java | 17 +++- .../leshan/core/request/WriteRequest.java | 19 +++++ .../core/node/codec/LwM2mNodeDecoderTest.java | 47 +++++++++++ .../tests/write/WriteSingleValueTest.java | 32 ++++++++ .../json/JacksonLwM2mNodeDeserializer.java | 15 ++++ .../json/JacksonLwM2mNodeSerializer.java | 7 ++ 19 files changed, 405 insertions(+), 15 deletions(-) diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/LwM2mTestObject.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/LwM2mTestObject.java index afe0d24624..9c831caee7 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/LwM2mTestObject.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/LwM2mTestObject.java @@ -26,6 +26,13 @@ import org.eclipse.leshan.client.resource.SimpleInstanceEnabler; import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.attributes.Attribute; +import org.eclipse.leshan.core.link.attributes.QuotedStringAttribute; +import org.eclipse.leshan.core.link.attributes.ResourceTypeAttribute; +import org.eclipse.leshan.core.link.attributes.UnquotedStringAttribute; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; +import org.eclipse.leshan.core.link.lwm2m.MixedLwM2mLink; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResourceInstance; @@ -57,6 +64,7 @@ public class LwM2mTestObject extends SimpleInstanceEnabler { public static final byte[] INITIAL_OPAQUE_VALUE = Hex.decodeHex("0123456789ABCDEF".toCharArray()); public static final Date INITIAL_TIME_VALUE = new Date(946684800000l); public static final ObjectLink INITIAL_OBJLINK_VALUE = new ObjectLink(3, 0); + public static final Link[] INITIAL_CORELINK_VALUE = new Link[] { new LwM2mLink(null, new LwM2mPath(3442)) }; private Random random = new Random(System.currentTimeMillis()); @@ -76,6 +84,7 @@ public LwM2mTestObject() { initialValues.put(150, INITIAL_OPAQUE_VALUE); initialValues.put(160, INITIAL_TIME_VALUE); initialValues.put(170, INITIAL_OBJLINK_VALUE); + initialValues.put(180, INITIAL_CORELINK_VALUE); // multi initialValues.put(1110, LwM2mResourceInstance.newStringInstance(0, INITIAL_STRING_VALUE)); @@ -86,6 +95,7 @@ public LwM2mTestObject() { initialValues.put(1150, LwM2mResourceInstance.newBinaryInstance(0, INITIAL_OPAQUE_VALUE)); initialValues.put(1160, LwM2mResourceInstance.newDateInstance(0, INITIAL_TIME_VALUE)); initialValues.put(1170, LwM2mResourceInstance.newObjectLinkInstance(0, INITIAL_OBJLINK_VALUE)); + initialValues.put(1180, LwM2mResourceInstance.newCoreLinkInstance(0, INITIAL_CORELINK_VALUE)); } private void clearValues() { @@ -103,6 +113,7 @@ private void clearValues() { clearedValues.put(150, new byte[0]); clearedValues.put(160, new Date(0l)); clearedValues.put(170, new ObjectLink()); + clearedValues.put(180, new Link[0]); // multi clearedValues.put(1110, Collections.EMPTY_MAP); @@ -113,6 +124,7 @@ private void clearValues() { clearedValues.put(1150, Collections.EMPTY_MAP); clearedValues.put(1160, Collections.EMPTY_MAP); clearedValues.put(1170, Collections.EMPTY_MAP); + clearedValues.put(1180, Collections.EMPTY_MAP); fireResourcesChange(applyValues(clearedValues)); } @@ -135,6 +147,7 @@ private void randomValues() { randomValues.put(150, new BytesGenerator().generate()); randomValues.put(160, new DateGenerator().generate()); randomValues.put(170, new ObjectLinkGenerator().generate()); + randomValues.put(180, new CoreLinkGenerator().generate()); // multi randomValues.put(1110, generateResourceInstances(new StringGenerator())); @@ -145,6 +158,7 @@ private void randomValues() { randomValues.put(1150, generateResourceInstances(new BytesGenerator())); randomValues.put(1160, generateResourceInstances(new DateGenerator())); randomValues.put(1170, generateResourceInstances(new ObjectLinkGenerator())); + randomValues.put(1180, generateResourceInstances(new CoreLinkGenerator())); fireResourcesChange(applyValues(randomValues)); } @@ -323,4 +337,67 @@ public ObjectLink generate() { return new ObjectLink(random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1)); } } + + class CoreLinkGenerator implements ValueGenerator { + + private LwM2mPath generatePath() { + int dice4Value = random.nextInt(4); + switch (dice4Value) { + case 0: + return new LwM2mPath(random.nextInt(ObjectLink.MAXID - 1)); + case 1: + return new LwM2mPath(random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1)); + case 2: + return new LwM2mPath(random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1), + random.nextInt(ObjectLink.MAXID - 1)); + case 3: + return new LwM2mPath(random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1), + random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1)); + } + return null; // should not happened + } + + private Attribute[] generateAttributes() { + int nbAttributes = random.nextInt(3); + Map attributes = new HashMap<>(nbAttributes); + for (int i = 0; i < nbAttributes; i++) { + int dice2value = random.nextInt(2); + Attribute attr = null; + switch (dice2value) { + case 0: + attr = new QuotedStringAttribute(RandomStringUtils.randomAlphabetic(random.nextInt(5) + 1), + RandomStringUtils.randomAlphanumeric(random.nextInt(5) + 1)); + break; + case 1: + attr = new UnquotedStringAttribute(RandomStringUtils.randomAlphabetic(random.nextInt(5) + 1), + RandomStringUtils.randomAlphanumeric(random.nextInt(5) + 1)); + break; + } + attributes.put(attr.getName(), attr); + } + return attributes.values().toArray(new Attribute[attributes.size()]); + } + + @Override + public Link[] generate() { + // define if root path is used or not + String rootpath = random.nextInt(4) == 0 ? "/" + RandomStringUtils.randomAlphanumeric(random.nextInt(4) + 1) + : null; + + // define number of link + int nbLink = random.nextInt(10); + // create links + Link[] links = new Link[nbLink]; + for (int i = 0; i < links.length; i++) { + // when there is a rootpath first link has oma attribute + if (rootpath != null && i == 0) { + links[i] = new MixedLwM2mLink(rootpath, LwM2mPath.ROOTPATH, new ResourceTypeAttribute("oma.lwm2m")); + } else { + // generate random link with random path and attributes + links[i] = new MixedLwM2mLink(rootpath, generatePath(), generateAttributes()); + } + } + return links; + } + } } \ No newline at end of file diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mMultipleResource.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mMultipleResource.java index 5d624886a5..a72b7d67a8 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mMultipleResource.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mMultipleResource.java @@ -25,6 +25,7 @@ import java.util.NoSuchElementException; import java.util.TreeMap; +import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.core.util.datatype.ULong; @@ -103,6 +104,9 @@ public static LwM2mMultipleResource newResource(int id, Map values, case OBJLNK: LwM2mNodeUtil.allElementsOfType(values.values(), ObjectLink.class); break; + case CORELINK: + LwM2mNodeUtil.allElementsOfType(values.values(), (new Link[] {}).getClass()); + break; case UNSIGNED_INTEGER: LwM2mNodeUtil.allElementsOfType(values.values(), ULong.class); break; @@ -142,6 +146,11 @@ public static LwM2mMultipleResource newObjectLinkResource(int id, Map values) { + LwM2mNodeUtil.noNullElements(values.values()); + return new LwM2mMultipleResource(id, values, Type.CORELINK); + } + public static LwM2mMultipleResource newBinaryResource(int id, Map values) { LwM2mNodeUtil.noNullElements(values.values()); return new LwM2mMultipleResource(id, values, Type.OPAQUE); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mResourceInstance.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mResourceInstance.java index f97bdda169..5036b116ee 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mResourceInstance.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mResourceInstance.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Date; +import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.util.datatype.ULong; @@ -63,6 +64,9 @@ public static LwM2mResourceInstance newInstance(int id, Object value) { if (value instanceof ObjectLink) { return new LwM2mResourceInstance(id, value, Type.OBJLNK); } + if (value instanceof Link[]) { + return new LwM2mResourceInstance(id, value, Type.CORELINK); + } if (value instanceof ULong) { return new LwM2mResourceInstance(id, value, Type.UNSIGNED_INTEGER); } @@ -101,6 +105,10 @@ public static LwM2mResourceInstance newInstance(int id, Object value, Type type) if (!(value instanceof ObjectLink)) throw new LwM2mNodeException(doesNotMatchMessage); break; + case CORELINK: + if (!(value instanceof Link[])) + throw new LwM2mNodeException(doesNotMatchMessage); + break; case UNSIGNED_INTEGER: if (!(value instanceof ULong)) throw new LwM2mNodeException(doesNotMatchMessage); @@ -123,6 +131,10 @@ public static LwM2mResourceInstance newObjectLinkInstance(int id, ObjectLink obj return new LwM2mResourceInstance(id, objlink, Type.OBJLNK); } + public static LwM2mResourceInstance newCoreLinkInstance(int id, Link[] coreLink) { + return new LwM2mResourceInstance(id, coreLink, Type.CORELINK); + } + public static LwM2mResourceInstance newBooleanInstance(int id, boolean value) { return new LwM2mResourceInstance(id, value, Type.BOOLEAN); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mSingleResource.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mSingleResource.java index 49533bb6a6..d3ee71ace7 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mSingleResource.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mSingleResource.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.NoSuchElementException; +import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.util.datatype.ULong; @@ -67,6 +68,9 @@ public static LwM2mSingleResource newResource(int id, Object value) { if (value instanceof ObjectLink) { return new LwM2mSingleResource(id, value, Type.OBJLNK); } + if (value instanceof Link[]) { + return new LwM2mSingleResource(id, value, Type.CORELINK); + } if (value instanceof ULong) { return new LwM2mSingleResource(id, value, Type.UNSIGNED_INTEGER); } @@ -116,6 +120,11 @@ public static LwM2mSingleResource newResource(int id, Object value, Type type) { throw new LwM2mNodeException( String.format(doesNotMatchMessage, value.getClass().getSimpleName(), type)); break; + case CORELINK: + if (!(value instanceof Link[])) + throw new LwM2mNodeException( + String.format(doesNotMatchMessage, value.getClass().getSimpleName(), type)); + break; case UNSIGNED_INTEGER: if (!(value instanceof ULong)) { throw new LwM2mNodeException( @@ -140,6 +149,10 @@ public static LwM2mSingleResource newObjectLinkResource(int id, ObjectLink objli return new LwM2mSingleResource(id, objlink, Type.OBJLNK); } + public static LwM2mSingleResource newCoreLinkResource(int id, Link[] coreLinks) { + return new LwM2mSingleResource(id, coreLinks, Type.CORELINK); + } + public static LwM2mSingleResource newBooleanResource(int id, boolean value) { return new LwM2mSingleResource(id, value, Type.BOOLEAN); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/cbor/LwM2mNodeCborDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/cbor/LwM2mNodeCborDecoder.java index 6cf3bf7af9..bdfa27997b 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/cbor/LwM2mNodeCborDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/cbor/LwM2mNodeCborDecoder.java @@ -17,6 +17,9 @@ import java.util.Date; +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.link.LinkParser; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -39,6 +42,22 @@ public class LwM2mNodeCborDecoder implements NodeDecoder { private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeCborDecoder.class); + // parser used for core link data type + private final LinkParser linkParser; + + public LwM2mNodeCborDecoder() { + this(new DefaultLwM2mLinkParser()); + } + + /** + * Create a new LwM2mNodeCborDecoder with a custom {@link LinkParser}. + * + * @param linkParser the link parser for core link format resources. + */ + public LwM2mNodeCborDecoder(LinkParser linkParser) { + this.linkParser = linkParser; + } + @Override @SuppressWarnings("unchecked") public T decode(byte[] content, LwM2mPath path, LwM2mModel model, Class nodeClass) @@ -155,6 +174,10 @@ private Object parseCborValue(CBORObject cborObject, Type type, LwM2mPath path) if (cborObject.getType() == CBORType.TextString) { return ObjectLink.decodeFromString(cborObject.AsString()); } + case CORELINK: + if (cborObject.getType() == CBORType.TextString) { + return linkParser.parseCoreLinkFormat(cborObject.AsString().getBytes()); + } case OPAQUE: if (cborObject.getType() == CBORType.ByteString) { return cborObject.GetByteString(); @@ -163,7 +186,7 @@ private Object parseCborValue(CBORObject cborObject, Type type, LwM2mPath path) default: throw new CodecException("Unsupported type %s for resource %s", type, path); } - } catch (IllegalStateException | ArithmeticException | NumberFormatException e) { + } catch (IllegalStateException | ArithmeticException | NumberFormatException | LinkParseException e) { throw new CodecException(e, "Unable to convert CBOR value %s of type %s in type %s for resource %s", cborObject.toString(), cborObject.getType(), type, path); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/cbor/LwM2mNodeCborEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/cbor/LwM2mNodeCborEncoder.java index b974d44c68..206a13660e 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/cbor/LwM2mNodeCborEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/cbor/LwM2mNodeCborEncoder.java @@ -17,6 +17,9 @@ import java.util.Date; +import org.eclipse.leshan.core.link.DefaultLinkSerializer; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -43,6 +46,16 @@ public class LwM2mNodeCborEncoder implements NodeEncoder { private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeCborEncoder.class); + private final LinkSerializer linkSerializer; + + public LwM2mNodeCborEncoder() { + this(new DefaultLinkSerializer()); + } + + public LwM2mNodeCborEncoder(LinkSerializer linkSerializer) { + this.linkSerializer = linkSerializer; + } + @Override public byte[] encode(LwM2mNode node, LwM2mPath path, LwM2mModel model, LwM2mValueConverter converter) throws CodecException { @@ -58,7 +71,7 @@ public byte[] encode(LwM2mNode node, LwM2mPath path, LwM2mModel model, LwM2mValu return internalEncoder.encoded; } - private static class InternalEncoder implements LwM2mNodeVisitor { + private class InternalEncoder implements LwM2mNodeVisitor { // visitor inputs private LwM2mPath path; private LwM2mModel model; @@ -153,6 +166,10 @@ private CBORObject getCborValue(Type expectedType, Object val) { ObjectLink objlnk = (ObjectLink) val; cbor = CBORObject.FromObject(objlnk.encodeToString()); break; + case CORELINK: + Link[] links = (Link[]) val; + cbor = CBORObject.FromObject(linkSerializer.serializeCoreLinkFormat(links)); + break; case OPAQUE: cbor = CBORObject.FromObject((byte[]) val); break; diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java index 3d8701336d..9b966acc61 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java @@ -32,6 +32,8 @@ import org.eclipse.leshan.core.json.LwM2mJsonDecoder; import org.eclipse.leshan.core.json.LwM2mJsonException; import org.eclipse.leshan.core.json.jackson.LwM2mJsonJacksonEncoderDecoder; +import org.eclipse.leshan.core.link.LinkParser; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -60,13 +62,15 @@ public class LwM2mNodeJsonDecoder implements TimestampedNodeDecoder { private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeJsonDecoder.class); private final LwM2mJsonDecoder decoder; + private final LinkParser linkParser; public LwM2mNodeJsonDecoder() { - this.decoder = new LwM2mJsonJacksonEncoderDecoder(); + this(new LwM2mJsonJacksonEncoderDecoder(), new DefaultLwM2mLinkParser()); } - public LwM2mNodeJsonDecoder(LwM2mJsonDecoder jsonDecoder) { + public LwM2mNodeJsonDecoder(LwM2mJsonDecoder jsonDecoder, LinkParser linkParser) { this.decoder = jsonDecoder; + this.linkParser = linkParser; } @SuppressWarnings("unchecked") @@ -455,6 +459,8 @@ private Object parseJsonValue(Object value, Type expectedType, LwM2mPath path) t return value; case OBJLNK: return ObjectLink.decodeFromString((String) value); + case CORELINK: + return linkParser.parseCoreLinkFormat(((String) value).getBytes()); default: throw new CodecException("Unsupported type %s for path %s", expectedType, path); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonEncoder.java index b3071a5870..32888ec281 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonEncoder.java @@ -25,6 +25,9 @@ import org.eclipse.leshan.core.json.LwM2mJsonEncoder; import org.eclipse.leshan.core.json.LwM2mJsonException; import org.eclipse.leshan.core.json.jackson.LwM2mJsonJacksonEncoderDecoder; +import org.eclipse.leshan.core.link.DefaultLinkSerializer; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -50,13 +53,15 @@ public class LwM2mNodeJsonEncoder implements TimestampedNodeEncoder { private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeJsonEncoder.class); private final LwM2mJsonEncoder encoder; + private final LinkSerializer linkSerializer; public LwM2mNodeJsonEncoder() { - this.encoder = new LwM2mJsonJacksonEncoderDecoder(); + this(new LwM2mJsonJacksonEncoderDecoder(), new DefaultLinkSerializer()); } - public LwM2mNodeJsonEncoder(LwM2mJsonEncoder jsonEncoder) { + public LwM2mNodeJsonEncoder(LwM2mJsonEncoder jsonEncoder, LinkSerializer linkSerializer) { this.encoder = jsonEncoder; + this.linkSerializer = linkSerializer; } @Override @@ -122,7 +127,7 @@ public byte[] encodeTimestampedData(List timestampedNodes, } - private static class InternalEncoder implements LwM2mNodeVisitor { + private class InternalEncoder implements LwM2mNodeVisitor { // visitor inputs private int objectId; private LwM2mModel model; @@ -292,6 +297,9 @@ private void setResourceValue(Object value, Type type, JsonArrayEntry jsonResour resourcePath); } break; + case CORELINK: + jsonResource.setStringValue(linkSerializer.serializeCoreLinkFormat((Link[]) value)); + break; default: throw new CodecException("Invalid value type %s for %s", type, resourcePath); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/senml/LwM2mNodeSenMLDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/senml/LwM2mNodeSenMLDecoder.java index 9ac12d11cd..24bab0e369 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/senml/LwM2mNodeSenMLDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/senml/LwM2mNodeSenMLDecoder.java @@ -28,6 +28,8 @@ import java.util.SortedMap; import java.util.TreeMap; +import org.eclipse.leshan.core.link.LinkParser; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -63,10 +65,17 @@ public class LwM2mNodeSenMLDecoder implements TimestampedNodeDecoder, MultiNodeD private final SenMLDecoder decoder; private boolean permissiveNumberConversion; + // parser used for core link data type + private final LinkParser linkParser; public LwM2mNodeSenMLDecoder(SenMLDecoder decoder, boolean permissiveNumberConversion) { + this(decoder, new DefaultLwM2mLinkParser(), permissiveNumberConversion); + } + + public LwM2mNodeSenMLDecoder(SenMLDecoder decoder, LinkParser linkParser, boolean permissiveNumberConversion) { this.decoder = decoder; this.permissiveNumberConversion = permissiveNumberConversion; + this.linkParser = linkParser; } @SuppressWarnings("unchecked") @@ -527,6 +536,8 @@ private Object parseResourceValue(Object value, Type expectedType, LwM2mPath pat return value; case OBJLNK: return ObjectLink.decodeFromString((String) value); + case CORELINK: + return linkParser.parseCoreLinkFormat(((String) value).getBytes()); default: throw new CodecException("Unsupported type %s for path %s", expectedType, path); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/senml/LwM2mNodeSenMLEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/senml/LwM2mNodeSenMLEncoder.java index 4f26495d38..0626f44560 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/senml/LwM2mNodeSenMLEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/senml/LwM2mNodeSenMLEncoder.java @@ -19,6 +19,9 @@ import java.util.Map; import java.util.Map.Entry; +import org.eclipse.leshan.core.link.DefaultLinkSerializer; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -49,9 +52,15 @@ public class LwM2mNodeSenMLEncoder implements TimestampedNodeEncoder, MultiNodeE private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeSenMLEncoder.class); private final SenMLEncoder encoder; + private final LinkSerializer linkSerializer; public LwM2mNodeSenMLEncoder(SenMLEncoder encoder) { + this(encoder, new DefaultLinkSerializer()); + } + + public LwM2mNodeSenMLEncoder(SenMLEncoder encoder, LinkSerializer linkSerializer) { this.encoder = encoder; + this.linkSerializer = linkSerializer; } @Override @@ -185,7 +194,7 @@ public byte[] encodeTimestampedNodes(TimestampedLwM2mNodes timestampedNodes, LwM } } - private static class InternalEncoder implements LwM2mNodeVisitor { + private class InternalEncoder implements LwM2mNodeVisitor { // visitor inputs private int objectId; private LwM2mModel model; @@ -342,6 +351,10 @@ private void setResourceValue(Object value, Type type, LwM2mPath resourcePath, S resourcePath); } break; + + case CORELINK: + record.setStringValue(linkSerializer.serializeCoreLinkFormat((Link[]) value)); + break; default: throw new CodecException("Invalid value type %s for %s", type, resourcePath); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/text/LwM2mNodeTextDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/text/LwM2mNodeTextDecoder.java index 4e422862b2..87adad9aab 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/text/LwM2mNodeTextDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/text/LwM2mNodeTextDecoder.java @@ -18,6 +18,9 @@ import java.nio.charset.StandardCharsets; import java.util.Date; +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.link.LinkParser; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -36,6 +39,22 @@ public class LwM2mNodeTextDecoder implements NodeDecoder { private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeTextDecoder.class); + // parser used for core link data type + private final LinkParser linkParser; + + public LwM2mNodeTextDecoder() { + this(new DefaultLwM2mLinkParser()); + } + + /** + * Create a new LwM2mNodeTextDecoder with a custom {@link LinkParser} + * + * @param linkParser the link parser for core link format resources. + */ + public LwM2mNodeTextDecoder(LinkParser linkParser) { + this.linkParser = linkParser; + } + @Override @SuppressWarnings("unchecked") public T decode(byte[] content, LwM2mPath path, LwM2mModel model, Class nodeClass) @@ -109,7 +128,13 @@ private Object parseTextValue(String value, Type type, LwM2mPath path) throws Co try { return ObjectLink.decodeFromString(value); } catch (IllegalArgumentException e) { - throw new CodecException(e, "Invalid value [%s] for objectLink resource [%s] ", value, path); + throw new CodecException(e, "Invalid value [%s] for objectLink resource [%s]", value, path); + } + case CORELINK: + try { + return linkParser.parseCoreLinkFormat(value.getBytes(StandardCharsets.UTF_8)); + } catch (LinkParseException e) { + throw new CodecException(e, "Invalid value [%s] for CoreLink resource [%s]", value, path); } case OPAQUE: if (!Base64.isBase64(value)) { diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/text/LwM2mNodeTextEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/text/LwM2mNodeTextEncoder.java index 7b55137323..c7e3a0ddcd 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/text/LwM2mNodeTextEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/text/LwM2mNodeTextEncoder.java @@ -18,6 +18,9 @@ import java.nio.charset.StandardCharsets; import java.util.Date; +import org.eclipse.leshan.core.link.DefaultLinkSerializer; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -41,6 +44,16 @@ public class LwM2mNodeTextEncoder implements NodeEncoder { private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeTextEncoder.class); + private final LinkSerializer linkSerializer; + + public LwM2mNodeTextEncoder() { + this(new DefaultLinkSerializer()); + } + + public LwM2mNodeTextEncoder(LinkSerializer linkSerializer) { + this.linkSerializer = linkSerializer; + } + @Override public byte[] encode(LwM2mNode node, LwM2mPath path, LwM2mModel model, LwM2mValueConverter converter) throws CodecException { @@ -56,7 +69,8 @@ public byte[] encode(LwM2mNode node, LwM2mPath path, LwM2mModel model, LwM2mValu return internalEncoder.encoded; } - private static class InternalEncoder implements LwM2mNodeVisitor { + private class InternalEncoder implements LwM2mNodeVisitor { + // visitor inputs private LwM2mPath path; private LwM2mModel model; @@ -134,6 +148,10 @@ private String getStringValue(Type expectedType, Object val) { ObjectLink objlnk = (ObjectLink) val; strValue = objlnk.encodeToString(); break; + case CORELINK: + Link[] links = (Link[]) val; + strValue = linkSerializer.serializeCoreLinkFormat(links); + break; case OPAQUE: byte[] binaryValue = (byte[]) val; strValue = Base64.encodeBase64String(binaryValue); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvDecoder.java index a12eb292ef..1ba4e0c494 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvDecoder.java @@ -19,10 +19,14 @@ import java.util.HashMap; import java.util.Map; +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.link.LinkParser; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; +import org.eclipse.leshan.core.node.InvalidLwM2mPathException; import org.eclipse.leshan.core.node.LwM2mIncompletePath; import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mNode; @@ -30,7 +34,6 @@ import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.node.InvalidLwM2mPathException; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.eclipse.leshan.core.node.LwM2mSingleResource; @@ -49,6 +52,22 @@ public class LwM2mNodeTlvDecoder implements NodeDecoder { private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeTlvDecoder.class); + // parser used for core link data type + private final LinkParser linkParser; + + public LwM2mNodeTlvDecoder() { + this(new DefaultLwM2mLinkParser()); + } + + /** + * Create a new LwM2mNodeTlvDecoder with a custom {@link LinkParser}. + * + * @param linkParser the link parser for core link format resources. + */ + public LwM2mNodeTlvDecoder(LinkParser linkParser) { + this.linkParser = linkParser; + } + @Override public T decode(byte[] content, LwM2mPath path, LwM2mModel model, Class nodeClass) throws CodecException { @@ -136,7 +155,8 @@ else if (nodeClass == LwM2mObjectInstance.class) { // Resource else if (nodeClass == LwM2mResource.class) { - // The object instance level should not be here, but if it is provided and consistent we tolerate it + // The object instance level should not be here, but if it is provided and + // consistent we tolerate it if (tlvs.length == 1 && tlvs[0].getType() == TlvType.OBJECT_INSTANCE) { if (tlvs[0].getIdentifier() != path.getObjectInstanceId()) { throw new CodecException("Id conflict between path [%s] and instance TLV [object instance id=%d]", @@ -147,7 +167,8 @@ else if (nodeClass == LwM2mResource.class) { ResourceModel resourceModel = model.getResourceModel(path.getObjectId(), path.getResourceId()); if (tlvs.length == 0 && resourceModel != null && !resourceModel.multiple) { - // If there is no TlV value and we know that this resource is a single resource we raise an exception + // If there is no TlV value and we know that this resource is a single resource + // we raise an exception // else we consider this is a multi-instance resource throw new CodecException("TLV payload is mandatory for single resource %s", path); } else if (tlvs.length == 1 && tlvs[0].getType() != TlvType.RESOURCE_INSTANCE) { @@ -284,10 +305,12 @@ private Object parseTlvValue(byte[] value, Type expectedType, LwM2mPath path) th return value; case OBJLNK: return TlvDecoder.decodeObjlnk(value); + case CORELINK: + return linkParser.parseCoreLinkFormat(value); default: throw new CodecException("Unsupported type %s for path %s", expectedType, path); } - } catch (TlvException e) { + } catch (TlvException | LinkParseException e) { throw new CodecException(e, "Invalid content [%s] for type %s for path %s", Hex.encodeHexString(value), expectedType, path); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvEncoder.java index 9ac8817700..fb2d942c29 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvEncoder.java @@ -21,6 +21,9 @@ import java.util.Date; import java.util.Map.Entry; +import org.eclipse.leshan.core.link.DefaultLinkSerializer; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -50,6 +53,16 @@ public class LwM2mNodeTlvEncoder implements NodeEncoder { private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeTlvEncoder.class); + private final LinkSerializer linkSerializer; + + public LwM2mNodeTlvEncoder() { + this(new DefaultLinkSerializer()); + } + + public LwM2mNodeTlvEncoder(LinkSerializer linkSerializer) { + this.linkSerializer = linkSerializer; + } + @Override public byte[] encode(LwM2mNode node, LwM2mPath path, LwM2mModel model, LwM2mValueConverter converter) throws CodecException { @@ -65,7 +78,7 @@ public byte[] encode(LwM2mNode node, LwM2mPath path, LwM2mModel model, LwM2mValu return internalEncoder.out.toByteArray(); } - private static class InternalEncoder implements LwM2mNodeVisitor { + private class InternalEncoder implements LwM2mNodeVisitor { // visitor inputs private LwM2mPath path; @@ -216,6 +229,8 @@ private byte[] encodeTlvValue(Object value, Type type, LwM2mPath path) { return (byte[]) value; case OBJLNK: return TlvEncoder.encodeObjlnk((ObjectLink) value); + case CORELINK: + return TlvEncoder.encodeString(linkSerializer.serializeCoreLinkFormat((Link[]) value)); default: throw new CodecException("Invalid value %s for type %s of %s", value, type, path); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/WriteRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/WriteRequest.java index 57cbf18f1a..2c2b5fa7fd 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/WriteRequest.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/WriteRequest.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.Map; +import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mNode; @@ -263,6 +264,24 @@ public WriteRequest(ContentFormat contentFormat, int objectId, int objectInstanc LwM2mSingleResource.newObjectLinkResource(resourceId, value), null); } + /** + * Request to write a CoreLnk Single-Instance Resource using the TLV content format. + */ + public WriteRequest(int objectId, int objectInstanceId, int resourceId, Link[] value) { + this(ContentFormat.TLV, objectId, objectInstanceId, resourceId, value); + } + + /** + * Request to write a CoreLnk Single-Instance Resource using the given content format (TLV, JSON, TEXT). + * + * @exception InvalidRequestException if bad @{link ContentFormat} format was used. + */ + public WriteRequest(ContentFormat contentFormat, int objectId, int objectInstanceId, int resourceId, Link[] value) + throws InvalidRequestException { + this(Mode.REPLACE, contentFormat, newPath(objectId, objectInstanceId, resourceId), + LwM2mSingleResource.newCoreLinkResource(resourceId, value), null); + } + // ***************** write multi instance resource ****************** // /** * Request to write a Multi-Instance Resource. diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoderTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoderTest.java index a55450504e..fba8689dd5 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoderTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoderTest.java @@ -27,6 +27,7 @@ import java.util.Map; import org.eclipse.leshan.core.json.LwM2mJsonException; +import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; @@ -49,6 +50,7 @@ import org.eclipse.leshan.core.tlv.Tlv.TlvType; import org.eclipse.leshan.core.tlv.TlvEncoder; import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.core.util.TestLwM2mId; import org.eclipse.leshan.core.util.TestObjectLoader; import org.eclipse.leshan.core.util.datatype.ULong; import org.junit.Assert; @@ -120,6 +122,11 @@ public static void loadModel() { "080026C8000B6D79536572766963652031C8010F496E7465726E65742E31352E323334C40200430000080126C8000B6D79536572766963652032C8010F496E7465726E65742E31352E323335C402FFFFFFFF" .toCharArray()); + // tlv content for object 3442 instance (contain core link value) + private final static byte[] ENCODED_OBJ3442 = Hex.decodeHex( + "080072c8b46f3c2f3e3b72743d226f6d612e6c776d326d223b63743d223630203131302031313220313534322031353433203131353432203131353433222c3c2f313e3b7665723d312e312c3c2f312f303e2c3c2f323e2c3c2f333e3b7665723d312e312c3c2f332f303e2c3c2f333434322f303e" + .toCharArray()); + private void assertDeviceInstance(LwM2mObjectInstance oInstance) { assertEquals(0, oInstance.getId()); @@ -206,6 +213,38 @@ private void assertObj66Instance(LwM2mObject oObject) { assertEquals("myService 2", oInstance2.getResource(0).getValue()); } + private void assertObj3442Instance(LwM2mObjectInstance instance) { + assertEquals(0, instance.getId()); + + // instance 0 + assertEquals(1, instance.getResources().size()); + + Link[] links = (Link[]) instance.getResource(TestLwM2mId.CORELNK_VALUE).getValue(); + + assertEquals(7, links.length); + + assertEquals("/", links[0].getUriReference()); + assertEquals(2, links[0].getAttributes().asCollection().size()); + + assertEquals("/1", links[1].getUriReference()); + assertEquals(1, links[1].getAttributes().asCollection().size()); + + assertEquals("/1/0", links[2].getUriReference()); + assertEquals(0, links[2].getAttributes().asCollection().size()); + + assertEquals("/2", links[3].getUriReference()); + assertEquals(0, links[3].getAttributes().asCollection().size()); + + assertEquals("/3", links[4].getUriReference()); + assertEquals(1, links[4].getAttributes().asCollection().size()); + + assertEquals("/3/0", links[5].getUriReference()); + assertEquals(0, links[5].getAttributes().asCollection().size()); + + assertEquals("/3442/0", links[6].getUriReference()); + assertEquals(0, links[6].getAttributes().asCollection().size()); + } + @Test public void text_manufacturer_resource() throws CodecException { String value = "MyManufacturer"; @@ -467,6 +506,13 @@ public void tlv_invalid_multi_resource_2_instance_with_the_same_id() { decoder.decode(content, ContentFormat.TLV, new LwM2mPath(3, 0, 22), model); } + @Test + public void tlv_instance_with_core_link_value() { + LwM2mObjectInstance instance = (LwM2mObjectInstance) decoder.decode(ENCODED_OBJ3442, ContentFormat.TLV, + new LwM2mPath(3442, 0), model); + assertObj3442Instance(instance); + } + @Test public void json_device_object_instance0() throws CodecException { // json content for instance 0 of device object @@ -1285,4 +1331,5 @@ public void senml_multiple_timestamped_nodes() throws CodecException { assertEquals(expectedResult.build(), data); } + } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/write/WriteSingleValueTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/write/WriteSingleValueTest.java index 542b45de70..ab8d4b0dfa 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/write/WriteSingleValueTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/write/WriteSingleValueTest.java @@ -26,8 +26,15 @@ import java.util.Date; import org.eclipse.californium.core.coap.Response; +import org.eclipse.leshan.core.LwM2m; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.attributes.QuotedStringAttribute; +import org.eclipse.leshan.core.link.attributes.UnquotedStringAttribute; +import org.eclipse.leshan.core.link.lwm2m.MixedLwM2mLink; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; import org.eclipse.leshan.core.model.ResourceModel.Type; +import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.eclipse.leshan.core.node.LwM2mSingleResource; @@ -213,6 +220,31 @@ public void write_time_resource() throws InterruptedException { assertEquals(expectedvalue, resource.getValue()); } + @Test + public void write_corelnk_resource() throws InterruptedException { + // write resource + Link[] expectedvalue = new Link[3]; + expectedvalue[0] = new MixedLwM2mLink(null, new LwM2mPath(3), + LwM2mAttributes.create(LwM2mAttributes.OBJECT_VERSION, new LwM2m.Version("1.2"))); + expectedvalue[1] = new MixedLwM2mLink(null, new LwM2mPath(3, 1)); + expectedvalue[2] = new MixedLwM2mLink(null, new LwM2mPath(3, 1, 0), + new QuotedStringAttribute("attr1", "attr1Value"), new UnquotedStringAttribute("attr2", "attr2Value")); + + WriteResponse response = helper.server.send(helper.getCurrentRegistration(), + new WriteRequest(contentFormat, TestLwM2mId.TEST_OBJECT, 0, TestLwM2mId.CORELNK_VALUE, expectedvalue)); + + // verify result + assertEquals(ResponseCode.CHANGED, response.getCode()); + assertNotNull(response.getCoapResponse()); + assertThat(response.getCoapResponse(), is(instanceOf(Response.class))); + + // read resource to check the value changed + ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), + new ReadRequest(contentFormat, TestLwM2mId.TEST_OBJECT, 0, TestLwM2mId.CORELNK_VALUE), 100000000); + LwM2mResource resource = (LwM2mResource) readResponse.getContent(); + assertArrayEquals(expectedvalue, (Link[]) resource.getValue()); + } + @Test public void write_unsigned_integer_resource() throws InterruptedException { // write resource diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/JacksonLwM2mNodeDeserializer.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/JacksonLwM2mNodeDeserializer.java index e756849617..22778e0949 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/JacksonLwM2mNodeDeserializer.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/JacksonLwM2mNodeDeserializer.java @@ -23,6 +23,9 @@ import java.util.Iterator; import java.util.Map; +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.link.LinkParser; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mMultipleResource; @@ -46,6 +49,8 @@ public class JacksonLwM2mNodeDeserializer extends JsonDeserializer { + private LinkParser linkparser = new DefaultLwM2mLinkParser(); + @Override public LwM2mNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { @@ -219,6 +224,16 @@ private Object deserializeValue(JsonNode val, ResourceModel.Type type) { } raiseUnexpectedType(val, type, "object{objectId:integer, objectInstanceId:integer}", val.getNodeType()); break; + case CORELINK: + if (val.isTextual()) { + try { + return linkparser.parseCoreLinkFormat(val.asText().getBytes()); + } catch (LinkParseException e) { + throw new IllegalArgumentException("Invalid Links: " + e.getMessage(), e); + } + } + raiseUnexpectedType(val, type, "object{objectId:integer, objectInstanceId:integer}", val.getNodeType()); + break; case UNSIGNED_INTEGER: // we use String for UNSIGNED_INTEGER because // Javascript number does not support safely number larger than Number.MAX_SAFE_INTEGER (2^53 - 1) diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/JacksonLwM2mNodeSerializer.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/JacksonLwM2mNodeSerializer.java index 1c6b2e4f5a..6fcbcc258d 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/JacksonLwM2mNodeSerializer.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/JacksonLwM2mNodeSerializer.java @@ -21,6 +21,9 @@ import java.util.Map; import java.util.Map.Entry; +import org.eclipse.leshan.core.link.DefaultLinkSerializer; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mObject; @@ -37,6 +40,8 @@ public class JacksonLwM2mNodeSerializer extends StdSerializer { private static final long serialVersionUID = 1L; + private LinkSerializer linkSerializer = new DefaultLinkSerializer(); + protected JacksonLwM2mNodeSerializer(Class t) { super(t); } @@ -97,6 +102,8 @@ private Object convertValue(Type type, Object value) { // We use String to be consistent with INTEGER but to be sure to not get any restriction from javascript // world. return value.toString(); + case CORELINK: + return linkSerializer.serializeCoreLinkFormat((Link[]) value); default: return value; }