From 998873d73fc53ce5922e77a4e72dd52b60159147 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Mon, 28 Oct 2019 11:55:11 +0100 Subject: [PATCH] Ensure that LwM2M node is created with valid ID --- .../core/node/LwM2mMultipleResource.java | 10 ++- .../leshan/core/node/LwM2mNodeUtil.java | 67 +++++++++++++++++++ .../eclipse/leshan/core/node/LwM2mObject.java | 1 + .../leshan/core/node/LwM2mObjectInstance.java | 11 +++ .../leshan/core/node/LwM2mSingleResource.java | 6 +- .../node/codec/json/LwM2mNodeJsonDecoder.java | 2 +- .../node/codec/tlv/LwM2mNodeTlvDecoder.java | 8 ++- .../core/node/codec/LwM2mNodeEncoderTest.java | 2 +- .../request/CoapRequestBuilder.java | 10 ++- 9 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mNodeUtil.java diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mMultipleResource.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mMultipleResource.java index f70dd5ff94..cbd98fb1e6 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mMultipleResource.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mMultipleResource.java @@ -43,6 +43,12 @@ public class LwM2mMultipleResource implements LwM2mResource { private final Type type; protected LwM2mMultipleResource(int id, Map values, Type type) { + Validate.notNull(values); + LwM2mNodeUtil.validateResourceId(id); + + for (Integer instanceId : values.keySet()) { + LwM2mNodeUtil.validateResourceInstanceId(instanceId); + } this.id = id; this.values = Collections.unmodifiableMap(new HashMap<>(values)); this.type = type; @@ -241,8 +247,8 @@ private boolean internalMapEquals(Map m1, Object o2) { return false; } else { // Custom equals to handle byte arrays - return type == Type.OPAQUE ? Arrays.equals((byte[]) value, (byte[]) m2.get(key)) : value.equals(m2 - .get(key)); + return type == Type.OPAQUE ? Arrays.equals((byte[]) value, (byte[]) m2.get(key)) + : value.equals(m2.get(key)); } } } catch (ClassCastException | NullPointerException unused) { 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 new file mode 100644 index 0000000000..d91404d13f --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mNodeUtil.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * 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; + +public class LwM2mNodeUtil { + + public static boolean isUnsignedInt(Integer id) { + return id != null && 0 <= id && id <= 65535; + } + + public static boolean isValidObjectId(Integer id) { + return isUnsignedInt(id); + } + + public static void validateObjectId(Integer id) { + if (!isValidObjectId(id)) { + throw new IllegalArgumentException(String.format("Invalid object id %d, It MUST be an unsigned int.", id)); + } + } + + public static boolean isValidObjectInstanceId(Integer id) { + // MAX_ID 65535 is a reserved value and MUST NOT be used for identifying an Object Instance. + return id != null && 0 <= id && id <= 65534; + } + + public static void validateObjectInstanceId(Integer id) { + if (!isValidObjectInstanceId(id)) { + throw new IllegalArgumentException(String + .format("Invalid object instance id %d, It MUST be an unsigned int. (65535 is reserved)", id)); + } + } + + public static boolean isValidResourceId(Integer id) { + return isUnsignedInt(id); + } + + public static void validateResourceId(Integer id) { + if (!isValidResourceId(id)) { + throw new IllegalArgumentException( + String.format("Invalid resource id %d, It MUST be an unsigned int.", id)); + } + } + + public static boolean isValidResourceInstanceId(Integer id) { + return isUnsignedInt(id); + } + + public static void validateResourceInstanceId(Integer id) { + if (!isValidResourceInstanceId(id)) { + throw new IllegalArgumentException( + String.format("Invalid resource instance id %d, It MUST be an unsigned int.", id)); + } + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mObject.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mObject.java index 6e76f8b440..57b1bc07f3 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mObject.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mObject.java @@ -37,6 +37,7 @@ public class LwM2mObject implements LwM2mNode { public LwM2mObject(int id, Collection instances) { Validate.notNull(instances); + LwM2mNodeUtil.validateObjectId(id); this.id = id; HashMap instancesMap = new HashMap<>(instances.size()); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mObjectInstance.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mObjectInstance.java index a31f8969db..182e8e318a 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mObjectInstance.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mObjectInstance.java @@ -35,8 +35,19 @@ public class LwM2mObjectInstance implements LwM2mNode { private final Map resources; + public LwM2mObjectInstance(Collection resources) { + Validate.notNull(resources); + this.id = UNDEFINED; + Map resourcesMap = new HashMap<>(resources.size()); + for (LwM2mResource resource : resources) { + resourcesMap.put(resource.getId(), resource); + } + this.resources = Collections.unmodifiableMap(resourcesMap); + } + public LwM2mObjectInstance(int id, Collection resources) { Validate.notNull(resources); + LwM2mNodeUtil.validateObjectInstanceId(id); this.id = id; Map resourcesMap = new HashMap<>(resources.size()); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mSingleResource.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mSingleResource.java index 907aa6bebd..6341646f04 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mSingleResource.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mSingleResource.java @@ -36,6 +36,8 @@ public class LwM2mSingleResource implements LwM2mResource { protected LwM2mSingleResource(int id, Object value, Type type) { Validate.notNull(value); + LwM2mNodeUtil.validateResourceId(id); + this.id = id; this.value = value; this.type = type; @@ -192,8 +194,8 @@ public boolean equals(Object obj) { return false; } else { // Custom equals to handle byte arrays - return type == Type.OPAQUE ? Arrays.equals((byte[]) value, (byte[]) other.value) : value - .equals(other.value); + return type == Type.OPAQUE ? Arrays.equals((byte[]) value, (byte[]) other.value) + : value.equals(other.value); } return true; } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java index f8da080b00..b4145a7095 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java @@ -64,7 +64,7 @@ public static T decode(byte[] content, LwM2mPath path, LwM // return the most recent value return (T) timestampedNodes.get(0).getNode(); } - } catch (LwM2mJsonException e) { + } catch (LwM2mJsonException | IllegalArgumentException e) { throw new CodecException(e, "Unable to deserialize json [path:%s]", path); } } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvDecoder.java index c17601782b..0b4a4974f2 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/tlv/LwM2mNodeTlvDecoder.java @@ -48,7 +48,7 @@ public static T decode(byte[] content, LwM2mPath path, LwM try { Tlv[] tlvs = TlvDecoder.decode(ByteBuffer.wrap(content != null ? content : new byte[0])); return parseTlv(tlvs, path, model, nodeClass); - } catch (TlvException e) { + } catch (TlvException | IllegalArgumentException e) { throw new CodecException(String.format("Unable to decode tlv for path [%s]", path), e); } } @@ -177,7 +177,11 @@ private static LwM2mObjectInstance parseObjectInstanceTlv(Tlv[] rscTlvs, int obj previousResource, resource, resource.getId(), resourcePath); } } - return new LwM2mObjectInstance(instanceId, resources.values()); + if (instanceId == LwM2mObjectInstance.UNDEFINED) { + return new LwM2mObjectInstance(resources.values()); + } else { + return new LwM2mObjectInstance(instanceId, resources.values()); + } } private static LwM2mResource parseResourceTlv(Tlv tlv, LwM2mPath resourcePath, LwM2mModel model) 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 06eb19c2fc..5de0fee71e 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 @@ -149,7 +149,7 @@ public void tlv_encode_device_object_instance_as_resources_array() { @Test public void tlv_encode_device_object_instance_as_resources_array__undefined_instance_id() { - LwM2mObjectInstance oInstance = new LwM2mObjectInstance(LwM2mObjectInstance.UNDEFINED, getDeviceResources()); + LwM2mObjectInstance oInstance = new LwM2mObjectInstance(getDeviceResources()); byte[] encoded = encoder.encode(oInstance, ContentFormat.TLV, new LwM2mPath("/3"), model); Assert.assertArrayEquals(ENCODED_DEVICE_WITHOUT_INSTANCE, encoded); diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilder.java index e41372db43..c37d4eb97f 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilder.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilder.java @@ -127,9 +127,13 @@ public void visit(CreateRequest request) { coapRequest = Request.newPost(); coapRequest.getOptions().setContentFormat(request.getContentFormat().getCode()); // if no instance id, the client will assign it. - int instanceId = request.getInstanceId() != null ? request.getInstanceId() : LwM2mObjectInstance.UNDEFINED; - coapRequest.setPayload(encoder.encode(new LwM2mObjectInstance(instanceId, request.getResources()), - request.getContentFormat(), request.getPath(), model)); + LwM2mObjectInstance instance; + if (request.getInstanceId() == null) { + instance = new LwM2mObjectInstance(request.getResources()); + } else { + instance = new LwM2mObjectInstance(request.getInstanceId(), request.getResources()); + } + coapRequest.setPayload(encoder.encode(instance, request.getContentFormat(), request.getPath(), model)); setTarget(coapRequest, request.getPath()); }