Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix some behavior about Timestamped Node in SenML and Old JSON content Format #1621

Merged
merged 3 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ public <T extends LwM2mNode> T decode(byte[] content, LwM2mPath path, LwM2mModel
if (timestampedNodes.size() == 0) {
return null;
} else {
TimestampedLwM2mNode timestampedNode = timestampedNodes.get(0);
validateNoTimestampedValue(timestampedNode, path);

// return the most recent value
return (T) timestampedNodes.get(0).getNode();
}
Expand All @@ -112,6 +115,12 @@ public List<TimestampedLwM2mNode> decodeTimestampedData(byte[] content, LwM2mPat
}
}

protected void validateNoTimestampedValue(TimestampedLwM2mNode timestampedNode, LwM2mPath path) {
if (timestampedNode.getTimestamp() != null) {
throw new CodecException("Unable to decode node[path:%s] : value should not be timestamped", path);
}
}

private List<TimestampedLwM2mNode> parseJSON(JsonRootObject jsonObject, LwM2mPath requestPath, LwM2mModel model,
Class<? extends LwM2mNode> nodeClass) throws CodecException {

Expand Down Expand Up @@ -233,7 +242,9 @@ private BigDecimal computeTimestamp(BigDecimal baseTime, BigDecimal time) {
/**
* Group all JsonArrayEntry by time-stamp
*
* @return a map (relativeTimestamp => collection of JsonArrayEntry)
* @return a sorted map (relativeTimestamp => collection of JsonArrayEntry) order by descending time-stamp (most
* recent one at first place). If null time-stamp (meaning no time information) exists it always at first
* place.
*/
private SortedMap<BigDecimal, Collection<JsonArrayEntry>> groupJsonEntryByTimestamp(JsonRootObject jsonObject) {
SortedMap<BigDecimal, Collection<JsonArrayEntry>> result = new TreeMap<>(new Comparator<BigDecimal>() {
Expand All @@ -243,9 +254,9 @@ public int compare(BigDecimal o1, BigDecimal o2) {
// - supports null (time null means 0 if there is a base time)
// - reverses natural order (most recent value in first)
if (o1 == null) {
return o2 == null ? 0 : 1;
return o2 == null ? 0 : -1;
} else if (o2 == null) {
return -1;
return 1;
} else {
return o2.compareTo(o1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ public <T extends LwM2mNode> T decode(byte[] content, LwM2mPath path, LwM2mModel
throw new CodecException("Invalid path [%s] for resource, it should start by %s",
resolvedRecord.getPath(), path);
}
if (resolvedRecord.getTimeStamp() != null) {
throw new CodecException("Unable to decode node[path:%s] : value should not be timestamped", path);
}
validateNoTimestampedRecord(resolvedRecord);
resolvedRecords.add(resolvedRecord);
}

Expand Down Expand Up @@ -141,6 +139,7 @@ public Map<LwM2mPath, LwM2mNode> decodeNodes(byte[] content, List<LwM2mPath> pat
// Meaning that a given path could have no corresponding value.
nodes.put(path, null);
} else {
validateNoTimestampedRecord(records);
LwM2mNode node = parseRecords(recordsByPath.get(path), path, model,
DefaultLwM2mDecoder.nodeClassFromPath(path));
nodes.put(path, node);
Expand All @@ -153,6 +152,7 @@ public Map<LwM2mPath, LwM2mNode> decodeNodes(byte[] content, List<LwM2mPath> pat
for (SenMLRecord record : pack.getRecords()) {
LwM2mResolvedSenMLRecord resolvedRecord = resolver.resolve(record);
LwM2mPath path = resolvedRecord.getPath();
validateNoTimestampedRecord(resolvedRecord);
LwM2mNode node = parseRecords(Arrays.asList(resolvedRecord), path, model,
DefaultLwM2mDecoder.nodeClassFromPath(path));
nodes.put(path, node);
Expand All @@ -165,6 +165,19 @@ public Map<LwM2mPath, LwM2mNode> decodeNodes(byte[] content, List<LwM2mPath> pat
}
}

protected void validateNoTimestampedRecord(Collection<LwM2mResolvedSenMLRecord> resolvedRecords) {
for (LwM2mResolvedSenMLRecord resolvedRecord : resolvedRecords) {
validateNoTimestampedRecord(resolvedRecord);
}
}

protected void validateNoTimestampedRecord(LwM2mResolvedSenMLRecord resolvedRecord) {
if (resolvedRecord.getTimeStamp() != null) {
throw new CodecException("Unable to decode node[path:%s] : value should not be timestamped",
resolvedRecord.getPath());
}
}

@Override
public List<TimestampedLwM2mNode> decodeTimestampedData(byte[] content, LwM2mPath path, LwM2mModel model,
Class<? extends LwM2mNode> nodeClass) throws CodecException {
Expand Down Expand Up @@ -460,9 +473,9 @@ private SortedMap<BigDecimal, Collection<LwM2mResolvedSenMLRecord>> groupRecordB
public int compare(BigDecimal o1, BigDecimal o2) {
// null at first place
if (o1 == null) {
return o2 == null ? 0 : 1;
return o2 == null ? 0 : -1;
} else if (o2 == null) {
return -1;
return 1;
} else {
return o2.compareTo(o1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ public byte[] encodeTimestampedData(List<TimestampedLwM2mNode> timestampedNodes,
SenMLPack pack = new SenMLPack();
for (TimestampedLwM2mNode timestampedLwM2mNode : timestampedNodes) {

if (timestampedLwM2mNode.getTimestamp().getEpochSecond() < 268_435_456) {
if (timestampedLwM2mNode.isTimestamped()
&& timestampedLwM2mNode.getTimestamp().getEpochSecond() < 268_435_456) {
// The smallest absolute Time value that can be expressed (2**28) is 1978-07-04 21:24:16 UTC.
// see https://tools.ietf.org/html/rfc8428#section-4.5.3
throw new CodecException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,20 @@ public LwM2mChildNode getContent() {
*/
@Override
public TimestampedLwM2mNode getTimestampedLwM2mNode() {
return super.getTimestampedLwM2mNode();
if (timestampedValues != null && !timestampedValues.isEmpty()) {
return timestampedValues.get(0);
} else {
return super.getTimestampedLwM2mNode();
}
}

/**
* A list of {@link LwM2mNode} representing different state of this resources at different instant. This method
* returns value only on notification when client are using "Notification Storing When Disabled or Offline" and
* content format support it.
* <p>
* The list is sorted by descending time-stamp order (most recent one at first place). If null time-stamp (meaning
* no time information) exists it always at first place as we consider it as "now".
*
* @return a list of {@link TimestampedLwM2mNode} OR <code>null</code> if this is a error response or "Notification
* Storing When Disabled or Offline" is not used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public void can_observe_timestamped_resource(ContentFormat contentFormat, String
ObserveResponse response = server.waitForNotificationOf(observation);
assertThat(response).hasContentFormat(contentFormat, givenServerEndpointProvider);
assertThat(response.getContent()).isEqualTo(mostRecentNode.getNode());
assertThat(response.getTimestampedLwM2mNode()).isEqualTo(mostRecentNode);
assertThat(response.getTimestampedLwM2mNodes()).isEqualTo(timestampedNodes);
}

Expand All @@ -178,7 +179,56 @@ public void can_observe_timestamped_instance(ContentFormat contentFormat, String
new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Paris")));
List<TimestampedLwM2mNode> timestampedNodes = new ArrayList<>();
timestampedNodes.add(mostRecentNode);
timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp().minusMillis(2),
timestampedNodes.add(new TimestampedLwM2mNode(Instant.ofEpochMilli(System.currentTimeMillis()).minusMillis(2),
new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres"))));
timestampedNodes.add(new TimestampedLwM2mNode(Instant.ofEpochMilli(System.currentTimeMillis()).minusMillis(4),
new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres"))));
byte[] payload = encoder.encodeTimestampedData(timestampedNodes, contentFormat, new LwM2mPath("/3/0"),
client.getObjectTree().getModel());

TestObserveUtil.sendNotification(
client.getClientConnector(client.getServerIdForRegistrationId(currentRegistration.getId())),
server.getEndpoint(Protocol.COAP).getURI(), payload,
observeResponse.getObservation().getId().getBytes(), 2, contentFormat);
// *** Hack End *** //

// verify result
server.waitForNewObservation(observation);
ObserveResponse response = server.waitForNotificationOf(observation);

assertThat(response).hasContentFormat(contentFormat, givenServerEndpointProvider);
assertThat(response.getContent()).isEqualTo(mostRecentNode.getNode());
assertThat(response.getTimestampedLwM2mNode()).isEqualTo(mostRecentNode);
assertThat(response.getTimestampedLwM2mNodes()).isEqualTo(timestampedNodes);

}

@TestAllCases
public void can_observe_timestamped_instance_with_null(ContentFormat contentFormat,
String givenServerEndpointProvider) throws InterruptedException {
// observe device timezone
ObserveResponse observeResponse = server.send(currentRegistration, new ObserveRequest(contentFormat, 3, 0));
assertThat(observeResponse) //
.hasCode(CONTENT) //
.hasValidUnderlyingResponseFor(givenServerEndpointProvider);

// an observation response should have been sent
SingleObservation observation = observeResponse.getObservation();
assertThat(observation.getPath()).asString().isEqualTo("/3/0");
assertThat(observation.getRegistrationId()).isEqualTo(currentRegistration.getId());
Set<Observation> observations = server.getObservationService().getObservations(currentRegistration);
assertThat(observations).containsExactly(observation);

// *** HACK send time-stamped notification as Leshan client does not support it *** //
// create time-stamped nodes
TimestampedLwM2mNode mostRecentNode = new TimestampedLwM2mNode(null,
new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Paris")));
List<TimestampedLwM2mNode> timestampedNodes = new ArrayList<>();
timestampedNodes.add(mostRecentNode);
Instant anInstant = Instant.ofEpochMilli(System.currentTimeMillis());
timestampedNodes.add(new TimestampedLwM2mNode(anInstant,
new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres"))));
timestampedNodes.add(new TimestampedLwM2mNode(anInstant.minusMillis(4),
new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres"))));
byte[] payload = encoder.encodeTimestampedData(timestampedNodes, contentFormat, new LwM2mPath("/3/0"),
client.getObjectTree().getModel());
Expand All @@ -194,6 +244,7 @@ public void can_observe_timestamped_instance(ContentFormat contentFormat, String
ObserveResponse response = server.waitForNotificationOf(observation);
assertThat(response).hasContentFormat(contentFormat, givenServerEndpointProvider);
assertThat(response.getContent()).isEqualTo(mostRecentNode.getNode());
assertThat(response.getTimestampedLwM2mNode()).isEqualTo(mostRecentNode);
assertThat(response.getTimestampedLwM2mNodes()).isEqualTo(timestampedNodes);
}

Expand Down Expand Up @@ -235,6 +286,7 @@ public void can_observe_timestamped_object(ContentFormat contentFormat, String g
ObserveResponse response = server.waitForNotificationOf(observation);
assertThat(response).hasContentFormat(contentFormat, givenServerEndpointProvider);
assertThat(response.getContent()).isEqualTo(mostRecentNode.getNode());
assertThat(response.getTimestampedLwM2mNode()).isEqualTo(mostRecentNode);
assertThat(response.getTimestampedLwM2mNodes()).isEqualTo(timestampedNodes);
}
}