Repository: eclipse/leshan
Pull request #710 (2019-06-28 15:11:37)

Do not do type conversion at encoding time by default anymore.
Until now Leshan used by default a kind of magic converter.  
This converter was used `LwM2mNodeEncoder` to fix `LwM2mResource` which would used a different
`ResourceModel.Type` than the one defined in the `ResourceModel`.

This magic conversion was mainly used to correct value which came from the leshan-server-demo UI...
So this does not make so much sense to keep this behavior by default.

So now the default converter is a `NoValueConverter` which do not do any converstion but raise an exception, if type in resource does not match the model.

**If you want to keep the previous behavior** you need to duplication class from leshan-server-demo : `MagicLwM2mValueConverter` and create your server like this : 

```java
LeshanServerBuilder builder = new LeshanServerBuilder();
builder.setEncoder(new DefaultLwM2mNodeEncoder(new MagicLwM2mValueConverter()));
LeshanServer server = builder.build();

```


Patch:
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mNodeEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mNodeEncoder.java
index e83f5c0b..591701fc 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mNodeEncoder.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mNodeEncoder.java
@@ -52,7 +52,7 @@ public class DefaultLwM2mNodeEncoder implements LwM2mNodeEncoder {
      * Create {@link DefaultLwM2mNodeEncoder} without support of old TLV and JSON code.
      */
     public DefaultLwM2mNodeEncoder() {
-        this(new DefaultLwM2mValueConverter());
+        this(new LwM2mValueChecker());
     }
 
     public DefaultLwM2mNodeEncoder(LwM2mValueConverter converter) {
@@ -68,7 +68,7 @@ public class DefaultLwM2mNodeEncoder implements LwM2mNodeEncoder {
      * @param supportDeprecatedContentFormat True to accept to encode old code.
      */
     public DefaultLwM2mNodeEncoder(boolean supportDeprecatedContentFormat) {
-        this(new DefaultLwM2mValueConverter(), supportDeprecatedContentFormat);
+        this(new LwM2mValueChecker(), supportDeprecatedContentFormat);
     }
 
     public DefaultLwM2mNodeEncoder(LwM2mValueConverter converter, boolean supportDeprecatedContentFormat) {
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mValueChecker.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mValueChecker.java
new file mode 100644
index 00000000..e7a4695d
--- /dev/null
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mValueChecker.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.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.node.codec;
+
+import org.eclipse.leshan.core.model.ResourceModel.Type;
+import org.eclipse.leshan.core.node.LwM2mPath;
+
+/**
+ * A {@link LwM2mValueConverter} which do no conversion but raise a {@link CodecException} if type is not the expected
+ * one.
+ *
+ */
+public class LwM2mValueChecker implements LwM2mValueConverter {
+
+    @Override
+    public Object convertValue(Object value, Type currentType, Type expectedType, LwM2mPath resourcePath) {
+        if (expectedType == null) {
+            // unknown resource, trusted value
+            return value;
+        }
+
+        if (currentType == expectedType) {
+            // expected type
+            return value;
+        }
+
+        throw new CodecException("Invalid value type for resource %s, expected %s, got %s", resourcePath, expectedType,
+                currentType);
+    }
+}
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mValueConverter.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mValueConverter.java
index c795117f..52b31df6 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mValueConverter.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mValueConverter.java
@@ -15,11 +15,16 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.node.codec;
 
+import org.eclipse.leshan.core.model.ResourceModel;
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.node.LwM2mPath;
+import org.eclipse.leshan.core.node.LwM2mResource;
 
 /**
  * Helper to convert value "magically" from one type to another.
+ * <p>
+ * This is used by {@link LwM2mNodeEncoder} to fix {@link LwM2mResource} which would used a different
+ * {@link ResourceModel.Type} than the one defined in the {@link ResourceModel}.
  */
 public interface LwM2mValueConverter {
 
diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java
index 969b2070..38907680 100644
--- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java
+++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java
@@ -66,6 +66,7 @@ import org.eclipse.leshan.server.demo.servlet.ClientServlet;
 import org.eclipse.leshan.server.demo.servlet.EventServlet;
 import org.eclipse.leshan.server.demo.servlet.ObjectSpecServlet;
 import org.eclipse.leshan.server.demo.servlet.SecurityServlet;
+import org.eclipse.leshan.server.demo.utils.MagicLwM2mValueConverter;
 import org.eclipse.leshan.server.impl.FileSecurityStore;
 import org.eclipse.leshan.server.model.LwM2mModelProvider;
 import org.eclipse.leshan.server.model.StaticModelProvider;
@@ -375,6 +376,9 @@ public class LeshanServerDemo {
         }
         builder.setSecurityStore(securityStore);
 
+        // use a magic converter to support bad type send by the UI.
+        builder.setEncoder(new DefaultLwM2mNodeEncoder(new MagicLwM2mValueConverter()));
+
         // Create and start LWM2M server
         LeshanServer lwServer = builder.build();
 
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mValueConverter.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/utils/MagicLwM2mValueConverter.java
similarity index 93%
rename from leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mValueConverter.java
rename to leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/utils/MagicLwM2mValueConverter.java
index a5281ce0..1ea5379d 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/DefaultLwM2mValueConverter.java
+++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/utils/MagicLwM2mValueConverter.java
@@ -16,7 +16,7 @@
  *                                                     javax.xml.bind.DatatypeConverter
  *                                                     which is not available on Android
  *******************************************************************************/
-package org.eclipse.leshan.core.node.codec;
+package org.eclipse.leshan.server.demo.utils;
 
 import java.util.Date;
 
@@ -26,14 +26,19 @@ import javax.xml.datatype.XMLGregorianCalendar;
 
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.node.LwM2mPath;
+import org.eclipse.leshan.core.node.codec.CodecException;
+import org.eclipse.leshan.core.node.codec.LwM2mValueConverter;
 import org.eclipse.leshan.util.Hex;
 import org.eclipse.leshan.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class DefaultLwM2mValueConverter implements LwM2mValueConverter {
+/**
+ * A {@link LwM2mValueConverter} which will do some magic conversion.
+ */
+public class MagicLwM2mValueConverter implements LwM2mValueConverter {
 
-    private static final Logger LOG = LoggerFactory.getLogger(DefaultLwM2mValueConverter.class);
+    private static final Logger LOG = LoggerFactory.getLogger(MagicLwM2mValueConverter.class);
 
     @Override
     public Object convertValue(Object value, Type currentType, Type expectedType, LwM2mPath resourcePath)


Test patch:
diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeEncoderTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeEncoderTest.java
index 7de5ed16..1a8d1303 100644
--- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeEncoderTest.java
+++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeEncoderTest.java
@@ -54,7 +54,7 @@ public class LwM2mNodeEncoderTest {
     }
 
     @Test
-    public void text_encode_single_resource() {
+    public void text_encode_single_resource_float() {
 
         byte[] encoded = encoder.encode(LwM2mSingleResource.newFloatResource(15, 56.4D), ContentFormat.TEXT,
                 new LwM2mPath("/323/0/15"), model);
@@ -63,20 +63,11 @@ public class LwM2mNodeEncoderTest {
     }
 
     @Test
-    public void text_encode_date_as_long() {
+    public void text_encode_single_resource_date() {
 
-        byte[] encoded = encoder.encode(LwM2mSingleResource.newStringResource(13, "2010-01-01T12:00:00+01:00"),
+        byte[] encoded = encoder.encode(LwM2mSingleResource.newDateResource(13, new Date(1367491215000L)),
                 ContentFormat.TEXT, new LwM2mPath("/3/0/13"), model);
 
-        Assert.assertEquals("1262343600", new String(encoded, StandardCharsets.UTF_8));
-    }
-
-    @Test
-    public void text_encode_date_as_iso_string() {
-
-        byte[] encoded = encoder.encode(LwM2mSingleResource.newIntegerResource(13, 1367491215000L), ContentFormat.TEXT,
-                new LwM2mPath("/3/0/13"), model);
-
         Assert.assertEquals("1367491215", new String(encoded, StandardCharsets.UTF_8));
     }
 
diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/Lwm2mValueConverterTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/Lwm2mValueConverterTest.java
deleted file mode 100644
index 8173b224..00000000
--- a/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/Lwm2mValueConverterTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
- * 
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * and Eclipse Distribution License v1.0 which accompany this distribution.
- * 
- * The Eclipse Public License is available at
- *    http://www.eclipse.org/legal/epl-v10.html
- * and the Eclipse Distribution License is available at
- *    http://www.eclipse.org/org/documents/edl-v10.html.
- * 
- * Contributors:
- *     Kai Hudalla (Bosch Software Innovations GmbH) - initial creation
- *******************************************************************************/
-package org.eclipse.leshan.core.node.codec;
-
-import static org.hamcrest.CoreMatchers.*;
-import static org.junit.Assert.*;
-
-import org.eclipse.leshan.core.model.ResourceModel.Type;
-import org.junit.Test;
-
-public class Lwm2mValueConverterTest {
-
-    @Test
-    public void testConvertValueParsesHexString() {
-        String hexString = "10FF"; // 16 * 256 + 255 = 4351
-        Object opaqueValue = new DefaultLwM2mValueConverter().convertValue(hexString, Type.STRING, Type.OPAQUE, null);
-        assertThat(opaqueValue, instanceOf(byte[].class));
-        assertThat((byte[]) opaqueValue, is(new byte[] { (byte) 0x10, (byte) 0xff }));
-    }
-
-    @Test(expected = CodecException.class)
-    public void testConvertValueDetectsNonHexChars() {
-        String hexString = "10R8";
-        new DefaultLwM2mValueConverter().convertValue(hexString, Type.STRING, Type.OPAQUE, null);
-    }
-
-    @Test(expected = CodecException.class)
-    public void testConvertValueDetectsInvalidHexStringLength() {
-        String hexString = "10F";
-        new DefaultLwM2mValueConverter().convertValue(hexString, Type.STRING, Type.OPAQUE, null);
-    }
-
-    @Test
-    public void testConvertPositiveFloatToInteger() {
-        double floatValue = 10.0d;
-        Object convertValue = new DefaultLwM2mValueConverter().convertValue(floatValue, Type.FLOAT, Type.INTEGER, null);
-        assertEquals(10l, convertValue);
-    }
-
-    @Test
-    public void testConvertNegativeFloatToInteger() {
-        double floatValue = -2015.0d;
-        Object convertValue = new DefaultLwM2mValueConverter().convertValue(floatValue, Type.FLOAT, Type.INTEGER, null);
-        assertEquals(-2015l, convertValue);
-    }
-
-    @Test(expected = CodecException.class)
-    public void testConvertFloatToIntegerDetectsInvalidConvertion() {
-        double floatValue = 10.5d;
-        new DefaultLwM2mValueConverter().convertValue(floatValue, Type.FLOAT, Type.INTEGER, null);
-    }
-
-    @Test(expected = CodecException.class)
-    public void testConvertMaxFloatToIntegerDetectsInvalidConvertion() {
-        double floatValue = Double.MAX_VALUE;
-        new DefaultLwM2mValueConverter().convertValue(floatValue, Type.FLOAT, Type.INTEGER, null);
-    }
-
-    @Test
-    public void testConvertPositiveIntegerToFloat() {
-        long longValue = 999l;
-        Object convertValue = new DefaultLwM2mValueConverter().convertValue(longValue, Type.INTEGER, Type.FLOAT, null);
-        assertEquals(999.0d, convertValue);
-    }
-
-    @Test
-    public void testConvertNegativeIntegerToFloat() {
-        long longValue = -10l;
-        Object convertValue = new DefaultLwM2mValueConverter().convertValue(longValue, Type.INTEGER, Type.FLOAT, null);
-        assertEquals(-10.0d, convertValue);
-    }
-
-    @Test(expected = CodecException.class)
-    public void testConvertIntegerToFloatDetectsInvalidConvertion() {
-        long longValue = 9223372036854775806l;
-        new DefaultLwM2mValueConverter().convertValue(longValue, Type.INTEGER, Type.FLOAT, null);
-    }
-}
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/CreateTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/CreateTest.java
index a4686544..3534313f 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/CreateTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/CreateTest.java
@@ -182,7 +182,7 @@ public class CreateTest {
     public void cannot_create_mandatory_single_object() throws InterruptedException {
         // try to create another instance of device object
         CreateResponse response = helper.server.send(helper.getCurrentRegistration(),
-                new CreateRequest(3, new LwM2mResource[] { LwM2mSingleResource.newIntegerResource(3, 123) }));
+                new CreateRequest(3, new LwM2mResource[] { LwM2mSingleResource.newStringResource(3, "v123") }));
 
         // verify result
         assertEquals(ResponseCode.BAD_REQUEST, response.getCode());
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
index 9bc145c7..d7318084 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
@@ -47,7 +47,7 @@ import org.eclipse.leshan.core.node.LwM2mObjectInstance;
 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.codec.DefaultLwM2mValueConverter;
+import org.eclipse.leshan.core.node.codec.LwM2mValueChecker;
 import org.eclipse.leshan.core.node.codec.json.LwM2mNodeJsonEncoder;
 import org.eclipse.leshan.core.observation.Observation;
 import org.eclipse.leshan.core.request.CancelObservationRequest;
@@ -324,8 +324,7 @@ public class ObserveTest {
 
         // *** HACK send a notification with unsupported content format *** //
         byte[] payload = LwM2mNodeJsonEncoder.encode(LwM2mSingleResource.newStringResource(15, "Paris"),
-                new LwM2mPath("/3/0/15"), new LwM2mModel(helper.createObjectModels()),
-                new DefaultLwM2mValueConverter());
+                new LwM2mPath("/3/0/15"), new LwM2mModel(helper.createObjectModels()), new LwM2mValueChecker());
         Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
         sendNotification(getConnector(helper.client), payload, firstCoapResponse, 666); // 666 is not a supported //
                                                                                         // contentFormat.
@@ -367,7 +366,7 @@ public class ObserveTest {
         timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp() - 2,
                 LwM2mSingleResource.newStringResource(15, "Londres")));
         byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3/0/15"),
-                new LwM2mModel(helper.createObjectModels()), new DefaultLwM2mValueConverter());
+                new LwM2mModel(helper.createObjectModels()), new LwM2mValueChecker());
         Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
         sendNotification(getConnector(helper.client), payload, firstCoapResponse, ContentFormat.JSON_CODE);
         // *** Hack End *** //
@@ -410,7 +409,7 @@ public class ObserveTest {
         timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp() - 2,
                 new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres"))));
         byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3/0"),
-                new LwM2mModel(helper.createObjectModels()), new DefaultLwM2mValueConverter());
+                new LwM2mModel(helper.createObjectModels()), new LwM2mValueChecker());
         Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
         sendNotification(getConnector(helper.client), payload, firstCoapResponse, ContentFormat.JSON_CODE);
         // *** Hack End *** //
@@ -453,7 +452,7 @@ public class ObserveTest {
         timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp() - 2,
                 new LwM2mObject(3, new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres")))));
         byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3"),
-                new LwM2mModel(helper.createObjectModels()), new DefaultLwM2mValueConverter());
+                new LwM2mModel(helper.createObjectModels()), new LwM2mValueChecker());
 
         Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
         sendNotification(getConnector(helper.client), payload, firstCoapResponse, ContentFormat.JSON_CODE);