From 75c17ba3fc3fbd6c209e874fc0b0d2dad9b43ebe Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Fri, 14 Jun 2024 15:03:10 +0200 Subject: [PATCH] GH-1622: Fix decode empty response for object instance --- .../codec/senml/LwM2mNodeSenMLDecoder.java | 7 +++- .../core/node/codec/LwM2mNodeDecoderTest.java | 38 +++++++++++++++++++ .../util/ContentFormatArgumentConverter.java | 30 +++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 leshan-core/src/test/java/org/eclipse/leshan/core/util/ContentFormatArgumentConverter.java 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 0ed3150254..f07e42a451 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 @@ -296,9 +296,14 @@ private LwM2mNode parseRecords(Collection records, LwM Map> recordsByInstanceId = groupRecordsByInstanceId(records); // validate we have resources for only 1 instance - if (recordsByInstanceId.size() != 1) + if (recordsByInstanceId.size() > 1) throw new CodecException("One instance expected in the payload [path:%s]", path); + // handle empty object instance + if (recordsByInstanceId.size() == 0) { + return new LwM2mObjectInstance(path.getObjectInstanceId(), Collections.emptyList()); + } + // Extract resources Entry> instanceEntry = recordsByInstanceId.entrySet() .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 71b93df6dc..8c29941fa7 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 @@ -57,12 +57,16 @@ import org.eclipse.leshan.core.tlv.Tlv; import org.eclipse.leshan.core.tlv.Tlv.TlvType; import org.eclipse.leshan.core.tlv.TlvEncoder; +import org.eclipse.leshan.core.util.ContentFormatArgumentConverter; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.TestLwM2mId; import org.eclipse.leshan.core.util.TestObjectLoader; import org.eclipse.leshan.core.util.datatype.ULong; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.ValueSource; /** * Unit tests for {@link LwM2mDecoder} @@ -966,6 +970,40 @@ public void senml_json_decode_device_object_instance() { assertEquals("U", instance.getResource(16).getValue()); } + @ParameterizedTest() + @ValueSource(strings = { "SENML_JSON", "SENML_CBOR", "TLV" }) + public void decode_null_object(@ConvertWith(ContentFormatArgumentConverter.class) ContentFormat contentFormat) { + LwM2mObject object = decoder.decode(new byte[0], contentFormat, new LwM2mPath("/1"), model, LwM2mObject.class); + + assertNotNull(object); + assertEquals(1, object.getId()); + assertTrue(object.getInstances().isEmpty()); + } + + @ParameterizedTest() + @ValueSource(strings = { "SENML_JSON", "SENML_CBOR", "TLV" }) + public void decode_null_object_instance( + @ConvertWith(ContentFormatArgumentConverter.class) ContentFormat contentFormat) { + LwM2mObjectInstance instance = decoder.decode(new byte[0], contentFormat, new LwM2mPath("/3/0"), model, + LwM2mObjectInstance.class); + + assertNotNull(instance); + assertEquals(0, instance.getId()); + assertTrue(instance.getResources().isEmpty()); + } + + @ParameterizedTest() + @ValueSource(strings = { "SENML_JSON", "SENML_CBOR", "TLV" }) + public void decode_null_multi_resource( + @ConvertWith(ContentFormatArgumentConverter.class) ContentFormat contentFormat) { + LwM2mResource resource = decoder.decode(new byte[0], contentFormat, new LwM2mPath("/3/0/7"), model, + LwM2mResource.class); + + assertNotNull(resource); + assertEquals(7, resource.getId()); + assertTrue(resource.getInstances().isEmpty()); + } + @Test public void senml_json_decode_single_resource() { String payload = "[{\"bn\":\"/3/0/0\",\"vs\":\"Open Mobile Alliance\"}]"; diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/util/ContentFormatArgumentConverter.java b/leshan-core/src/test/java/org/eclipse/leshan/core/util/ContentFormatArgumentConverter.java new file mode 100644 index 0000000000..3fe838a149 --- /dev/null +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/util/ContentFormatArgumentConverter.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2024 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.core.util; + +import org.eclipse.leshan.core.request.ContentFormat; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.SimpleArgumentConverter; + +public class ContentFormatArgumentConverter extends SimpleArgumentConverter { + @Override + protected Object convert(Object source, Class targetType) throws ArgumentConversionException { + if (targetType.equals(ContentFormat.class)) { + return ContentFormat.fromName((String) source); + } + return null; + } +}