Repository: eclipse/leshan (java)

Issue:
Concerns about getSupportedObject impact on performance
With new `VersionedModelProvider` from recently integrated #657 PR, the `getSupportedObject` will be called each time a Registration is extracted from Store.

This implies some questions about the about performance impact.
Here some result obtained with [jmh](https://openjdk.java.net/projects/code-tools/jmh/).

```
Benchmark (short is better)
(for shot registration ~4 objects)                                        Mode  Cnt      Score      Error  Units
get registration from store                                               avgt    3     75,454 ±    6,694  us/op
get registration from store + getSupportedObject                          avgt    3     90,996 ±  269,585  us/op
get registration from store + getSupportedObject with best alternative    avgt    3     75,726 ±   15,547  us/op

(for shot registration ~1000 objects)
get registration from store                                               avgt    3    747,277 ±  180,946  us/op
get registration from store + getSupportedObject                          avgt    3   1113,980 ±  836,456  us/op
get registration from store + getSupportedObject with best alternative    avgt    3    888,841 ±  669,591  us/op

JSON deserialization (total)                                              avgt    3    547,493 ±  245,424  us/op
JSON deserialization (string => minimal-json object)                      avgt    3    428,356 ±  110,013  us/op
JSON deserialization (minimal-json object => registration)                avgt    3    212,030 ±   63,589  us/op

getSupportedObject current implementation                                 avgt    3    250,588 ±   36,980  us/op
getSupportedObject using Scanner                                          avgt    3  16903,316 ± 1026,846  us/op
getSupportedObject using LWM2MPath (String.split)                         avgt    3    162,832 ±   18,660  us/op
getSupportedObject using StringTokenizer                                  avgt    3    110,568 ±    6,635  us/op
getSupportedObject using iterate over string                              avgt    3    105,756 ±   12,045  us/op
```

Pull request:
#732: fix rt and ver attributes of Link format.
This PR aims to fix #729.

### About `ver` attribute

The lwm2m specification **v1.0.x** seems to not be clear about the use of quote `"` for `ver` value :
 - _§5.2.7.3 Bootstrap DISCOVER_ seems to not use quote : `</55>;ver=1.9,`
 - _§6.2.3Object Definition and Object Version Usage_ seems to use quote : `</44>;ver=“2.2”`

The version **v1.1.x** both examples seem to not use quote and the format is clearly defined in _§5.1.2. Attributes Classification_ : `"ver" "=" 1*DIGIT "." 1*DIGIT`, so quote MUST NOT be used in v1.1.x.

Regarding this, we choose to be able to read (server side) both quoted and unquoted value and we will write(client) it without using quote.

### About `rt="oma.lwm2m"` attribute

There is nothing clear in the specification neither in **v1.0.x** nor **v1.1.x**, but all example use quote (`"oma.lwm2m"`).

Following the [rfc669](https://tools.ietf.org/html/rfc6690#section-2) quote could be used or not for single rt value.

So we choose to be able to read (server side) both quoted and unquoted value and we will write(client) it with quote (like in lwm2m example).

Patch:
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/util/LinkFormatHelper.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/util/LinkFormatHelper.java
index f6a3381c..384313b2 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/util/LinkFormatHelper.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/util/LinkFormatHelper.java
@@ -48,8 +48,8 @@ public final class LinkFormatHelper {
 
         // create links for "object"
         String rootURL = getPath("/", root);
-        Map<String, Object> attributes = new HashMap<>();
-        attributes.put("rt", "oma.lwm2m");
+        Map<String, String> attributes = new HashMap<>();
+        attributes.put("rt", "\"oma.lwm2m\"");
         links.add(new Link(rootURL, attributes));
 
         // sort resources
@@ -67,7 +67,7 @@ public final class LinkFormatHelper {
 
             List<Integer> availableInstance = objectEnabler.getAvailableInstanceIds();
             // Include an object link if there are no instances or there are object attributes (e.g. "ver")
-            Map<String, ?> objectAttributes = getObjectAttributes(objectEnabler.getObjectModel());
+            Map<String, String> objectAttributes = getObjectAttributes(objectEnabler.getObjectModel());
             if (availableInstance.isEmpty() || (objectAttributes != null)) {
                 String objectInstanceUrl = getPath("/", root, Integer.toString(objectEnabler.getId()));
                 links.add(new Link(objectInstanceUrl, objectAttributes));
@@ -89,7 +89,7 @@ public final class LinkFormatHelper {
         String rootPath = root == null ? "" : root;
 
         // create link for "object"
-        Map<String, ?> objectAttributes = getObjectAttributes(objectEnabler.getObjectModel());
+        Map<String, String> objectAttributes = getObjectAttributes(objectEnabler.getObjectModel());
         String objectURL = getPath("/", rootPath, Integer.toString(objectEnabler.getId()));
         links.add(new Link(objectURL, objectAttributes));
 
@@ -192,7 +192,7 @@ public final class LinkFormatHelper {
         return sb.toString();
     }
 
-    private static Map<String, ?> getObjectAttributes(ObjectModel objectModel) {
+    private static Map<String, String> getObjectAttributes(ObjectModel objectModel) {
         if (StringUtils.isEmpty(objectModel.version) || ObjectModel.DEFAULT_VERSION.equals(objectModel.version)) {
             return null;
         }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/Link.java b/leshan-core/src/main/java/org/eclipse/leshan/Link.java
index ef633ecc..41a839b7 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/Link.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/Link.java
@@ -24,6 +24,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 
 import org.eclipse.leshan.util.StringUtils;
+import org.eclipse.leshan.util.Validate;
 
 /**
  * A Link as defined in http://tools.ietf.org/html/rfc6690.
@@ -46,7 +47,7 @@ public class Link implements Serializable {
 
     private final String url;
 
-    private final Map<String, Object> attributes;
+    private final Map<String, String> attributes;
 
     /**
      * Creates a new Link without attributes.
@@ -54,7 +55,7 @@ public class Link implements Serializable {
      * @param url the object link URL
      */
     public Link(String url) {
-        this(url, null);
+        this(url, (Map<String, String>) null);
     }
 
     /**
@@ -63,7 +64,8 @@ public class Link implements Serializable {
      * @param url the link URL
      * @param attributes the object link attributes or <code>null</code> if the link has no attributes
      */
-    public Link(String url, Map<String, ?> attributes) {
+    public <T> Link(String url, Map<String, String> attributes) {
+        Validate.notNull(url);
         this.url = url;
         if (attributes != null) {
             this.attributes = Collections.unmodifiableMap(new HashMap<>(attributes));
@@ -72,6 +74,61 @@ public class Link implements Serializable {
         }
     }
 
+    /**
+     * Creates a new link and with its attributes.
+     * 
+     * @param url the link URL
+     * @param attributes the object link attributes or <code>null</code> if the link has no attributes
+     */
+    @SuppressWarnings("unchecked")
+    public <T> Link(String url, Map<String, T> attributes, Class<T> clazz) {
+        Validate.notNull(url);
+        this.url = url;
+        if (attributes == null || attributes.isEmpty()) {
+            this.attributes = Collections.emptyMap();
+        } else {
+            if (String.class.equals(clazz)) {
+                this.attributes = Collections.unmodifiableMap((Map<String, String>) new HashMap<>(attributes));
+            } else {
+                HashMap<String, String> attributesMap = new HashMap<>();
+                for (Entry<String, T> attr : attributes.entrySet()) {
+                    if (attr.getValue() == null) {
+                        attributesMap.put(attr.getKey(), null);
+                    } else {
+                        attributesMap.put(attr.getKey(), attr.getValue().toString());
+                    }
+
+                }
+                this.attributes = Collections.unmodifiableMap(attributesMap);
+            }
+        }
+    }
+
+    /**
+     * Creates a new link and with its attributes.
+     * 
+     * @param url the link URL
+     * @param attributes the object link attributes. The format is attributeKey1, attributeValue1, attributeKey2,
+     *        attributeValue2. For empty attributes null value should be used.
+     */
+    public Link(String url, String... attributes) {
+        Validate.notNull(url);
+        this.url = url;
+        if (attributes == null || attributes.length == 0) {
+            this.attributes = Collections.emptyMap();
+        } else {
+            if (attributes.length % 2 != 0) {
+                throw new IllegalArgumentException("Each attributes key must have a value");
+            }
+
+            HashMap<String, String> attributesMap = new HashMap<>();
+            for (int i = 0; i < attributes.length; i = i + 2) {
+                attributesMap.put(attributes[i], attributes[i + 1]);
+            }
+            this.attributes = Collections.unmodifiableMap(attributesMap);
+        }
+    }
+
     public String getUrl() {
         return url;
     }
@@ -81,7 +138,7 @@ public class Link implements Serializable {
      * 
      * @return an unmodifiable map containing the link attributes
      */
-    public Map<String, Object> getAttributes() {
+    public Map<String, String> getAttributes() {
         return attributes;
     }
 
@@ -92,18 +149,14 @@ public class Link implements Serializable {
         builder.append(getUrl());
         builder.append('>');
 
-        Map<String, Object> attributes = getAttributes();
+        Map<String, String> attributes = getAttributes();
         if (attributes != null && !attributes.isEmpty()) {
-            for (Entry<String, Object> entry : attributes.entrySet()) {
+            for (Entry<String, String> entry : attributes.entrySet()) {
                 builder.append(";");
                 builder.append(entry.getKey());
                 if (entry.getValue() != null) {
                     builder.append("=");
-                    if (entry.getValue() instanceof String) {
-                        builder.append("\"").append(entry.getValue()).append("\"");
-                    } else {
-                        builder.append(entry.getValue());
-                    }
+                    builder.append(entry.getValue());
                 }
             }
         }
@@ -132,22 +185,16 @@ public class Link implements Serializable {
             url = StringUtils.removeStart(StringUtils.removeEnd(url, ">"), "<");
 
             // parse attributes
-            Map<String, Object> attributes = new HashMap<>();
+            Map<String, String> attributes = new HashMap<>();
 
             if (linkParts.length > 1) {
                 for (int i = 1; i < linkParts.length; i++) {
                     String[] attParts = linkParts[i].split("=");
                     if (attParts.length > 0) {
                         String key = attParts[0];
-                        Object value = null;
+                        String value = null;
                         if (attParts.length > 1) {
-                            String rawvalue = attParts[1];
-                            try {
-                                value = Integer.valueOf(rawvalue);
-                            } catch (NumberFormatException e) {
-
-                                value = rawvalue.replaceFirst("^\"(.*)\"$", "$1");
-                            }
+                            value = attParts[1];
                         }
                         attributes.put(key, value);
                     }
@@ -210,4 +257,18 @@ public class Link implements Serializable {
             return false;
         return true;
     }
+
+    /**
+     * remove quote from string, only if it begins and ends by a quote.
+     * 
+     * @param string to unquote
+     * @return unquoted string or the original string if there no quote to remove.
+     */
+    public static String unquote(String string) {
+        if (string != null && string.length() >= 2 && string.charAt(0) == '"'
+                && string.charAt(string.length() - 1) == '"') {
+            string = string.substring(1, string.length() - 1);
+        }
+        return string;
+    }
 }
\ No newline at end of file
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java
index b06e2f62..b206b893 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java
@@ -98,7 +98,7 @@ public class Registration implements Serializable {
         String rootPath = "/";
         if (objectLinks != null) {
             for (Link link : objectLinks) {
-                if (link != null && "oma.lwm2m".equals(link.getAttributes().get("rt"))) {
+                if (link != null && "oma.lwm2m".equals(Link.unquote(link.getAttributes().get("rt")))) {
                     rootPath = link.getUrl();
                     break;
                 }
@@ -366,21 +366,23 @@ public class Registration implements Serializable {
                     try {
                         // extract object id and version
                         int objectId = Integer.parseInt(m.group(1));
-                        Object version = link.getAttributes().get(Attribute.OBJECT_VERSION);
+                        String version = link.getAttributes().get(Attribute.OBJECT_VERSION);
+                        // un-quote version (see https://github.com/eclipse/leshan/issues/732)
+                        version = Link.unquote(version);
                         String currentVersion = objects.get(objectId);
 
                         // store it in map
                         if (currentVersion == null) {
                             // we never find version for this object add it
-                            if (version instanceof String) {
-                                objects.put(objectId, (String) version);
+                            if (version != null) {
+                                objects.put(objectId, version);
                             } else {
                                 objects.put(objectId, ObjectModel.DEFAULT_VERSION);
                             }
                         } else {
                             // if version is already set, we override it only if new version is not DEFAULT_VERSION
-                            if (version instanceof String && !version.equals(ObjectModel.DEFAULT_VERSION)) {
-                                objects.put(objectId, (String) version);
+                            if (version != null && !version.equals(ObjectModel.DEFAULT_VERSION)) {
+                                objects.put(objectId, version);
                             }
                         }
                     } catch (NumberFormatException e) {
diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java
index b189fbe1..d864de1b 100644
--- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java
+++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java
@@ -51,11 +51,9 @@ public class RegistrationSerDes {
             JsonObject ol = Json.object();
             ol.add("url", l.getUrl());
             JsonObject at = Json.object();
-            for (Map.Entry<String, Object> e : l.getAttributes().entrySet()) {
+            for (Map.Entry<String, String> e : l.getAttributes().entrySet()) {
                 if (e.getValue() == null) {
                     at.add(e.getKey(), Json.NULL);
-                } else if (e.getValue() instanceof Integer) {
-                    at.add(e.getKey(), (int) e.getValue());
                 } else {
                     at.add(e.getKey(), e.getValue().toString());
                 }
@@ -99,14 +97,15 @@ public class RegistrationSerDes {
         for (int i = 0; i < links.size(); i++) {
             JsonObject ol = (JsonObject) links.get(i);
 
-            Map<String, Object> attMap = new HashMap<>();
+            Map<String, String> attMap = new HashMap<>();
             JsonObject att = (JsonObject) ol.get("at");
             for (String k : att.names()) {
                 JsonValue jsonValue = att.get(k);
                 if (jsonValue.isNull()) {
                     attMap.put(k, null);
                 } else if (jsonValue.isNumber()) {
-                    attMap.put(k, jsonValue.asInt());
+                    // This else block is just needed for retro-compatibility
+                    attMap.put(k, Integer.toString(jsonValue.asInt()));
                 } else {
                     attMap.put(k, jsonValue.asString());
                 }


Test patch:
diff --git a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java
index 478e1e9b..9984abba 100644
--- a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java
+++ b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java
@@ -85,8 +85,7 @@ public class LinkFormatHelperTest {
         Link[] links = LinkFormatHelper.getObjectDescription(createObjectEnabler(locationModel), "/");
         String strLinks = Link.serialize(links);
 
-        assertEquals("</6>;ver=\"2.0\",</6/0>,</6/0/0>,</6/0/1>,</6/0/2>,</6/0/3>,</6/0/4>,</6/0/5>,</6/0/6>",
-                strLinks);
+        assertEquals("</6>;ver=2.0,</6/0>,</6/0/0>,</6/0/1>,</6/0/2>,</6/0/3>,</6/0/4>,</6/0/5>,</6/0/6>", strLinks);
     }
 
     @Test
@@ -128,7 +127,7 @@ public class LinkFormatHelperTest {
         Link[] links = LinkFormatHelper.getClientDescription(objectEnablers, null);
         String strLinks = Link.serialize(links);
 
-        assertEquals("</>;rt=\"oma.lwm2m\",</6>;ver=\"2.0\",</6/0>,</6/1>", strLinks);
+        assertEquals("</>;rt=\"oma.lwm2m\",</6>;ver=2.0,</6/0>,</6/1>", strLinks);
     }
 
     @Test
@@ -142,7 +141,7 @@ public class LinkFormatHelperTest {
         Link[] links = LinkFormatHelper.getClientDescription(objectEnablers, null);
         String strLinks = Link.serialize(links);
 
-        assertEquals("</>;rt=\"oma.lwm2m\",</6>;ver=\"2.0\"", strLinks);
+        assertEquals("</>;rt=\"oma.lwm2m\",</6>;ver=2.0", strLinks);
     }
 
     private ObjectModel getObjectModel(int id) {
diff --git a/leshan-core/src/test/java/org/eclipse/leshan/LinkObjectTest.java b/leshan-core/src/test/java/org/eclipse/leshan/LinkObjectTest.java
index ce47903b..67bfabe8 100644
--- a/leshan-core/src/test/java/org/eclipse/leshan/LinkObjectTest.java
+++ b/leshan-core/src/test/java/org/eclipse/leshan/LinkObjectTest.java
@@ -15,6 +15,8 @@
  *******************************************************************************/
 package org.eclipse.leshan;
 
+import static org.junit.Assert.assertEquals;
+
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -30,9 +32,9 @@ public class LinkObjectTest {
         Assert.assertEquals(5, parse.length);
         Assert.assertEquals("/", parse[0].getUrl());
 
-        Map<String, Object> attResult = new HashMap<>();
-        attResult.put("rt", "oma.lwm2m");
-        attResult.put("ct", 100);
+        Map<String, String> attResult = new HashMap<>();
+        attResult.put("rt", "\"oma.lwm2m\"");
+        attResult.put("ct", "100");
         Assert.assertEquals(attResult, parse[0].getAttributes());
 
         Assert.assertEquals("/1/101", parse[1].getUrl());
@@ -58,7 +60,7 @@ public class LinkObjectTest {
         Assert.assertEquals("/", parse[0].getUrl());
 
         Map<String, String> attResult = new HashMap<>();
-        attResult.put("k1", "quotes\"inside");
+        attResult.put("k1", "\"quotes\"inside\"");
         attResult.put("k2", "endwithquotes\"");
         attResult.put("k3", "noquotes");
         attResult.put("k4", "\"startwithquotes");
@@ -79,17 +81,9 @@ public class LinkObjectTest {
 
     @Test
     public void serialyse_with_attributes() {
-        HashMap<String, Object> attributesObj1 = new HashMap<>();
-        attributesObj1.put("number", 12);
-        Link obj1 = new Link("/1/0/1", attributesObj1);
-
-        HashMap<String, Object> attributesObj2 = new HashMap<>();
-        attributesObj2.put("string", "stringval");
-        Link obj2 = new Link("/2/1", attributesObj2);
-
-        HashMap<String, Object> attributesObj3 = new HashMap<>();
-        attributesObj3.put("empty", null);
-        Link obj3 = new Link("/3", attributesObj3);
+        Link obj1 = new Link("/1/0/1", "number", "12");
+        Link obj2 = new Link("/2/1", "string", "\"stringval\"");
+        Link obj3 = new Link("/3", "empty", null);
 
         String res = Link.serialize(obj1, obj2, obj3);
 
@@ -99,9 +93,9 @@ public class LinkObjectTest {
 
     @Test
     public void serialyse_with_root_url() {
-        HashMap<String, Object> attributesObj1 = new HashMap<>();
+        HashMap<String, Integer> attributesObj1 = new HashMap<>();
         attributesObj1.put("number", 12);
-        Link obj1 = new Link("/", attributesObj1);
+        Link obj1 = new Link("/", attributesObj1, Integer.class);
 
         String res = Link.serialize(obj1);
 
@@ -115,11 +109,11 @@ public class LinkObjectTest {
         attributesObj1.put("number1", 1);
         attributesObj1.put("number2", 1);
         attributesObj1.put("string1", "stringval1");
-        Link obj1 = new Link("/1/0", attributesObj1);
+        Link obj1 = new Link("/1/0", attributesObj1, Object.class);
 
-        HashMap<String, Object> attributesObj2 = new HashMap<>();
+        HashMap<String, Integer> attributesObj2 = new HashMap<>();
         attributesObj2.put("number3", 3);
-        Link obj2 = new Link("/2", attributesObj2);
+        Link obj2 = new Link("/2", attributesObj2, Integer.class);
 
         Link[] input = new Link[] { obj1, obj2 };
         String strObjs = Link.serialize(input);
@@ -137,4 +131,26 @@ public class LinkObjectTest {
         Assert.assertEquals(input, output);
 
     }
+
+    @Test
+    public void parse_quoted_ver_attributes() {
+        String input = "</1>;ver=\"2.2\"";
+        Link[] objs = Link.parse(input.getBytes());
+        assertEquals(objs[0].getAttributes().get("ver"), "\"2.2\"");
+    }
+
+    @Test
+    public void parse_unquoted_ver_attributes() {
+        String input = "</1>;ver=2.2";
+        Link[] objs = Link.parse(input.getBytes());
+        assertEquals(objs[0].getAttributes().get("ver"), "2.2");
+    }
+
+    @Test
+    public void serialyse_ver_attributes_without_quote() {
+        Map<String, String> att = new HashMap<>();
+        att.put("ver", "2.2");
+        Link link = new Link("/1", att);
+        assertEquals("</1>;ver=2.2", Link.serialize(link));
+    }
 }
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java
index 0f4db71c..4a314e74 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java
@@ -78,7 +78,7 @@ public class CoapRequestBuilderTest {
                 Identity.unsecure(Inet4Address.getLoopbackAddress(), 12354));
         if (rootpath != null) {
             Map<String, String> attr = new HashMap<>();
-            attr.put("rt", "oma.lwm2m");
+            attr.put("rt", "\"oma.lwm2m\"");
             b.objectLinks(new Link[] { new Link(rootpath, attr) });
         }
         return b.build();
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationTest.java
index ac289c8b..ae7af277 100644
--- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationTest.java
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationTest.java
@@ -52,6 +52,17 @@ public class RegistrationTest {
         assertNull(supportedObject.get(3));
     }
 
+    @Test
+    public void test_supported_object_given_an_object_link_with_unquoted_rootpath() {
+        Registration reg = given_a_registration_with_object_link_like("</root>;rt=oma.lwm2m, </root/1/0>,</3/0>");
+
+        // Ensure supported objects are correct
+        Map<Integer, String> supportedObject = reg.getSupportedObject();
+        assertEquals(1, supportedObject.size());
+        assertEquals(ObjectModel.DEFAULT_VERSION, supportedObject.get(1));
+        assertNull(supportedObject.get(3));
+    }
+
     @Test
     public void test_supported_object_given_an_object_link_with_regexp_rootpath() {
         Registration reg = given_a_registration_with_object_link_like(
diff --git a/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java b/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java
index 5cba1dd5..426083e9 100644
--- a/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java
+++ b/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java
@@ -36,7 +36,7 @@ public class RegistrationSerDesTest {
         att.put("ts", 12);
         att.put("rt", "test");
         att.put("hb", null);
-        objs[0] = new Link("/0/1024/2", att);
+        objs[0] = new Link("/0/1024/2", att, Object.class);
         objs[1] = new Link("/0/2");
 
         Registration.Builder builder = new Registration.Builder("registrationId", "endpoint",