From 291c926aa46d600f2de58973896f7d833ff88e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Wadowski?= Date: Wed, 2 Mar 2022 10:12:50 +0100 Subject: [PATCH 01/20] Add Timestamped nodes support to Send Operation at server side. --- .../request/CoapRequestBuilder.java | 2 +- .../core/node/TimestampedLwM2mNodes.java | 164 +++++++++++++++ .../core/node/codec/DefaultLwM2mDecoder.java | 15 ++ .../core/node/codec/DefaultLwM2mEncoder.java | 7 + .../leshan/core/node/codec/LwM2mDecoder.java | 14 ++ .../leshan/core/node/codec/LwM2mEncoder.java | 14 ++ .../codec/TimestampedMultiNodeDecoder.java | 38 ++++ .../codec/senml/LwM2mNodeSenMLDecoder.java | 28 ++- .../leshan/core/request/SendRequest.java | 43 +++- .../core/node/TimestampedLwM2mNodesTest.java | 189 ++++++++++++++++++ .../core/node/codec/LwM2mNodeDecoderTest.java | 38 ++++ .../integration/tests/RegistrationTest.java | 6 +- .../integration/tests/send/SendTest.java | 13 +- .../tests/send/SendTimestampedTest.java | 156 +++++++++++++++ .../tests/util/IntegrationTestHelper.java | 2 +- .../util/SecureIntegrationTestHelper.java | 4 +- .../tests/util/SynchronousSendListener.java | 12 +- .../util/TimestampedInstanceEnabler.java | 34 ++++ .../server/californium/send/SendResource.java | 9 +- .../server/californium/DummyDecoder.java | 7 + .../leshan/server/send/SendHandler.java | 18 +- .../leshan/server/send/SendListener.java | 6 +- .../server/demo/servlet/EventServlet.java | 4 +- 23 files changed, 768 insertions(+), 55 deletions(-) create mode 100644 leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java create mode 100644 leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/TimestampedMultiNodeDecoder.java create mode 100644 leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java create mode 100644 leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java create mode 100644 leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TimestampedInstanceEnabler.java diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java index 9a65831eae..fb924871f9 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java @@ -162,7 +162,7 @@ public void visit(SendRequest request) { ContentFormat format = request.getFormat(); coapRequest.getOptions().setContentFormat(format.getCode()); - coapRequest.setPayload(encoder.encodeNodes(request.getNodes(), format, model)); + coapRequest.setPayload(encoder.encodeMultiTimestampedNodes(request.getTimestampedNodes(), format, model)); } public Request getRequest() { diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java new file mode 100644 index 0000000000..614eeef04d --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2021 Orange. + * + * 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: + * Orange - Send with multiple-timestamped values + *******************************************************************************/ +package org.eclipse.leshan.core.node; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * A container for nodes {@link LwM2mNode} with path {@link LwM2mPath} and optional timestamp information. + */ +public class TimestampedLwM2mNodes { + + private final Map> timestampedPathNodesMap; + + private TimestampedLwM2mNodes(Map> timestampedPathNodesMap) { + this.timestampedPathNodesMap = timestampedPathNodesMap; + } + + /** + * Get maps {@link LwM2mPath}-{@link LwM2mNode} grouped by timestamp with ascending order. + * Null timestamp keys are allowed, and are considered as most recent one. + */ + public Map> getTimestampedNodes() { + return timestampedPathNodesMap; + } + + /** + * Get nodes for specific timestamp. Null timestamp is allowed. + * + * @param timestamp + * @return map of {@link LwM2mPath}-{@link LwM2mNode} or null if there is no value for asked timestamp. + */ + public Map getNodesForTimestamp(Long timestamp) { + return timestampedPathNodesMap.get(timestamp); + } + + /** + * Get all collected nodes as {@link LwM2mPath}-{@link LwM2mNode} map ignoring timestamp information. In case + * of the same path conflict the most recent one is taken. Null timestamp is considered as most recent one. + */ + public Map getNodes() { + Map result = new HashMap<>(); + for (Map.Entry> entry : timestampedPathNodesMap.entrySet()) { + result.putAll(entry.getValue()); + } + return result; + } + + /** + * Returns the all sorted timestamps of contained nodes with ascending order. Null timestamp is considered as most + * recent one. + */ + public Set getTimestamps() { + return timestampedPathNodesMap.keySet(); + } + + @Override + public String toString() { + return String.format("TimestampedLwM2mNodes [timestampedPathNodesMap=%s, timestampedNodes=%s]", + timestampedPathNodesMap); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((timestampedPathNodesMap == null) ? 0 : timestampedPathNodesMap.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TimestampedLwM2mNodes other = (TimestampedLwM2mNodes) obj; + if (timestampedPathNodesMap == null) { + if (other.timestampedPathNodesMap != null) + return false; + } else if (!timestampedPathNodesMap.equals(other.timestampedPathNodesMap)) + return false; + return true; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final Map> timestampedPathNodesMap = getTimestampedMap(); + + public Builder addNodes(Map pathNodesMap) { + timestampedPathNodesMap.put(null, pathNodesMap); + return this; + } + + public Builder put(Long timestamp, LwM2mPath path, LwM2mNode node) { + if (!timestampedPathNodesMap.containsKey(timestamp)) { + timestampedPathNodesMap.put(timestamp, new HashMap<>()); + } + timestampedPathNodesMap.get(timestamp).put(path, node); + return this; + } + + public Builder put(LwM2mPath path, LwM2mNode node) { + put(null, path, node); + return this; + } + + public Builder add(TimestampedLwM2mNodes timestampedNodes) { + for (Map.Entry> entry : timestampedNodes.getTimestampedNodes().entrySet()) { + Long timestamp = entry.getKey(); + Map pathNodeMap = entry.getValue(); + + for (Map.Entry pathNodeEntry : pathNodeMap.entrySet()) { + LwM2mPath path = pathNodeEntry.getKey(); + LwM2mNode node = pathNodeEntry.getValue(); + put(timestamp, path, node); + } + } + return this; + } + + public TimestampedLwM2mNodes build() { + return new TimestampedLwM2mNodes(timestampedPathNodesMap); + } + + private static TreeMap> getTimestampedMap() { + return new TreeMap<>(getTimestampComparator()); + } + + private static Comparator getTimestampComparator() { + return (o1, o2) -> { + if (o1 == null) { + return (o2 == null) ? 0 : 1; + } else if (o2 == null) { + return -1; + } else { + return o1.compareTo(o2); + } + }; + } + } +} \ No newline at end of file diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java index 93a902882f..f1ca780d0b 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java @@ -31,6 +31,7 @@ import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.eclipse.leshan.core.node.TimestampedLwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.node.codec.cbor.LwM2mNodeCborDecoder; import org.eclipse.leshan.core.node.codec.json.LwM2mNodeJsonDecoder; import org.eclipse.leshan.core.node.codec.opaque.LwM2mNodeOpaqueDecoder; @@ -181,6 +182,20 @@ public Map decodeNodes(byte[] content, ContentFormat forma } } + @Override + public TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) + throws CodecException { + NodeDecoder decoder = nodeDecoders.get(format); + + if (decoder instanceof TimestampedMultiNodeDecoder) { + return ((TimestampedMultiNodeDecoder) decoder).decodeMultiTimestampedNodes(content, model); + } else { + throw new CodecException( + "Decoder does not support multiple timestamped nodes decoding for this content format %s [%s] ", + format); + } + } + @Override public List decodeTimestampedData(byte[] content, ContentFormat format, LwM2mPath path, LwM2mModel model) throws CodecException { diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java index 3b1ed67ad7..05ce2f66da 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java @@ -25,6 +25,7 @@ import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.TimestampedLwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.node.codec.cbor.LwM2mNodeCborEncoder; import org.eclipse.leshan.core.node.codec.json.LwM2mNodeJsonEncoder; import org.eclipse.leshan.core.node.codec.opaque.LwM2mNodeOpaqueEncoder; @@ -226,4 +227,10 @@ public boolean isSupported(ContentFormat format) { public Set getSupportedContentFormat() { return nodeEncoders.keySet(); } + + @Override + public byte[] encodeMultiTimestampedNodes(TimestampedLwM2mNodes data, ContentFormat format, LwM2mModel model) + throws CodecException { + return encodeNodes(data.getNodes(), format, model); + } } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mDecoder.java index 00aa0abdda..9106f4ec18 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mDecoder.java @@ -25,6 +25,7 @@ import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.TimestampedLwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.request.ContentFormat; /** @@ -84,6 +85,19 @@ T decode(byte[] content, ContentFormat format, LwM2mPath p Map decodeNodes(byte[] content, ContentFormat format, List paths, LwM2mModel model) throws CodecException; + /** + * Deserializes a binary content into a {@link TimestampedLwM2mNodes}. + *

+ * + * @param content the content + * @param format the content format + * @param model the collection of supported object models + * @return the decoded timestamped nodes represented by {@link TimestampedLwM2mNodes} + * @throws CodecException if content is malformed. + */ + TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) + throws CodecException; + /** * Deserializes a binary content into a list of time-stamped {@link LwM2mNode} ordering by time-stamp. * diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mEncoder.java index 2441ac5081..1ef1d90511 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mEncoder.java @@ -23,6 +23,7 @@ import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.TimestampedLwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.request.ContentFormat; /** @@ -93,4 +94,17 @@ byte[] encodeTimestampedData(List timestampedNodes, Conten * @return {@link ContentFormat} supported by this Encoder */ Set getSupportedContentFormat(); + + /** + * Serializes a multiple time-stamped nodes contained in {@link TimestampedLwM2mNodes} with the given content + * format. + * + * @param data the {@link TimestampedLwM2mNodes} to serialize + * @param format the content format + * @param model the collection of supported object models + * @return the encoded node as a byte array + * @throws CodecException if encoding failed. + */ + byte[] encodeMultiTimestampedNodes(TimestampedLwM2mNodes data, ContentFormat format, LwM2mModel model) + throws CodecException; } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/TimestampedMultiNodeDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/TimestampedMultiNodeDecoder.java new file mode 100644 index 0000000000..1e159e6e06 --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/TimestampedMultiNodeDecoder.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2021 Orange. + * + * 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: + * Orange - Send with multiple-timestamped values + *******************************************************************************/ +package org.eclipse.leshan.core.node.codec; + +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; + +/** + * A decoder for {@link TimestampedLwM2mNodes}. + * + * @see DefaultLwM2mDecoder + */ +public interface TimestampedMultiNodeDecoder { + /** + * Deserializes a binary content into a {@link TimestampedLwM2mNodes}. + *

+ * + * @param content the content + * @param model the collection of supported object models + * @return the decoded timestamped nodes represented by {@link TimestampedLwM2mNodes} + * @throws CodecException if content is malformed. + */ + TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, LwM2mModel model) throws CodecException; + +} 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 d269506e1a..94969cbe50 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 @@ -41,9 +41,11 @@ import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.TimestampedLwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.MultiNodeDecoder; +import org.eclipse.leshan.core.node.codec.TimestampedMultiNodeDecoder; import org.eclipse.leshan.core.node.codec.TimestampedNodeDecoder; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.datatype.NumberUtil; @@ -55,7 +57,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class LwM2mNodeSenMLDecoder implements TimestampedNodeDecoder, MultiNodeDecoder { +public class LwM2mNodeSenMLDecoder implements TimestampedNodeDecoder, MultiNodeDecoder, TimestampedMultiNodeDecoder { private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeSenMLDecoder.class); @@ -177,6 +179,30 @@ public List decodeTimestampedData(byte[] content, LwM2mPat } } + @Override + public TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, LwM2mModel model) throws CodecException { + try { + // Decode SenML pack + SenMLPack pack = decoder.fromSenML(content); + + TimestampedLwM2mNodes.Builder nodes = TimestampedLwM2mNodes.builder(); + + LwM2mSenMLResolver resolver = new LwM2mSenMLResolver(); + for (SenMLRecord record : pack.getRecords()) { + LwM2mResolvedSenMLRecord resolvedRecord = resolver.resolve(record); + LwM2mPath path = resolvedRecord.getPath(); + LwM2mNode node = parseRecords(Arrays.asList(resolvedRecord), path, model, + DefaultLwM2mDecoder.nodeClassFromPath(path)); + nodes.put(resolvedRecord.getTimeStamp(), path, node); + } + + return nodes.build(); + } catch (SenMLException e) { + String hexValue = content != null ? Hex.encodeHexString(content) : ""; + throw new CodecException(e, "Unable to decode nodes : %s", hexValue, e); + } + } + /** * Parse records for a given LWM2M path. */ diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/SendRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/SendRequest.java index 7417b35d9d..8825ac1da6 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/SendRequest.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/SendRequest.java @@ -24,6 +24,7 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.response.SendResponse; import org.eclipse.leshan.core.util.Validate; @@ -38,8 +39,8 @@ public class SendRequest implements UplinkRequest { private final ContentFormat format; - private final Map nodes; private final Object coapRequest; + private final TimestampedLwM2mNodes timestampedNodes; /** * @param format {@link ContentFormat} used to encode data. It MUST be {@link ContentFormat#SENML_CBOR} or @@ -51,7 +52,21 @@ public SendRequest(ContentFormat format, Map nodes) { this(format, nodes, null); } + public SendRequest(ContentFormat format, TimestampedLwM2mNodes timestampedNodes, Object coapRequest) { + this.timestampedNodes = timestampedNodes; + // Validate Format + if (format == null || !(format.equals(ContentFormat.SENML_CBOR) || format.equals(ContentFormat.SENML_JSON))) { + throw new InvalidRequestException("Content format MUST be SenML_CBOR or SenML_JSON but was " + format); + } + // Validate Nodes + validateNodes(timestampedNodes.getNodes()); + + this.format = format; + this.coapRequest = coapRequest; + } + public SendRequest(ContentFormat format, Map nodes, Object coapRequest) { + timestampedNodes = TimestampedLwM2mNodes.builder().addNodes(nodes).build(); // Validate Format if (format == null || !(format.equals(ContentFormat.SENML_CBOR) || format.equals(ContentFormat.SENML_JSON))) { throw new InvalidRequestException("Content format MUST be SenML_CBOR or SenML_JSON but was " + format); @@ -60,7 +75,6 @@ public SendRequest(ContentFormat format, Map nodes, Object validateNodes(nodes); this.format = format; - this.nodes = nodes; this.coapRequest = coapRequest; } @@ -86,15 +100,15 @@ private void validateNodes(Map nodes) { } } + public TimestampedLwM2mNodes getTimestampedNodes() { + return timestampedNodes; + } + @Override public Object getCoapRequest() { return coapRequest; } - public Map getNodes() { - return nodes; - } - public ContentFormat getFormat() { return format; } @@ -106,15 +120,16 @@ public void accept(UplinkRequestVisitor visitor) { @Override public String toString() { - return String.format("SendRequest [format=%s, nodes=%s]", format, nodes); + return String.format("SendRequest [format=%s, timestampedNodes=%s]", format, timestampedNodes); } @Override public int hashCode() { final int prime = 31; int result = 1; + result = prime * result + ((coapRequest == null) ? 0 : coapRequest.hashCode()); result = prime * result + ((format == null) ? 0 : format.hashCode()); - result = prime * result + ((nodes == null) ? 0 : nodes.hashCode()); + result = prime * result + ((timestampedNodes == null) ? 0 : timestampedNodes.hashCode()); return result; } @@ -127,16 +142,22 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; SendRequest other = (SendRequest) obj; + if (coapRequest == null) { + if (other.coapRequest != null) + return false; + } else if (!coapRequest.equals(other.coapRequest)) + return false; if (format == null) { if (other.format != null) return false; } else if (!format.equals(other.format)) return false; - if (nodes == null) { - if (other.nodes != null) + if (timestampedNodes == null) { + if (other.timestampedNodes != null) return false; - } else if (!nodes.equals(other.nodes)) + } else if (!timestampedNodes.equals(other.timestampedNodes)) return false; return true; } + } diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java new file mode 100644 index 0000000000..186929dc4b --- /dev/null +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java @@ -0,0 +1,189 @@ +/******************************************************************************* + * Copyright (c) 2021 Orange. + * + * 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: + * Orange - Send with multiple-timestamped values + *******************************************************************************/ +package org.eclipse.leshan.core.node; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.junit.Test; + +public class TimestampedLwM2mNodesTest { + + @Test + public void should_getPathNodesMapForTimestamp_pick_specific_timestamp_nodes() { + // given + TimestampedLwM2mNodes tsNodes = getExampleTimestampedLwM2mNodes(); + + // when + Map tsNodesMap = tsNodes.getNodesForTimestamp(123L); + + // then + assertNotNull(tsNodesMap); + assertTrue(tsNodesMap.containsKey(new LwM2mPath("/0/0/1"))); + assertEquals(1L, tsNodesMap.get(new LwM2mPath("/0/0/1")).getId()); + } + + @Test + public void should_getPathNodesMapForTimestamp_pick_null_timestamp_nodes() { + // given + TimestampedLwM2mNodes tsNodes = getExampleMixedTimestampLwM2mNodes(); + + // when + Map tsNodesMap = tsNodes.getNodesForTimestamp(null); + + // then + assertNotNull(tsNodesMap); + assertTrue(tsNodesMap.containsKey(new LwM2mPath("/0/0/2"))); + assertEquals(2L, tsNodesMap.get(new LwM2mPath("/0/0/2")).getId()); + } + + @Test + public void should_getPathNodesMapForTimestamp_returns_null_for_nonexistent_timestamp() { + // given + TimestampedLwM2mNodes tsNodes = getExampleTimestampedLwM2mNodes(); + + // when + Map tsNodesMap = tsNodes.getNodesForTimestamp(0L); + + // then + assertNull(tsNodesMap); + } + + @Test + public void should_getNodes_returns_all_nodes_ignoring_timestamp() { + // given + TimestampedLwM2mNodes tsNodes = getExampleTimestampedLwM2mNodes(); + + // when + Map tsNodesMap = tsNodes.getNodes(); + + // then + assertNotNull(tsNodesMap); + assertTrue(tsNodesMap.containsKey(new LwM2mPath("/0/0/1"))); + assertTrue(tsNodesMap.containsKey(new LwM2mPath("/0/0/2"))); + assertEquals(1L, tsNodesMap.get(new LwM2mPath("/0/0/1")).getId()); + assertEquals(2L, tsNodesMap.get(new LwM2mPath("/0/0/2")).getId()); + } + + @Test + public void should_getNodes_returns_latest_node_if_path_conflict() { + // given + TimestampedLwM2mNodes tsNodes = getSamePathTimestampedLwM2mNodes(); + + // when + Map tsNodesMap = tsNodes.getNodes(); + + // then + assertNotNull(tsNodesMap); + assertTrue(tsNodesMap.containsKey(new LwM2mPath("/0/0/1"))); + assertEquals(222L, ((LwM2mSingleResource)tsNodesMap.get(new LwM2mPath("/0/0/1"))).getValue() ); + } + + @Test + public void should_getNodesForTimestamp_returns_ascending_ordered_nodes() { + // given + TimestampedLwM2mNodes tsNodes = getExampleTimestampedLwM2mNodes(); + + // when + Map> tsNodesMap = tsNodes.getTimestampedNodes(); + + // then + assertNotNull(tsNodesMap); + Iterator iterator = tsNodesMap.keySet().iterator(); + assertEquals(123L, iterator.next().longValue()); + assertEquals(456L, iterator.next().longValue()); + } + + @Test + public void should_null_timestamp_be_considered_as_latest_for_getNodesForTimestamp() { + // given + TimestampedLwM2mNodes tsNodes = getExampleMixedTimestampLwM2mNodes(); + + // when + Map> tsNodesMap = tsNodes.getTimestampedNodes(); + + // then + assertNotNull(tsNodesMap); + Iterator iterator = tsNodesMap.keySet().iterator(); + assertEquals(123L, iterator.next().longValue()); + assertNull(iterator.next()); + } + + @Test + public void should_getNodes_returns_empty_map_for_empty_TimestampedLwM2mNodes() { + // given + TimestampedLwM2mNodes tsNodes = TimestampedLwM2mNodes.builder().build(); + + // when + Map tsNodesMap = tsNodes.getNodes(); + + // then + assertNotNull(tsNodesMap); + assertTrue(tsNodesMap.isEmpty()); + } + + @Test + public void should_getTimestamps_returns_all_timestamps() { + // given + TimestampedLwM2mNodes tsNodes = getExampleTimestampedLwM2mNodes(); + + // when + Set timestamps = tsNodes.getTimestamps(); + + // then + assertNotNull(timestamps); + assertEquals(new HashSet<>(Arrays.asList(123L, 456L)), timestamps); + } + + @Test + public void should_null_timestamp_be_considered_as_latest_for_getTimestamps() { + // given + TimestampedLwM2mNodes tsNodes = getExampleMixedTimestampLwM2mNodes(); + + // when + Set timestamps = tsNodes.getTimestamps(); + + // then + assertNotNull(timestamps); + assertEquals(new HashSet<>(Arrays.asList(123L, null)), timestamps); + } + + private TimestampedLwM2mNodes getExampleTimestampedLwM2mNodes() { + TimestampedLwM2mNodes.Builder tsNodes = TimestampedLwM2mNodes.builder(); + tsNodes.put(456L, new LwM2mPath("/0/0/2"), LwM2mSingleResource.newIntegerResource(2, 222L)); + tsNodes.put(123L, new LwM2mPath("/0/0/1"), LwM2mSingleResource.newIntegerResource(1, 111L)); + return tsNodes.build(); + } + + private TimestampedLwM2mNodes getSamePathTimestampedLwM2mNodes() { + TimestampedLwM2mNodes.Builder tsNodes = TimestampedLwM2mNodes.builder(); + tsNodes.put(456L, new LwM2mPath("/0/0/1"), LwM2mSingleResource.newIntegerResource(1, 222L)); + tsNodes.put(123L, new LwM2mPath("/0/0/1"), LwM2mSingleResource.newIntegerResource(1, 111L)); + return tsNodes.build(); + } + + private TimestampedLwM2mNodes getExampleMixedTimestampLwM2mNodes() { + TimestampedLwM2mNodes.Builder tsNodes = TimestampedLwM2mNodes.builder(); + tsNodes.put(new LwM2mPath("/0/0/2"), LwM2mSingleResource.newIntegerResource(2, 222L)); + tsNodes.put(123L, new LwM2mPath("/0/0/1"), LwM2mSingleResource.newIntegerResource(1, 111L)); + return tsNodes.build(); + } +} \ No newline at end of file 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 4e47e396cb..e3fb60a8b9 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 @@ -40,9 +40,11 @@ import org.eclipse.leshan.core.node.LwM2mObjectInstance; 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; import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.TimestampedLwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.tlv.Tlv; import org.eclipse.leshan.core.tlv.Tlv.TlvType; @@ -1259,4 +1261,40 @@ public void senml_cbor_empty_multi_resource() { assertEquals(6, resource.getId()); assertTrue(resource.getInstances().isEmpty()); } + + @Test + public void senml_multiple_timestamped_nodes() throws CodecException { + // given + StringBuilder b = new StringBuilder(); + b.append("[{\"bn\":\"/4/0/\",\"bt\":268600000,\"n\":\"0\",\"v\":1,\"t\":1},"); + b.append("{\"n\":\"1\",\"v\":2,\"t\":2},"); + b.append("{\"n\":\"1\",\"v\":2,\"t\":3},"); + b.append("{\"bn\":\"/3/0/7/\",\"n\":\"0\",\"v\":3800}"); + b.append("]"); + + // when + TimestampedLwM2mNodes data = decoder.decodeMultiTimestampedNodes(b.toString().getBytes(), + ContentFormat.SENML_JSON, model); + + // then + Map> expectedResult = data.getTimestampedNodes(); + Map first = new HashMap<>(); + first.put(new LwM2mPath("/3/0/7/0"), LwM2mResourceInstance.newIntegerInstance(0, 3000)); + expectedResult.put(268600000L, first); + + Map second = new HashMap<>(); + second.put(new LwM2mPath("/4/0/0"), LwM2mResourceInstance.newIntegerInstance(0, 1)); + expectedResult.put(268600001L, second); + + Map third = new HashMap<>(); + third.put(new LwM2mPath("/4/0/1"), LwM2mResourceInstance.newIntegerInstance(0, 2)); + expectedResult.put(268600002L, third); + + Map fourth = new HashMap<>(); + fourth.put(new LwM2mPath("/4/0/2"), LwM2mResourceInstance.newIntegerInstance(0, 3)); + expectedResult.put(268600003L, fourth); + + assertEquals(expectedResult, data.getTimestampedNodes()); + } + } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java index a355b06c89..bf03513d27 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java @@ -91,7 +91,7 @@ public void register_update_deregister() throws LinkParseException { // Check client is well registered helper.assertClientRegisterered(); assertArrayEquals(linkParser.parseCoreLinkFormat( - ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,," + ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,,," .getBytes()), helper.getCurrentRegistration().getObjectLinks()); @@ -117,7 +117,7 @@ public void deregister_cancel_multiple_pending_request() throws InterruptedExcep // Check client is well registered helper.assertClientRegisterered(); assertArrayEquals(linkParser.parseCoreLinkFormat( - ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,," + ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,,," .getBytes()), helper.getCurrentRegistration().getObjectLinks()); @@ -268,7 +268,7 @@ public void register_with_additional_attributes() throws InterruptedException, L assertNotNull(helper.getLastRegistration()); assertEquals(additionalAttributes, helper.getLastRegistration().getAdditionalRegistrationAttributes()); assertArrayEquals(linkParser.parseCoreLinkFormat( - ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,," + ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,,," .getBytes()), helper.getCurrentRegistration().getObjectLinks()); } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTest.java index 2dbcd34e61..32981c9baf 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTest.java @@ -27,6 +27,7 @@ import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.SendResponse; @@ -95,12 +96,12 @@ public void can_send_resources() throws InterruptedException, TimeoutException { // wait for data and check result listener.waitForData(1, TimeUnit.SECONDS); assertNotNull(listener.getRegistration()); - Map data = listener.getData(); - LwM2mResource modelnumber = (LwM2mResource) data.get("/3/0/1"); + Map data = listener.getNodes(); + LwM2mResource modelnumber = (LwM2mResource) data.get(new LwM2mPath("/3/0/1")); assertEquals(modelnumber.getId(), 1); assertEquals(modelnumber.getValue(), "IT-TEST-123"); - LwM2mResource serialnumber = (LwM2mResource) data.get("/3/0/2"); + LwM2mResource serialnumber = (LwM2mResource) data.get(new LwM2mPath("/3/0/2")); assertEquals(serialnumber.getId(), 2); assertEquals(serialnumber.getValue(), "12345"); } @@ -122,12 +123,12 @@ public void can_send_resources_asynchronously() throws InterruptedException, Tim // wait for data and check result listener.waitForData(1, TimeUnit.SECONDS); assertNotNull(listener.getRegistration()); - Map data = listener.getData(); - LwM2mResource modelnumber = (LwM2mResource) data.get("/3/0/1"); + Map data = listener.getNodes(); + LwM2mResource modelnumber = (LwM2mResource) data.get(new LwM2mPath("/3/0/1")); assertEquals(modelnumber.getId(), 1); assertEquals(modelnumber.getValue(), "IT-TEST-123"); - LwM2mResource serialnumber = (LwM2mResource) data.get("/3/0/2"); + LwM2mResource serialnumber = (LwM2mResource) data.get(new LwM2mPath("/3/0/2")); assertEquals(serialnumber.getId(), 2); assertEquals(serialnumber.getValue(), "12345"); } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java new file mode 100644 index 0000000000..994cbb9378 --- /dev/null +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2021 Orange. + * + * 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: + * Orange - Send with multiple-timestamped values + *******************************************************************************/ +package org.eclipse.leshan.integration.tests.send; + +import static org.junit.Assert.*; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.object.Security; +import org.eclipse.leshan.client.object.Server; +import org.eclipse.leshan.client.resource.DummyInstanceEnabler; +import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; +import org.eclipse.leshan.client.resource.ObjectsInitializer; +import org.eclipse.leshan.client.resource.SimpleInstanceEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.LwM2mId; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.model.StaticModel; +import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.integration.tests.util.IntegrationTestHelper; +import org.eclipse.leshan.integration.tests.util.SynchronousSendListener; +import org.eclipse.leshan.integration.tests.util.TimestampedInstanceEnabler; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class SendTimestampedTest { + + protected final IntegrationTestHelper helper = new TestHelperWithFakeDecoder(); + + @Before + public void start() { + helper.initialize(); + helper.createServer(); + helper.server.start(); + helper.createClient(); + helper.client.start(); + helper.waitForRegistrationAtServerSide(1); + } + + @After + public void stop() { + helper.client.destroy(false); + helper.server.destroy(); + helper.dispose(); + } + + @Test + public void server_handle_multiple_timestamped_node() throws InterruptedException, TimeoutException { + // Define send listener + SynchronousSendListener listener = new SynchronousSendListener(); + helper.server.getSendService().addListener(listener); + + // Send Data + helper.waitForRegistrationAtClientSide(1); + ServerIdentity server = helper.client.getRegisteredServers().values().iterator().next(); + helper.client.sendData(server, ContentFormat.SENML_JSON, Arrays.asList(getExamplePath().toString()), 1000); + listener.waitForData(1, TimeUnit.SECONDS); + + // Verify SendListener data received + assertNotNull(listener.getRegistration()); + TimestampedLwM2mNodes data = listener.getData(); + + Map exampleNodes = getExampleTimestampedNodes(); + assertEquals(exampleNodes.keySet(), data.getTimestamps()); + + for (Long ts : exampleNodes.keySet()) { + Map pathNodeMap = data.getNodesForTimestamp(ts); + assertTrue(pathNodeMap.containsKey(getExamplePath())); + + LwM2mNode node = pathNodeMap.get(getExamplePath()); + LwM2mNode expectedNode = exampleNodes.get(ts); + + assertEquals(node, expectedNode); + } + } + + private static LwM2mPath getExamplePath() { + return new LwM2mPath("/2000/2/3"); + } + + private static Map getExampleTimestampedNodes() { + Map timestampedNodes = new HashMap<>(); + timestampedNodes.put(268435456L, LwM2mSingleResource.newFloatResource(3, 12345)); + timestampedNodes.put(268435457L, LwM2mSingleResource.newFloatResource(3, 67890)); + return timestampedNodes; + } + + private static class TestHelperWithFakeDecoder extends IntegrationTestHelper { + @Override + protected ObjectsInitializer createObjectsInitializer() { + return new ObjectsInitializer(new StaticModel(createObjectModels())); + } + + public void createClient(Map additionalAttributes) { + // Create objects Enabler + ObjectsInitializer initializer = createObjectsInitializer(); + initializer.setInstancesForObject(LwM2mId.SECURITY, + Security.noSec("coap://" + server.getUnsecuredAddress().getHostString() + ":" + + server.getUnsecuredAddress().getPort(), 12345)); + initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); + initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345")); + initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); + initializer.setInstancesForObject(TEST_OBJECT_ID, new DummyInstanceEnabler(0), + new SimpleInstanceEnabler(1, OPAQUE_RESOURCE_ID, new byte[0]), new TimestampedInstanceEnabler(2)); + List objects = initializer.createAll(); + + // Build Client + LeshanClientBuilder builder = new LeshanClientBuilder(currentEndpointIdentifier.get()); + builder.setDecoder(new DefaultLwM2mDecoder(true)); + builder.setEncoder(new FakeEncoder()); + builder.setAdditionalAttributes(additionalAttributes); + builder.setObjects(objects); + client = builder.build(); + setupClientMonitoring(); + } + } + + private static class FakeEncoder extends DefaultLwM2mEncoder { + public FakeEncoder() { + super(true); + } + + @Override + public byte[] encodeNodes(Map nodes, ContentFormat format, LwM2mModel model) { + return ("[{\"bn\":\"/2000/2/\",\"n\":\"3\",\"v\":12345,\"t\":268435456}," + + "{\"n\":\"3\",\"v\":67890,\"t\":268435457}]").getBytes(StandardCharsets.UTF_8); + } + } +} diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java index a663d68056..b7217dcd0d 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java @@ -200,7 +200,7 @@ public void createClient(Map additionalAttributes) { initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); initializer.setInstancesForObject(TEST_OBJECT_ID, new DummyInstanceEnabler(0), - new SimpleInstanceEnabler(1, OPAQUE_RESOURCE_ID, new byte[0])); + new SimpleInstanceEnabler(1, OPAQUE_RESOURCE_ID, new byte[0]), new TimestampedInstanceEnabler(2)); List objects = initializer.createAll(); // Build Client diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java index 18f8d86897..7e10e2b7b8 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java @@ -245,8 +245,8 @@ public void createPSKClient(boolean queueMode) { builder.setRegistrationEngineFactory(new DefaultRegistrationEngineFactory().setQueueMode(queueMode)); builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort()); builder.setObjects(objects); - builder.setDtlsConfig(DtlsConnectorConfig.builder(configuration) - .setAsList(DtlsConfig.DTLS_CIPHER_SUITES, CipherSuite.TLS_PSK_WITH_AES_128_CCM_8)); + builder.setDtlsConfig(DtlsConnectorConfig.builder(configuration).setAsList(DtlsConfig.DTLS_CIPHER_SUITES, + CipherSuite.TLS_PSK_WITH_AES_128_CCM_8)); // set an editable PSK store for tests builder.setEndpointFactory(new EndpointFactory() { diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SynchronousSendListener.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SynchronousSendListener.java index 52a9ad0cb2..436f09a10f 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SynchronousSendListener.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SynchronousSendListener.java @@ -21,13 +21,15 @@ import java.util.concurrent.TimeoutException; import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.request.SendRequest; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.send.SendListener; public class SynchronousSendListener implements SendListener { private CountDownLatch dataLatch = new CountDownLatch(1); - private volatile Map data; + private volatile TimestampedLwM2mNodes data; private CountDownLatch errorLatch = new CountDownLatch(1); private volatile Exception error; @@ -35,7 +37,7 @@ public class SynchronousSendListener implements SendListener { private volatile Registration registration; @Override - public void dataReceived(Registration registration, Map data, SendRequest request) { + public void dataReceived(Registration registration, TimestampedLwM2mNodes data, SendRequest request) { this.data = data; this.registration = registration; dataLatch.countDown(); @@ -48,10 +50,14 @@ public void onError(Registration registration, Exception error) { errorLatch.countDown(); } - public Map getData() { + public TimestampedLwM2mNodes getData() { return data; } + public Map getNodes() { + return data.getNodes(); + } + public Registration getRegistration() { return registration; } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TimestampedInstanceEnabler.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TimestampedInstanceEnabler.java new file mode 100644 index 0000000000..10607f1898 --- /dev/null +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TimestampedInstanceEnabler.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2021 Orange. + * + * 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: + * Orange - Send with multiple-timestamped values + *******************************************************************************/ +package org.eclipse.leshan.integration.tests.util; + +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.response.ReadResponse; + +public class TimestampedInstanceEnabler extends BaseInstanceEnabler { + + public TimestampedInstanceEnabler(int id) { + super(id); + } + + @Override + public ReadResponse read(ServerIdentity identity, int resourceid) { + LwM2mSingleResource lwM2mSingleResource = LwM2mSingleResource.newFloatResource(resourceid, 111.1); + return ReadResponse.success(lwM2mSingleResource); + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java index db5d5c6eb4..51922c11c8 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java @@ -17,16 +17,12 @@ import static org.eclipse.leshan.core.californium.ResponseCodeUtil.toCoapResponseCode; -import java.util.List; -import java.util.Map; - import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.leshan.core.californium.LwM2mCoapResource; import org.eclipse.leshan.core.model.LwM2mModel; -import org.eclipse.leshan.core.node.LwM2mNode; -import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.LwM2mDecoder; import org.eclipse.leshan.core.request.ContentFormat; @@ -83,9 +79,8 @@ public void handlePOST(CoapExchange exchange) { "Unsupported content format [%s] in [%s] from [%s]", contentFormat, coapRequest, sender)); return; } - Map data = null; - data = decoder.decodeNodes(payload, contentFormat, (List) null, model); + TimestampedLwM2mNodes data = decoder.decodeMultiTimestampedNodes(payload, contentFormat, model); // Handle "send op request SendRequest sendRequest = new SendRequest(contentFormat, data, coapRequest); diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/DummyDecoder.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/DummyDecoder.java index dd6304b035..629a45ff63 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/DummyDecoder.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/DummyDecoder.java @@ -25,6 +25,7 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.TimestampedLwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.LwM2mDecoder; import org.eclipse.leshan.core.request.ContentFormat; @@ -54,6 +55,12 @@ public List decodeTimestampedData(byte[] content, ContentF return Collections.singletonList(new TimestampedLwM2mNode(null, decode(null, null, null, null))); } + @Override + public TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) + throws CodecException { + return null; + } + @Override public List decodePaths(byte[] content, ContentFormat format) throws CodecException { return null; diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/send/SendHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/send/SendHandler.java index bb096e5b49..b40c9b917b 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/send/SendHandler.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/send/SendHandler.java @@ -15,15 +15,10 @@ *******************************************************************************/ package org.eclipse.leshan.server.send; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.CopyOnWriteArrayList; -import org.eclipse.leshan.core.node.LwM2mNode; -import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.request.SendRequest; import org.eclipse.leshan.core.response.SendResponse; import org.eclipse.leshan.core.response.SendableResponse; @@ -52,20 +47,15 @@ public SendableResponse handleSend(final Registration registration SendableResponse response = new SendableResponse<>(SendResponse.success(), new Runnable() { @Override public void run() { - fireDataReceived(registration, request.getNodes(), request); + fireDataReceived(registration, request.getTimestampedNodes(), request); } }); return response; } - protected void fireDataReceived(Registration registration, Map data, SendRequest request) { - HashMap nodes = new HashMap<>(); - for (Entry entry : data.entrySet()) { - nodes.put(entry.getKey().toString(), entry.getValue()); - } - + protected void fireDataReceived(Registration registration, TimestampedLwM2mNodes data, SendRequest request) { for (SendListener listener : listeners) { - listener.dataReceived(registration, Collections.unmodifiableMap(nodes), request); + listener.dataReceived(registration, data, request); } } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/send/SendListener.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/send/SendListener.java index a1db5922b1..0f3b673b14 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/send/SendListener.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/send/SendListener.java @@ -15,9 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.server.send; -import java.util.Map; - -import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.request.SendRequest; import org.eclipse.leshan.server.registration.Registration; @@ -35,7 +33,7 @@ public interface SendListener { * @param data The data received * @param request The request received */ - void dataReceived(Registration registration, Map data, SendRequest request); + void dataReceived(Registration registration, TimestampedLwM2mNodes data, SendRequest request); /** * Called when Send Request can't not be handled by server (because of e.g. unsupported content format or invalid diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java index dd2fd2c8c1..8c5e175671 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -34,6 +33,7 @@ import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.SingleObservation; @@ -238,7 +238,7 @@ public void newObservation(Observation observation, Registration registration) { private final SendListener sendListener = new SendListener() { @Override - public void dataReceived(Registration registration, Map data, SendRequest request) { + public void dataReceived(Registration registration, TimestampedLwM2mNodes data, SendRequest request) { if (LOG.isDebugEnabled()) { LOG.debug("Received Send request from [{}] containing value [{}]", registration, data.toString()); From f46fc9ff836b3dbab8a843f356de2403c72f96bc Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 15:38:58 +0100 Subject: [PATCH 02/20] Fix javadoc issue : no description for @param [WARNING] Javadoc Warnings [WARNING] leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java:41: warning: no description for @param [WARNING] * @param timestamp [WARNING] ^ --- .../org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java index 614eeef04d..b0bf561e10 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java @@ -43,7 +43,6 @@ public Map> getTimestampedNodes() { /** * Get nodes for specific timestamp. Null timestamp is allowed. * - * @param timestamp * @return map of {@link LwM2mPath}-{@link LwM2mNode} or null if there is no value for asked timestamp. */ public Map getNodesForTimestamp(Long timestamp) { @@ -51,8 +50,8 @@ public Map getNodesForTimestamp(Long timestamp) { } /** - * Get all collected nodes as {@link LwM2mPath}-{@link LwM2mNode} map ignoring timestamp information. In case - * of the same path conflict the most recent one is taken. Null timestamp is considered as most recent one. + * Get all collected nodes as {@link LwM2mPath}-{@link LwM2mNode} map ignoring timestamp information. In case of the + * same path conflict the most recent one is taken. Null timestamp is considered as most recent one. */ public Map getNodes() { Map result = new HashMap<>(); From 20f189b676386fdfa65bb6c0d21c70be4281398e Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 17:01:52 +0100 Subject: [PATCH 03/20] Fix Eclipse Warnings. --- .../leshan/integration/tests/send/SendTimestampedTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java index 994cbb9378..8ce2ad0843 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java @@ -118,6 +118,7 @@ protected ObjectsInitializer createObjectsInitializer() { return new ObjectsInitializer(new StaticModel(createObjectModels())); } + @Override public void createClient(Map additionalAttributes) { // Create objects Enabler ObjectsInitializer initializer = createObjectsInitializer(); From 19b183168062f710470a579723f59bcdebfb6645 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 17:07:20 +0100 Subject: [PATCH 04/20] Fix some formatting issue --- .../org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java | 1 + .../org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java index b0bf561e10..cb96a20ee4 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java @@ -34,6 +34,7 @@ private TimestampedLwM2mNodes(Map> timestampedPa /** * Get maps {@link LwM2mPath}-{@link LwM2mNode} grouped by timestamp with ascending order. + *

* Null timestamp keys are allowed, and are considered as most recent one. */ public Map> getTimestampedNodes() { diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java index 186929dc4b..6821409587 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java @@ -94,7 +94,7 @@ public void should_getNodes_returns_latest_node_if_path_conflict() { // then assertNotNull(tsNodesMap); assertTrue(tsNodesMap.containsKey(new LwM2mPath("/0/0/1"))); - assertEquals(222L, ((LwM2mSingleResource)tsNodesMap.get(new LwM2mPath("/0/0/1"))).getValue() ); + assertEquals(222L, ((LwM2mSingleResource) tsNodesMap.get(new LwM2mPath("/0/0/1"))).getValue()); } @Test From 1fe56a78ff033a391c94ee4d5cf57d04137571c5 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 17:38:28 +0100 Subject: [PATCH 05/20] SendRequest : remove duplicate code. --- .../leshan/core/request/SendRequest.java | 34 ++++--------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/SendRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/SendRequest.java index 8825ac1da6..54efc2e214 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/SendRequest.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/SendRequest.java @@ -36,10 +36,9 @@ * The "Send" operation can be used by the LwM2M Client to report values for Resources and Resource Instances of LwM2M * Object Instance(s) to the LwM2M Server. */ -public class SendRequest implements UplinkRequest { +public class SendRequest extends AbstractLwM2mRequest implements UplinkRequest { private final ContentFormat format; - private final Object coapRequest; private final TimestampedLwM2mNodes timestampedNodes; /** @@ -52,7 +51,12 @@ public SendRequest(ContentFormat format, Map nodes) { this(format, nodes, null); } + public SendRequest(ContentFormat format, Map nodes, Object coapRequest) { + this(format, TimestampedLwM2mNodes.builder().addNodes(nodes).build(), coapRequest); + } + public SendRequest(ContentFormat format, TimestampedLwM2mNodes timestampedNodes, Object coapRequest) { + super(coapRequest); this.timestampedNodes = timestampedNodes; // Validate Format if (format == null || !(format.equals(ContentFormat.SENML_CBOR) || format.equals(ContentFormat.SENML_JSON))) { @@ -62,20 +66,6 @@ public SendRequest(ContentFormat format, TimestampedLwM2mNodes timestampedNodes, validateNodes(timestampedNodes.getNodes()); this.format = format; - this.coapRequest = coapRequest; - } - - public SendRequest(ContentFormat format, Map nodes, Object coapRequest) { - timestampedNodes = TimestampedLwM2mNodes.builder().addNodes(nodes).build(); - // Validate Format - if (format == null || !(format.equals(ContentFormat.SENML_CBOR) || format.equals(ContentFormat.SENML_JSON))) { - throw new InvalidRequestException("Content format MUST be SenML_CBOR or SenML_JSON but was " + format); - } - // Validate Nodes - validateNodes(nodes); - - this.format = format; - this.coapRequest = coapRequest; } private void validateNodes(Map nodes) { @@ -104,11 +94,6 @@ public TimestampedLwM2mNodes getTimestampedNodes() { return timestampedNodes; } - @Override - public Object getCoapRequest() { - return coapRequest; - } - public ContentFormat getFormat() { return format; } @@ -127,7 +112,6 @@ public String toString() { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((coapRequest == null) ? 0 : coapRequest.hashCode()); result = prime * result + ((format == null) ? 0 : format.hashCode()); result = prime * result + ((timestampedNodes == null) ? 0 : timestampedNodes.hashCode()); return result; @@ -142,11 +126,6 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; SendRequest other = (SendRequest) obj; - if (coapRequest == null) { - if (other.coapRequest != null) - return false; - } else if (!coapRequest.equals(other.coapRequest)) - return false; if (format == null) { if (other.format != null) return false; @@ -159,5 +138,4 @@ public boolean equals(Object obj) { return false; return true; } - } From 2e95bb9c2575ab9d5fe263f1bdf4e49edd8661de Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 17:39:24 +0100 Subject: [PATCH 06/20] Removed unused TimestampedInstanceEnabler. --- .../integration/tests/RegistrationTest.java | 6 ++-- .../tests/send/SendTimestampedTest.java | 7 ++-- .../tests/util/IntegrationTestHelper.java | 2 +- .../util/TimestampedInstanceEnabler.java | 34 ------------------- 4 files changed, 7 insertions(+), 42 deletions(-) delete mode 100644 leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TimestampedInstanceEnabler.java diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java index bf03513d27..a355b06c89 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java @@ -91,7 +91,7 @@ public void register_update_deregister() throws LinkParseException { // Check client is well registered helper.assertClientRegisterered(); assertArrayEquals(linkParser.parseCoreLinkFormat( - ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,,," + ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,," .getBytes()), helper.getCurrentRegistration().getObjectLinks()); @@ -117,7 +117,7 @@ public void deregister_cancel_multiple_pending_request() throws InterruptedExcep // Check client is well registered helper.assertClientRegisterered(); assertArrayEquals(linkParser.parseCoreLinkFormat( - ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,,," + ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,," .getBytes()), helper.getCurrentRegistration().getObjectLinks()); @@ -268,7 +268,7 @@ public void register_with_additional_attributes() throws InterruptedException, L assertNotNull(helper.getLastRegistration()); assertEquals(additionalAttributes, helper.getLastRegistration().getAdditionalRegistrationAttributes()); assertArrayEquals(linkParser.parseCoreLinkFormat( - ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,,," + ";rt=\"oma.lwm2m\";ct=\"60 110 112 1542 1543 11542 11543\",;ver=1.1,,,;ver=1.1,,," .getBytes()), helper.getCurrentRegistration().getObjectLinks()); } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java index 8ce2ad0843..7a42af448e 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java @@ -45,7 +45,6 @@ import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.integration.tests.util.IntegrationTestHelper; import org.eclipse.leshan.integration.tests.util.SynchronousSendListener; -import org.eclipse.leshan.integration.tests.util.TimestampedInstanceEnabler; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -102,7 +101,7 @@ public void server_handle_multiple_timestamped_node() throws InterruptedExceptio } private static LwM2mPath getExamplePath() { - return new LwM2mPath("/2000/2/3"); + return new LwM2mPath("/2000/1/3"); } private static Map getExampleTimestampedNodes() { @@ -129,7 +128,7 @@ public void createClient(Map additionalAttributes) { initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); initializer.setInstancesForObject(TEST_OBJECT_ID, new DummyInstanceEnabler(0), - new SimpleInstanceEnabler(1, OPAQUE_RESOURCE_ID, new byte[0]), new TimestampedInstanceEnabler(2)); + new SimpleInstanceEnabler(1, FLOAT_RESOURCE_ID, 12345d)); List objects = initializer.createAll(); // Build Client @@ -150,7 +149,7 @@ public FakeEncoder() { @Override public byte[] encodeNodes(Map nodes, ContentFormat format, LwM2mModel model) { - return ("[{\"bn\":\"/2000/2/\",\"n\":\"3\",\"v\":12345,\"t\":268435456}," + return ("[{\"bn\":\"/2000/1/\",\"n\":\"3\",\"v\":12345,\"t\":268435456}," + "{\"n\":\"3\",\"v\":67890,\"t\":268435457}]").getBytes(StandardCharsets.UTF_8); } } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java index b7217dcd0d..a663d68056 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java @@ -200,7 +200,7 @@ public void createClient(Map additionalAttributes) { initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); initializer.setInstancesForObject(TEST_OBJECT_ID, new DummyInstanceEnabler(0), - new SimpleInstanceEnabler(1, OPAQUE_RESOURCE_ID, new byte[0]), new TimestampedInstanceEnabler(2)); + new SimpleInstanceEnabler(1, OPAQUE_RESOURCE_ID, new byte[0])); List objects = initializer.createAll(); // Build Client diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TimestampedInstanceEnabler.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TimestampedInstanceEnabler.java deleted file mode 100644 index 10607f1898..0000000000 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TimestampedInstanceEnabler.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021 Orange. - * - * 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: - * Orange - Send with multiple-timestamped values - *******************************************************************************/ -package org.eclipse.leshan.integration.tests.util; - -import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; -import org.eclipse.leshan.core.node.LwM2mSingleResource; -import org.eclipse.leshan.core.response.ReadResponse; - -public class TimestampedInstanceEnabler extends BaseInstanceEnabler { - - public TimestampedInstanceEnabler(int id) { - super(id); - } - - @Override - public ReadResponse read(ServerIdentity identity, int resourceid) { - LwM2mSingleResource lwM2mSingleResource = LwM2mSingleResource.newFloatResource(resourceid, 111.1); - return ReadResponse.success(lwM2mSingleResource); - } -} From 4b89dd55f5770d2f7cb38988c494a09ac2f5dff3 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 17:40:44 +0100 Subject: [PATCH 07/20] Revert formatting as it seems there is no other changes in this file --- .../integration/tests/util/SecureIntegrationTestHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java index 7e10e2b7b8..18f8d86897 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java @@ -245,8 +245,8 @@ public void createPSKClient(boolean queueMode) { builder.setRegistrationEngineFactory(new DefaultRegistrationEngineFactory().setQueueMode(queueMode)); builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort()); builder.setObjects(objects); - builder.setDtlsConfig(DtlsConnectorConfig.builder(configuration).setAsList(DtlsConfig.DTLS_CIPHER_SUITES, - CipherSuite.TLS_PSK_WITH_AES_128_CCM_8)); + builder.setDtlsConfig(DtlsConnectorConfig.builder(configuration) + .setAsList(DtlsConfig.DTLS_CIPHER_SUITES, CipherSuite.TLS_PSK_WITH_AES_128_CCM_8)); // set an editable PSK store for tests builder.setEndpointFactory(new EndpointFactory() { From aa6817e3d89946604f8b2eed444ccc7c08c5f99f Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 18:16:09 +0100 Subject: [PATCH 08/20] add some check to Default Decoder/Encoder --- .../core/node/codec/DefaultLwM2mDecoder.java | 39 ++++++++++++------- .../core/node/codec/DefaultLwM2mEncoder.java | 23 ++++++++--- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java index f1ca780d0b..7c19fa1af9 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java @@ -182,20 +182,6 @@ public Map decodeNodes(byte[] content, ContentFormat forma } } - @Override - public TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) - throws CodecException { - NodeDecoder decoder = nodeDecoders.get(format); - - if (decoder instanceof TimestampedMultiNodeDecoder) { - return ((TimestampedMultiNodeDecoder) decoder).decodeMultiTimestampedNodes(content, model); - } else { - throw new CodecException( - "Decoder does not support multiple timestamped nodes decoding for this content format %s [%s] ", - format); - } - } - @Override public List decodeTimestampedData(byte[] content, ContentFormat format, LwM2mPath path, LwM2mModel model) throws CodecException { @@ -219,6 +205,31 @@ public List decodeTimestampedData(byte[] content, ContentF } } + @Override + public TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) + throws CodecException { + LOG.trace("Decoding value for format {}: {}", format, content); + + if (format == null) { + throw new CodecException("Content format is mandatory."); + } + + NodeDecoder decoder = nodeDecoders.get(format); + if (decoder == null) { + throw new CodecException("Content format %s is not supported", format); + } + + if (decoder instanceof TimestampedMultiNodeDecoder) { + return ((TimestampedMultiNodeDecoder) decoder).decodeMultiTimestampedNodes(content, model); + } else if (decoder instanceof MultiNodeDecoder) { + return new TimestampedLwM2mNodes.Builder() + .addNodes(((MultiNodeDecoder) decoder).decodeNodes(content, null, model)).build(); + } else { + throw new CodecException( + "Decoder does not support multiple nodes decoding for this content format %s [%s] ", format); + } + } + @Override public List decodePaths(byte[] content, ContentFormat format) throws CodecException { LOG.trace("Decoding paths encoded with {}: {}", format, content); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java index 05ce2f66da..edd1d581ae 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java @@ -200,6 +200,23 @@ public byte[] encodeTimestampedData(List timestampedNodes, } + @Override + public byte[] encodeMultiTimestampedNodes(TimestampedLwM2mNodes timestampedNodes, ContentFormat format, + LwM2mModel model) throws CodecException { + Validate.notNull(timestampedNodes); + + if (format == null) { + throw new CodecException("Content format is mandatory."); + } + + NodeEncoder encoder = nodeEncoders.get(format); + if (encoder == null) { + throw new CodecException("Content format %s is not supported", format); + } + // TODO implements timestamped nodes encoding with fall back to "not timestamped" multi node. + return encodeNodes(timestampedNodes.getNodes(), format, model); + } + @Override public byte[] encodePaths(List paths, ContentFormat format) throws CodecException { Validate.notEmpty(paths); @@ -227,10 +244,4 @@ public boolean isSupported(ContentFormat format) { public Set getSupportedContentFormat() { return nodeEncoders.keySet(); } - - @Override - public byte[] encodeMultiTimestampedNodes(TimestampedLwM2mNodes data, ContentFormat format, LwM2mModel model) - throws CodecException { - return encodeNodes(data.getNodes(), format, model); - } } From b15ec7f037200c6fe90cbabeaacd66e35f39b366 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 18:40:49 +0100 Subject: [PATCH 09/20] Fix retro comptability in EventServlet to not break the demo. --- .../org/eclipse/leshan/server/demo/servlet/EventServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java index 8c5e175671..e0d2d5f4b3 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java @@ -246,7 +246,7 @@ public void dataReceived(Registration registration, TimestampedLwM2mNodes data, if (registration != null) { try { - String jsonContent = EventServlet.this.mapper.writeValueAsString(data); + String jsonContent = EventServlet.this.mapper.writeValueAsString(data.getNodes()); String eventData = new StringBuilder("{\"ep\":\"") // .append(registration.getEndpoint()) // From ae0073fec694c78724deea107371e294b8d0c8f1 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 18:20:21 +0100 Subject: [PATCH 10/20] Rename : remove "Multi" in (encode/decode)MultiTimestampedNodes. --- .../request/CoapRequestBuilder.java | 2 +- .../core/node/codec/DefaultLwM2mDecoder.java | 4 +-- .../core/node/codec/DefaultLwM2mEncoder.java | 2 +- .../leshan/core/node/codec/LwM2mDecoder.java | 24 ++++++++--------- .../leshan/core/node/codec/LwM2mEncoder.java | 26 +++++++++---------- .../codec/TimestampedMultiNodeDecoder.java | 2 +- .../codec/senml/LwM2mNodeSenMLDecoder.java | 2 +- .../core/node/codec/LwM2mNodeDecoderTest.java | 2 +- .../server/californium/send/SendResource.java | 2 +- .../server/californium/DummyDecoder.java | 2 +- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java index fb924871f9..2f58647030 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java @@ -162,7 +162,7 @@ public void visit(SendRequest request) { ContentFormat format = request.getFormat(); coapRequest.getOptions().setContentFormat(format.getCode()); - coapRequest.setPayload(encoder.encodeMultiTimestampedNodes(request.getTimestampedNodes(), format, model)); + coapRequest.setPayload(encoder.encodeTimestampedNodes(request.getTimestampedNodes(), format, model)); } public Request getRequest() { diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java index 7c19fa1af9..72863312c7 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mDecoder.java @@ -206,7 +206,7 @@ public List decodeTimestampedData(byte[] content, ContentF } @Override - public TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) + public TimestampedLwM2mNodes decodeTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) throws CodecException { LOG.trace("Decoding value for format {}: {}", format, content); @@ -220,7 +220,7 @@ public TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, Content } if (decoder instanceof TimestampedMultiNodeDecoder) { - return ((TimestampedMultiNodeDecoder) decoder).decodeMultiTimestampedNodes(content, model); + return ((TimestampedMultiNodeDecoder) decoder).decodeTimestampedNodes(content, model); } else if (decoder instanceof MultiNodeDecoder) { return new TimestampedLwM2mNodes.Builder() .addNodes(((MultiNodeDecoder) decoder).decodeNodes(content, null, model)).build(); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java index edd1d581ae..e5f3bfe8a0 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mEncoder.java @@ -201,7 +201,7 @@ public byte[] encodeTimestampedData(List timestampedNodes, } @Override - public byte[] encodeMultiTimestampedNodes(TimestampedLwM2mNodes timestampedNodes, ContentFormat format, + public byte[] encodeTimestampedNodes(TimestampedLwM2mNodes timestampedNodes, ContentFormat format, LwM2mModel model) throws CodecException { Validate.notNull(timestampedNodes); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mDecoder.java index 9106f4ec18..28ae8d719c 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mDecoder.java @@ -86,30 +86,30 @@ Map decodeNodes(byte[] content, ContentFormat format, List throws CodecException; /** - * Deserializes a binary content into a {@link TimestampedLwM2mNodes}. - *

+ * Deserializes a binary content into a list of time-stamped {@link LwM2mNode} ordering by time-stamp. * * @param content the content * @param format the content format + * @param path the path of the node to build * @param model the collection of supported object models - * @return the decoded timestamped nodes represented by {@link TimestampedLwM2mNodes} - * @throws CodecException if content is malformed. + * @return the resulting list of time-stamped {@link LwM2mNode} ordering by time-stamp + * @exception CodecException if content is malformed. */ - TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) - throws CodecException; + List decodeTimestampedData(byte[] content, ContentFormat format, LwM2mPath path, + LwM2mModel model) throws CodecException; /** - * Deserializes a binary content into a list of time-stamped {@link LwM2mNode} ordering by time-stamp. + * Deserializes a binary content into a {@link TimestampedLwM2mNodes}. + *

* * @param content the content * @param format the content format - * @param path the path of the node to build * @param model the collection of supported object models - * @return the resulting list of time-stamped {@link LwM2mNode} ordering by time-stamp - * @exception CodecException if content is malformed. + * @return the decoded timestamped nodes represented by {@link TimestampedLwM2mNodes} + * @throws CodecException if content is malformed. */ - List decodeTimestampedData(byte[] content, ContentFormat format, LwM2mPath path, - LwM2mModel model) throws CodecException; + TimestampedLwM2mNodes decodeTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) + throws CodecException; /** * Deserializes a binary content into a list of {@link LwM2mPath}. diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mEncoder.java index 1ef1d90511..20e97ce5e3 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mEncoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mEncoder.java @@ -74,6 +74,19 @@ public interface LwM2mEncoder { byte[] encodeTimestampedData(List timestampedNodes, ContentFormat format, LwM2mPath path, LwM2mModel model) throws CodecException; + /** + * Serializes a multiple time-stamped nodes contained in {@link TimestampedLwM2mNodes} with the given content + * format. + * + * @param data the {@link TimestampedLwM2mNodes} to serialize + * @param format the content format + * @param model the collection of supported object models + * @return the encoded node as a byte array + * @throws CodecException if encoding failed. + */ + byte[] encodeTimestampedNodes(TimestampedLwM2mNodes data, ContentFormat format, LwM2mModel model) + throws CodecException; + /** * Serializes a list of {@link LwM2mPath} with the given content format. * @@ -94,17 +107,4 @@ byte[] encodeTimestampedData(List timestampedNodes, Conten * @return {@link ContentFormat} supported by this Encoder */ Set getSupportedContentFormat(); - - /** - * Serializes a multiple time-stamped nodes contained in {@link TimestampedLwM2mNodes} with the given content - * format. - * - * @param data the {@link TimestampedLwM2mNodes} to serialize - * @param format the content format - * @param model the collection of supported object models - * @return the encoded node as a byte array - * @throws CodecException if encoding failed. - */ - byte[] encodeMultiTimestampedNodes(TimestampedLwM2mNodes data, ContentFormat format, LwM2mModel model) - throws CodecException; } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/TimestampedMultiNodeDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/TimestampedMultiNodeDecoder.java index 1e159e6e06..dba265c54b 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/TimestampedMultiNodeDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/TimestampedMultiNodeDecoder.java @@ -33,6 +33,6 @@ public interface TimestampedMultiNodeDecoder { * @return the decoded timestamped nodes represented by {@link TimestampedLwM2mNodes} * @throws CodecException if content is malformed. */ - TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, LwM2mModel model) throws CodecException; + TimestampedLwM2mNodes decodeTimestampedNodes(byte[] content, LwM2mModel model) throws CodecException; } 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 94969cbe50..9ac12d11cd 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 @@ -180,7 +180,7 @@ public List decodeTimestampedData(byte[] content, LwM2mPat } @Override - public TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, LwM2mModel model) throws CodecException { + public TimestampedLwM2mNodes decodeTimestampedNodes(byte[] content, LwM2mModel model) throws CodecException { try { // Decode SenML pack SenMLPack pack = decoder.fromSenML(content); 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 e3fb60a8b9..35fd0d8ec9 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 @@ -1273,7 +1273,7 @@ public void senml_multiple_timestamped_nodes() throws CodecException { b.append("]"); // when - TimestampedLwM2mNodes data = decoder.decodeMultiTimestampedNodes(b.toString().getBytes(), + TimestampedLwM2mNodes data = decoder.decodeTimestampedNodes(b.toString().getBytes(), ContentFormat.SENML_JSON, model); // then diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java index 51922c11c8..502555a46b 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java @@ -80,7 +80,7 @@ public void handlePOST(CoapExchange exchange) { return; } - TimestampedLwM2mNodes data = decoder.decodeMultiTimestampedNodes(payload, contentFormat, model); + TimestampedLwM2mNodes data = decoder.decodeTimestampedNodes(payload, contentFormat, model); // Handle "send op request SendRequest sendRequest = new SendRequest(contentFormat, data, coapRequest); diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/DummyDecoder.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/DummyDecoder.java index 629a45ff63..a428d34a5c 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/DummyDecoder.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/DummyDecoder.java @@ -56,7 +56,7 @@ public List decodeTimestampedData(byte[] content, ContentF } @Override - public TimestampedLwM2mNodes decodeMultiTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) + public TimestampedLwM2mNodes decodeTimestampedNodes(byte[] content, ContentFormat format, LwM2mModel model) throws CodecException { return null; } From 2249f456cdcd76669e2b04e6e48b9e615a823107 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 10 Mar 2022 18:21:29 +0100 Subject: [PATCH 11/20] Rename getNodesForTimestamp in getNodesAt --- .../org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java | 2 +- .../eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java | 6 +++--- .../leshan/integration/tests/send/SendTimestampedTest.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java index cb96a20ee4..5a16dbb047 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java @@ -46,7 +46,7 @@ public Map> getTimestampedNodes() { * * @return map of {@link LwM2mPath}-{@link LwM2mNode} or null if there is no value for asked timestamp. */ - public Map getNodesForTimestamp(Long timestamp) { + public Map getNodesAt(Long timestamp) { return timestampedPathNodesMap.get(timestamp); } diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java index 6821409587..2ea8a7125d 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java @@ -33,7 +33,7 @@ public void should_getPathNodesMapForTimestamp_pick_specific_timestamp_nodes() { TimestampedLwM2mNodes tsNodes = getExampleTimestampedLwM2mNodes(); // when - Map tsNodesMap = tsNodes.getNodesForTimestamp(123L); + Map tsNodesMap = tsNodes.getNodesAt(123L); // then assertNotNull(tsNodesMap); @@ -47,7 +47,7 @@ public void should_getPathNodesMapForTimestamp_pick_null_timestamp_nodes() { TimestampedLwM2mNodes tsNodes = getExampleMixedTimestampLwM2mNodes(); // when - Map tsNodesMap = tsNodes.getNodesForTimestamp(null); + Map tsNodesMap = tsNodes.getNodesAt(null); // then assertNotNull(tsNodesMap); @@ -61,7 +61,7 @@ public void should_getPathNodesMapForTimestamp_returns_null_for_nonexistent_time TimestampedLwM2mNodes tsNodes = getExampleTimestampedLwM2mNodes(); // when - Map tsNodesMap = tsNodes.getNodesForTimestamp(0L); + Map tsNodesMap = tsNodes.getNodesAt(0L); // then assertNull(tsNodesMap); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java index 7a42af448e..c8ddd3fd37 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/SendTimestampedTest.java @@ -90,7 +90,7 @@ public void server_handle_multiple_timestamped_node() throws InterruptedExceptio assertEquals(exampleNodes.keySet(), data.getTimestamps()); for (Long ts : exampleNodes.keySet()) { - Map pathNodeMap = data.getNodesForTimestamp(ts); + Map pathNodeMap = data.getNodesAt(ts); assertTrue(pathNodeMap.containsKey(getExamplePath())); LwM2mNode node = pathNodeMap.get(getExamplePath()); From 758fd57fc67b073e8e5901dddaa5c43185b6ae06 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Wed, 16 Mar 2022 17:40:54 +0100 Subject: [PATCH 12/20] Fix LwM2mNodeDecoderTest.senml_multiple_timestamped_nodes --- .../core/node/codec/LwM2mNodeDecoderTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 35fd0d8ec9..524f22c6ab 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 @@ -1268,30 +1268,30 @@ public void senml_multiple_timestamped_nodes() throws CodecException { StringBuilder b = new StringBuilder(); b.append("[{\"bn\":\"/4/0/\",\"bt\":268600000,\"n\":\"0\",\"v\":1,\"t\":1},"); b.append("{\"n\":\"1\",\"v\":2,\"t\":2},"); - b.append("{\"n\":\"1\",\"v\":2,\"t\":3},"); + b.append("{\"n\":\"2\",\"v\":3,\"t\":3},"); b.append("{\"bn\":\"/3/0/7/\",\"n\":\"0\",\"v\":3800}"); b.append("]"); // when - TimestampedLwM2mNodes data = decoder.decodeTimestampedNodes(b.toString().getBytes(), - ContentFormat.SENML_JSON, model); + TimestampedLwM2mNodes data = decoder.decodeTimestampedNodes(b.toString().getBytes(), ContentFormat.SENML_JSON, + model); // then - Map> expectedResult = data.getTimestampedNodes(); + Map> expectedResult = new HashMap<>(); Map first = new HashMap<>(); - first.put(new LwM2mPath("/3/0/7/0"), LwM2mResourceInstance.newIntegerInstance(0, 3000)); + first.put(new LwM2mPath("/3/0/7/0"), LwM2mResourceInstance.newIntegerInstance(0, 3800)); expectedResult.put(268600000L, first); Map second = new HashMap<>(); - second.put(new LwM2mPath("/4/0/0"), LwM2mResourceInstance.newIntegerInstance(0, 1)); + second.put(new LwM2mPath("/4/0/0"), LwM2mSingleResource.newIntegerResource(0, 1)); expectedResult.put(268600001L, second); Map third = new HashMap<>(); - third.put(new LwM2mPath("/4/0/1"), LwM2mResourceInstance.newIntegerInstance(0, 2)); + third.put(new LwM2mPath("/4/0/1"), LwM2mSingleResource.newIntegerResource(1, 2)); expectedResult.put(268600002L, third); Map fourth = new HashMap<>(); - fourth.put(new LwM2mPath("/4/0/2"), LwM2mResourceInstance.newIntegerInstance(0, 3)); + fourth.put(new LwM2mPath("/4/0/2"), LwM2mSingleResource.newIntegerResource(2, 3)); expectedResult.put(268600003L, fourth); assertEquals(expectedResult, data.getTimestampedNodes()); From 278ee9b15925c98dd8ca94e89e008a9b1f39c31a Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Wed, 16 Mar 2022 17:10:36 +0100 Subject: [PATCH 13/20] Make TimestampedLwM2mNodes really immutable. --- .../core/node/TimestampedLwM2mNodes.java | 31 +++++++------------ .../core/node/TimestampedLwM2mNodesTest.java | 12 +++---- .../core/node/codec/LwM2mNodeDecoderTest.java | 24 ++++---------- 3 files changed, 24 insertions(+), 43 deletions(-) diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java index 5a16dbb047..0f51d580a7 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.core.node; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Map; @@ -32,22 +33,17 @@ private TimestampedLwM2mNodes(Map> timestampedPa this.timestampedPathNodesMap = timestampedPathNodesMap; } - /** - * Get maps {@link LwM2mPath}-{@link LwM2mNode} grouped by timestamp with ascending order. - *

- * Null timestamp keys are allowed, and are considered as most recent one. - */ - public Map> getTimestampedNodes() { - return timestampedPathNodesMap; - } - /** * Get nodes for specific timestamp. Null timestamp is allowed. * * @return map of {@link LwM2mPath}-{@link LwM2mNode} or null if there is no value for asked timestamp. */ public Map getNodesAt(Long timestamp) { - return timestampedPathNodesMap.get(timestamp); + Map map = timestampedPathNodesMap.get(timestamp); + if (map != null) { + return Collections.unmodifiableMap(timestampedPathNodesMap.get(timestamp)); + } + return null; } /** @@ -59,7 +55,7 @@ public Map getNodes() { for (Map.Entry> entry : timestampedPathNodesMap.entrySet()) { result.putAll(entry.getValue()); } - return result; + return Collections.unmodifiableMap(result); } /** @@ -67,13 +63,12 @@ public Map getNodes() { * recent one. */ public Set getTimestamps() { - return timestampedPathNodesMap.keySet(); + return Collections.unmodifiableSet(timestampedPathNodesMap.keySet()); } @Override public String toString() { - return String.format("TimestampedLwM2mNodes [timestampedPathNodesMap=%s, timestampedNodes=%s]", - timestampedPathNodesMap); + return String.format("TimestampedLwM2mNodes [timestampedNodes=%s]", timestampedPathNodesMap); } @Override @@ -128,11 +123,9 @@ public Builder put(LwM2mPath path, LwM2mNode node) { } public Builder add(TimestampedLwM2mNodes timestampedNodes) { - for (Map.Entry> entry : timestampedNodes.getTimestampedNodes().entrySet()) { - Long timestamp = entry.getKey(); - Map pathNodeMap = entry.getValue(); - - for (Map.Entry pathNodeEntry : pathNodeMap.entrySet()) { + for (Long timestamp : timestampedNodes.getTimestamps()) { + for (Map.Entry pathNodeEntry : timestampedNodes.getNodesAt(timestamp) + .entrySet()) { LwM2mPath path = pathNodeEntry.getKey(); LwM2mNode node = pathNodeEntry.getValue(); put(timestamp, path, node); diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java index 2ea8a7125d..82934e4e65 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java @@ -103,11 +103,11 @@ public void should_getNodesForTimestamp_returns_ascending_ordered_nodes() { TimestampedLwM2mNodes tsNodes = getExampleTimestampedLwM2mNodes(); // when - Map> tsNodesMap = tsNodes.getTimestampedNodes(); + Set timestamps = tsNodes.getTimestamps(); // then - assertNotNull(tsNodesMap); - Iterator iterator = tsNodesMap.keySet().iterator(); + assertNotNull(timestamps); + Iterator iterator = timestamps.iterator(); assertEquals(123L, iterator.next().longValue()); assertEquals(456L, iterator.next().longValue()); } @@ -118,11 +118,11 @@ public void should_null_timestamp_be_considered_as_latest_for_getNodesForTimesta TimestampedLwM2mNodes tsNodes = getExampleMixedTimestampLwM2mNodes(); // when - Map> tsNodesMap = tsNodes.getTimestampedNodes(); + Set timestamps = tsNodes.getTimestamps(); // then - assertNotNull(tsNodesMap); - Iterator iterator = tsNodesMap.keySet().iterator(); + assertNotNull(timestamps); + Iterator iterator = timestamps.iterator(); assertEquals(123L, iterator.next().longValue()); assertNull(iterator.next()); } 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 524f22c6ab..03c8ef8d86 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 @@ -1277,24 +1277,12 @@ public void senml_multiple_timestamped_nodes() throws CodecException { model); // then - Map> expectedResult = new HashMap<>(); - Map first = new HashMap<>(); - first.put(new LwM2mPath("/3/0/7/0"), LwM2mResourceInstance.newIntegerInstance(0, 3800)); - expectedResult.put(268600000L, first); + TimestampedLwM2mNodes.Builder expectedResult = new TimestampedLwM2mNodes.Builder(); + expectedResult.put(268600000L, new LwM2mPath("/3/0/7/0"), LwM2mResourceInstance.newIntegerInstance(0, 3800)); + expectedResult.put(268600001L, new LwM2mPath("/4/0/0"), LwM2mSingleResource.newIntegerResource(0, 1)); + expectedResult.put(268600002L, new LwM2mPath("/4/0/1"), LwM2mSingleResource.newIntegerResource(1, 2)); + expectedResult.put(268600003L, new LwM2mPath("/4/0/2"), LwM2mSingleResource.newIntegerResource(2, 3)); - Map second = new HashMap<>(); - second.put(new LwM2mPath("/4/0/0"), LwM2mSingleResource.newIntegerResource(0, 1)); - expectedResult.put(268600001L, second); - - Map third = new HashMap<>(); - third.put(new LwM2mPath("/4/0/1"), LwM2mSingleResource.newIntegerResource(1, 2)); - expectedResult.put(268600002L, third); - - Map fourth = new HashMap<>(); - fourth.put(new LwM2mPath("/4/0/2"), LwM2mSingleResource.newIntegerResource(2, 3)); - expectedResult.put(268600003L, fourth); - - assertEquals(expectedResult, data.getTimestampedNodes()); + assertEquals(expectedResult.build(), data); } - } From d346379d7f7803c12d70009052e10c05e96939f6 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Wed, 16 Mar 2022 18:40:22 +0100 Subject: [PATCH 14/20] Add some validation. --- .../core/node/TimestampedLwM2mNodes.java | 72 ++++++++++++++----- .../core/node/TimestampedLwM2mNodesTest.java | 31 ++++++++ 2 files changed, 85 insertions(+), 18 deletions(-) diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java index 0f51d580a7..afb1afa039 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java @@ -15,10 +15,13 @@ *******************************************************************************/ package org.eclipse.leshan.core.node; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; @@ -102,44 +105,77 @@ public static Builder builder() { public static class Builder { - private final Map> timestampedPathNodesMap = getTimestampedMap(); + private static class InternalNode { + Long timestamp; + LwM2mPath path; + LwM2mNode node; + + public InternalNode(Long timestamp, LwM2mPath path, LwM2mNode node) { + this.timestamp = timestamp; + this.path = path; + this.node = node; + } + } + + private final List nodes = new ArrayList<>(); + private boolean noDuplicate = true; + + public Builder raiseExceptionOnDuplicate(boolean raiseException) { + noDuplicate = raiseException; + return this; + } public Builder addNodes(Map pathNodesMap) { - timestampedPathNodesMap.put(null, pathNodesMap); + for (Entry node : pathNodesMap.entrySet()) { + nodes.add(new InternalNode(null, node.getKey(), node.getValue())); + } return this; } public Builder put(Long timestamp, LwM2mPath path, LwM2mNode node) { - if (!timestampedPathNodesMap.containsKey(timestamp)) { - timestampedPathNodesMap.put(timestamp, new HashMap<>()); - } - timestampedPathNodesMap.get(timestamp).put(path, node); + nodes.add(new InternalNode(timestamp, path, node)); return this; } public Builder put(LwM2mPath path, LwM2mNode node) { - put(null, path, node); + nodes.add(new InternalNode(null, path, node)); return this; } public Builder add(TimestampedLwM2mNodes timestampedNodes) { for (Long timestamp : timestampedNodes.getTimestamps()) { - for (Map.Entry pathNodeEntry : timestampedNodes.getNodesAt(timestamp) - .entrySet()) { - LwM2mPath path = pathNodeEntry.getKey(); - LwM2mNode node = pathNodeEntry.getValue(); - put(timestamp, path, node); + Map pathNodeMap = timestampedNodes.getNodesAt(timestamp); + for (Map.Entry pathNodeEntry : pathNodeMap.entrySet()) { + nodes.add(new InternalNode(timestamp, pathNodeEntry.getKey(), pathNodeEntry.getValue())); } } return this; } - public TimestampedLwM2mNodes build() { - return new TimestampedLwM2mNodes(timestampedPathNodesMap); - } - - private static TreeMap> getTimestampedMap() { - return new TreeMap<>(getTimestampComparator()); + /** + * Build the {@link TimestampedLwM2mNodes} and raise {@link IllegalArgumentException} if builder inputs are + * invalid. + */ + public TimestampedLwM2mNodes build() throws IllegalArgumentException { + Map> timestampToPathToNode = new TreeMap<>(getTimestampComparator()); + + for (InternalNode internalNode : nodes) { + // TODO validate path is consistent with Node ? + Map pathToNode = timestampToPathToNode.get(internalNode.timestamp); + if (pathToNode == null) { + pathToNode = new HashMap<>(); + timestampToPathToNode.put(internalNode.timestamp, pathToNode); + pathToNode.put(internalNode.path, internalNode.node); + } else { + LwM2mNode previous = pathToNode.put(internalNode.path, internalNode.node); + if (noDuplicate && previous != null) { + throw new IllegalArgumentException(String.format( + "Unable to create TimestampedLwM2mNodes : duplicate value for path %s. (%s, %s)", + internalNode.path, internalNode.node, previous)); + } + } + } + return new TimestampedLwM2mNodes(timestampToPathToNode); } private static Comparator getTimestampComparator() { diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java index 82934e4e65..03bd2bd5c7 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java @@ -166,6 +166,37 @@ public void should_null_timestamp_be_considered_as_latest_for_getTimestamps() { assertEquals(new HashSet<>(Arrays.asList(123L, null)), timestamps); } + @Test + public void should_raise_exception_for_duplicates() { + // given + TimestampedLwM2mNodes.Builder builder = TimestampedLwM2mNodes.builder(); + builder.put(456L, new LwM2mPath("/0/0/2"), LwM2mSingleResource.newIntegerResource(2, 222L)); + builder.put(123L, new LwM2mPath("/0/0/1"), LwM2mSingleResource.newIntegerResource(1, 111L)); + builder.put(123L, new LwM2mPath("/0/0/1"), LwM2mSingleResource.newIntegerResource(1, 112L)); + + // when + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void should_not_raise_exception_for_duplicates() { + // given + TimestampedLwM2mNodes.Builder builder = TimestampedLwM2mNodes.builder().raiseExceptionOnDuplicate(false); + builder.put(456L, new LwM2mPath("/0/0/2"), LwM2mSingleResource.newIntegerResource(2, 222L)); + builder.put(123L, new LwM2mPath("/0/0/1"), LwM2mSingleResource.newIntegerResource(1, 111L)); + builder.put(123L, new LwM2mPath("/0/0/1"), LwM2mSingleResource.newIntegerResource(1, 112L)); + + // when + TimestampedLwM2mNodes nodes = builder.build(); + + // then + assertNotNull(nodes); + TimestampedLwM2mNodes.Builder expected = TimestampedLwM2mNodes.builder(); + expected.put(456L, new LwM2mPath("/0/0/2"), LwM2mSingleResource.newIntegerResource(2, 222L)); + expected.put(123L, new LwM2mPath("/0/0/1"), LwM2mSingleResource.newIntegerResource(1, 112L)); + assertEquals(expected.build(), nodes); + } + private TimestampedLwM2mNodes getExampleTimestampedLwM2mNodes() { TimestampedLwM2mNodes.Builder tsNodes = TimestampedLwM2mNodes.builder(); tsNodes.put(456L, new LwM2mPath("/0/0/2"), LwM2mSingleResource.newIntegerResource(2, 222L)); From a0f18ad6fa817c74485bab081b8c4ceee5cf383b Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 17 Mar 2022 11:17:17 +0100 Subject: [PATCH 15/20] Use fluent notation in senml_multiple_timestamped_nodes test --- .../leshan/core/node/codec/LwM2mNodeDecoderTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 03c8ef8d86..be7a085c63 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 @@ -1277,11 +1277,11 @@ public void senml_multiple_timestamped_nodes() throws CodecException { model); // then - TimestampedLwM2mNodes.Builder expectedResult = new TimestampedLwM2mNodes.Builder(); - expectedResult.put(268600000L, new LwM2mPath("/3/0/7/0"), LwM2mResourceInstance.newIntegerInstance(0, 3800)); - expectedResult.put(268600001L, new LwM2mPath("/4/0/0"), LwM2mSingleResource.newIntegerResource(0, 1)); - expectedResult.put(268600002L, new LwM2mPath("/4/0/1"), LwM2mSingleResource.newIntegerResource(1, 2)); - expectedResult.put(268600003L, new LwM2mPath("/4/0/2"), LwM2mSingleResource.newIntegerResource(2, 3)); + TimestampedLwM2mNodes.Builder expectedResult = new TimestampedLwM2mNodes.Builder() + .put(268600000L, new LwM2mPath("/3/0/7/0"), LwM2mResourceInstance.newIntegerInstance(0, 3800)) + .put(268600001L, new LwM2mPath("/4/0/0"), LwM2mSingleResource.newIntegerResource(0, 1)) + .put(268600002L, new LwM2mPath("/4/0/1"), LwM2mSingleResource.newIntegerResource(1, 2)) + .put(268600003L, new LwM2mPath("/4/0/2"), LwM2mSingleResource.newIntegerResource(2, 3)); assertEquals(expectedResult.build(), data); } From 65be1106943a94da7bd4b2c78db9348cb610ab58 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 17 Mar 2022 11:18:00 +0100 Subject: [PATCH 16/20] Fix test name and remove duplicate tests in TimestampedLwM2mNodesTest --- .../core/node/TimestampedLwM2mNodesTest.java | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java index 03bd2bd5c7..a89eef4e9a 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java @@ -98,7 +98,7 @@ public void should_getNodes_returns_latest_node_if_path_conflict() { } @Test - public void should_getNodesForTimestamp_returns_ascending_ordered_nodes() { + public void should_getTimestamps_returns_ascending_ordered_nodes() { // given TimestampedLwM2mNodes tsNodes = getExampleTimestampedLwM2mNodes(); @@ -113,7 +113,7 @@ public void should_getNodesForTimestamp_returns_ascending_ordered_nodes() { } @Test - public void should_null_timestamp_be_considered_as_latest_for_getNodesForTimestamp() { + public void should_null_timestamp_be_considered_as_latest_for_getTimestamps() { // given TimestampedLwM2mNodes tsNodes = getExampleMixedTimestampLwM2mNodes(); @@ -153,19 +153,6 @@ public void should_getTimestamps_returns_all_timestamps() { assertEquals(new HashSet<>(Arrays.asList(123L, 456L)), timestamps); } - @Test - public void should_null_timestamp_be_considered_as_latest_for_getTimestamps() { - // given - TimestampedLwM2mNodes tsNodes = getExampleMixedTimestampLwM2mNodes(); - - // when - Set timestamps = tsNodes.getTimestamps(); - - // then - assertNotNull(timestamps); - assertEquals(new HashSet<>(Arrays.asList(123L, null)), timestamps); - } - @Test public void should_raise_exception_for_duplicates() { // given From 343a97fc75112b9de14b90e604afa88ffc8bf526 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 17 Mar 2022 11:18:49 +0100 Subject: [PATCH 17/20] Fix TimestampedLwM2mNodes.toString() --- .../org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java index afb1afa039..a4678491b3 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java @@ -71,7 +71,7 @@ public Set getTimestamps() { @Override public String toString() { - return String.format("TimestampedLwM2mNodes [timestampedNodes=%s]", timestampedPathNodesMap); + return String.format("TimestampedLwM2mNodes [%s]", timestampedPathNodesMap); } @Override From 5d01cecfb1aef2994d68915e6f60f34af1f61dfc Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 17 Mar 2022 11:21:55 +0100 Subject: [PATCH 18/20] Fix LwM2mNodeDecoderTest.senml_multiple_timestamped_nodes test --- .../eclipse/leshan/core/node/codec/LwM2mNodeDecoderTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 be7a085c63..02ecc3e853 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 @@ -1268,7 +1268,7 @@ public void senml_multiple_timestamped_nodes() throws CodecException { StringBuilder b = new StringBuilder(); b.append("[{\"bn\":\"/4/0/\",\"bt\":268600000,\"n\":\"0\",\"v\":1,\"t\":1},"); b.append("{\"n\":\"1\",\"v\":2,\"t\":2},"); - b.append("{\"n\":\"2\",\"v\":3,\"t\":3},"); + b.append("{\"n\":\"1\",\"v\":3,\"t\":3},"); b.append("{\"bn\":\"/3/0/7/\",\"n\":\"0\",\"v\":3800}"); b.append("]"); @@ -1281,7 +1281,7 @@ public void senml_multiple_timestamped_nodes() throws CodecException { .put(268600000L, new LwM2mPath("/3/0/7/0"), LwM2mResourceInstance.newIntegerInstance(0, 3800)) .put(268600001L, new LwM2mPath("/4/0/0"), LwM2mSingleResource.newIntegerResource(0, 1)) .put(268600002L, new LwM2mPath("/4/0/1"), LwM2mSingleResource.newIntegerResource(1, 2)) - .put(268600003L, new LwM2mPath("/4/0/2"), LwM2mSingleResource.newIntegerResource(2, 3)); + .put(268600003L, new LwM2mPath("/4/0/1"), LwM2mSingleResource.newIntegerResource(1, 3)); assertEquals(expectedResult.build(), data); } From 74b3eb1db992913d95547a9fa276ec1cd7a78bea Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 17 Mar 2022 12:05:33 +0100 Subject: [PATCH 19/20] Add some path/node consistency validation in TimestampedLwM2mNodes --- .../leshan/core/node/LwM2mNodeUtil.java | 44 +++++++++++++++++++ .../core/node/TimestampedLwM2mNodes.java | 8 +++- .../core/node/TimestampedLwM2mNodesTest.java | 15 +++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mNodeUtil.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mNodeUtil.java index 85748d167f..18b2fd34b4 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mNodeUtil.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mNodeUtil.java @@ -266,4 +266,48 @@ public static void validateIncompletePath(LwM2mPath path) throws InvalidLwM2mPat if (err != null) throw new InvalidLwM2mPathException(err); } + + public static String getInvalidPathForNodeCause(LwM2mNode node, LwM2mPath path) { + if (node instanceof LwM2mObject) { + if (!path.isObject()) { + return String.format("Invalid Path %s : path does not target a LWM2M object for %s", path, node); + } else if (node.getId() != path.getObjectId()) { + return String.format("Invalid Path %s : path object id (%d) does not match LWM2M object id %d for %s", + path, path.getObjectId(), node.getId(), node); + } + } else if (node instanceof LwM2mObjectInstance) { + if (!path.isObjectInstance()) { + return String.format("Invalid Path %s : path does not target a LWM2M object instance for %s", path, + node); + } else if (node.getId() != path.getObjectInstanceId()) { + return String.format( + "Invalid Path %s : path object instance id (%d) does not match LWM2M object instance id %d for %s", + path, path.getObjectInstanceId(), node.getId(), node); + } + } else if (node instanceof LwM2mResource) { + if (!path.isResource()) { + return String.format("Invalid Path %s : path does not target a LWM2M resource for %s", path, node); + } else if (node.getId() != path.getResourceId()) { + return String.format( + "Invalid Path %s : path resource id (%d) does not match LWM2M resource id %d for %s", path, + path.getResourceId(), node.getId(), node); + } + } else if (node instanceof LwM2mResourceInstance) { + if (!path.isResourceInstance()) { + return String.format("Invalid Path %s : path does not target a LWM2M resource instance for %s", path, + node); + } else if (node.getId() != path.getResourceInstanceId()) { + return String.format( + "Invalid Path %s : path resource instance id (%d) does not match LWM2M resource instance id %d for %s", + path, path.getResourceInstanceId(), node.getId(), node); + } + } + return null; + } + + public static void validatePathForNode(LwM2mNode node, LwM2mPath path) throws InvalidLwM2mPathException { + String err = getInvalidPathForNodeCause(node, path); + if (err != null) + throw new InvalidLwM2mPathException(err); + } } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java index a4678491b3..acfbf57083 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodes.java @@ -160,7 +160,13 @@ public TimestampedLwM2mNodes build() throws IllegalArgumentException { Map> timestampToPathToNode = new TreeMap<>(getTimestampComparator()); for (InternalNode internalNode : nodes) { - // TODO validate path is consistent with Node ? + // validate path is consistent with Node + String cause = LwM2mNodeUtil.getInvalidPathForNodeCause(internalNode.node, internalNode.path); + if (cause != null) { + throw new IllegalArgumentException(cause); + } + + // add to the map Map pathToNode = timestampToPathToNode.get(internalNode.timestamp); if (pathToNode == null) { pathToNode = new HashMap<>(); diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java index a89eef4e9a..6e21573acb 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java @@ -165,6 +165,21 @@ public void should_raise_exception_for_duplicates() { assertThrows(IllegalArgumentException.class, () -> builder.build()); } + @Test + public void should_raise_exception_id_path_does_not_match_node() { + TimestampedLwM2mNodes.Builder builder = TimestampedLwM2mNodes.builder().put(456L, new LwM2mPath("/0/0/2"), + LwM2mResourceInstance.newIntegerInstance(0, 222L)); + assertThrows(IllegalArgumentException.class, () -> builder.build()); + + } + + @Test + public void should_raise_exception_path_does_not_match_id() { + TimestampedLwM2mNodes.Builder builder = TimestampedLwM2mNodes.builder().put(456L, new LwM2mPath("/0/0/2"), + LwM2mSingleResource.newIntegerResource(1, 222L)); + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + @Test public void should_not_raise_exception_for_duplicates() { // given From 9f69122916adf9b701cd927203e5e5603ff50c32 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Fri, 18 Mar 2022 14:27:36 +0100 Subject: [PATCH 20/20] Remove unnecessary empty line TimestampedLwM2mNodesTest --- .../org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java index 6e21573acb..eb40ebec45 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/TimestampedLwM2mNodesTest.java @@ -170,7 +170,6 @@ public void should_raise_exception_id_path_does_not_match_node() { TimestampedLwM2mNodes.Builder builder = TimestampedLwM2mNodes.builder().put(456L, new LwM2mPath("/0/0/2"), LwM2mResourceInstance.newIntegerInstance(0, 222L)); assertThrows(IllegalArgumentException.class, () -> builder.build()); - } @Test