diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/JSONFileBootstrapStore.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/JSONFileBootstrapStore.java index c70492b387..91588644be 100644 --- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/JSONFileBootstrapStore.java +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/JSONFileBootstrapStore.java @@ -22,16 +22,19 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.lang.reflect.Type; +import java.util.EnumSet; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; +import org.eclipse.leshan.server.bootstrap.demo.json.BindingModeTypeAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,6 +72,9 @@ public JSONFileBootstrapStore(String filename) { Validate.notEmpty(filename); GsonBuilder builder = new GsonBuilder(); builder.setPrettyPrinting(); + builder.registerTypeAdapter(new TypeToken>() { + }.getType(), new BindingModeTypeAdapter()); + this.gson = builder.create(); this.gsonType = new TypeToken>() { }.getType(); diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/json/BindingModeTypeAdapter.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/json/BindingModeTypeAdapter.java new file mode 100644 index 0000000000..82f6aa8e3a --- /dev/null +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/json/BindingModeTypeAdapter.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2020 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.server.bootstrap.demo.json; + +import java.io.IOException; +import java.util.EnumSet; + +import org.eclipse.leshan.core.request.BindingMode; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +public class BindingModeTypeAdapter extends TypeAdapter> { + + @Override + public void write(JsonWriter out, EnumSet value) throws IOException { + out.value(BindingMode.toString(value)); + } + + @Override + public EnumSet read(JsonReader in) throws IOException { + return BindingMode.parse(in.nextString()); + } +} diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/BootstrapServlet.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/BootstrapServlet.java index e48c2f9ad0..a717d0328b 100644 --- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/BootstrapServlet.java +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/BootstrapServlet.java @@ -20,6 +20,7 @@ import java.io.InputStreamReader; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; +import java.util.EnumSet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -27,9 +28,11 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; +import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; +import org.eclipse.leshan.server.bootstrap.demo.json.BindingModeTypeAdapter; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -41,6 +44,7 @@ import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; /** * Servlet for REST API in charge of adding bootstrap information to the bootstrap server. @@ -70,8 +74,10 @@ public JsonElement serialize(Byte src, Type typeOfSrc, JsonSerializationContext public BootstrapServlet(EditableBootstrapConfigStore bsStore) { this.bsStore = bsStore; - this.gson = new GsonBuilder().registerTypeHierarchyAdapter(Byte.class, new SignedByteUnsignedByteAdapter()) - .create(); + this.gson = new GsonBuilder()// + .registerTypeAdapter(new TypeToken>() { + }.getType(), new BindingModeTypeAdapter()) // + .registerTypeHierarchyAdapter(Byte.class, new SignedByteUnsignedByteAdapter()).create(); } @Override diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java index 94860e8f61..be06ab0287 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java @@ -17,6 +17,7 @@ import java.net.InetSocketAddress; import java.security.cert.Certificate; +import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; @@ -160,6 +161,7 @@ public LeshanClientBuilder setDtlsConfig(DtlsConnectorConfig.Builder config) { /** * Set optional trust store for verifying X.509 server certificates. + * * @param trustStore List of trusted CA certificates */ public LeshanClientBuilder setTrustStore(List trustStore) { @@ -207,6 +209,7 @@ public LeshanClientBuilder setAdditionalAttributes(Map additiona /** * Set the additionalAttributes for {@link BootstrapRequest} + * * @since 1.1 */ public LeshanClientBuilder setBootstrapAdditionalAttributes(Map additionalAttributes) { @@ -253,8 +256,9 @@ public LeshanClient build() { ObjectsInitializer initializer = new ObjectsInitializer(); initializer.setInstancesForObject(LwM2mId.SECURITY, Security.noSec("coap://leshan.eclipseprojects.io:5683", 12345)); - initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, 5 * 60, BindingMode.U, false)); - initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", "model12345", "12345", "U")); + initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, 5 * 60)); + initializer.setInstancesForObject(LwM2mId.DEVICE, + new Device("Eclipse Leshan", "model12345", "12345", EnumSet.of(BindingMode.U))); objectEnablers = initializer.createAll(); } if (encoder == null) diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java index c1108de5a5..46ce6bdfd5 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.client.californium.request; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map.Entry; @@ -85,12 +86,20 @@ public void visit(RegisterRequest request) { if (lwVersion != null) attributes.put("lwm2m", lwVersion); - BindingMode bindingMode = request.getBindingMode(); + EnumSet bindingMode = request.getBindingMode(); if (bindingMode != null) - attributes.put("b", bindingMode.toString()); + attributes.put("b", BindingMode.toString(bindingMode)); + + Boolean queueMode = request.getQueueMode(); + if (queueMode != null && queueMode) + attributes.put("Q", null); for (Entry attr : attributes.entrySet()) { - coapRequest.getOptions().addUriQuery(attr.getKey() + "=" + attr.getValue()); + if (attr.getValue() != null) { + coapRequest.getOptions().addUriQuery(attr.getKey() + "=" + attr.getValue()); + } else { + coapRequest.getOptions().addUriQuery(attr.getKey()); + } } Link[] objectLinks = request.getObjectLinks(); @@ -113,9 +122,9 @@ public void visit(UpdateRequest request) { if (smsNumber != null) coapRequest.getOptions().addUriQuery("sms=" + smsNumber); - BindingMode bindingMode = request.getBindingMode(); + EnumSet bindingMode = request.getBindingMode(); if (bindingMode != null) - coapRequest.getOptions().addUriQuery("b=" + bindingMode.toString()); + coapRequest.getOptions().addUriQuery("b=" + BindingMode.toString(bindingMode)); Link[] linkObjects = request.getObjectLinks(); if (linkObjects != null) { diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdate.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdate.java index b2f11ef6ce..bc39503f44 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdate.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdate.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.client; +import java.util.EnumSet; import java.util.Map; import org.eclipse.leshan.core.Link; @@ -27,12 +28,12 @@ public class RegistrationUpdate { private final Long lifeTimeInSec; private final String smsNumber; - private final BindingMode bindingMode; + private final EnumSet bindingMode; private final Link[] objectLinks; private final Map additionalAttributes; - public RegistrationUpdate(Long lifeTimeInSec, String smsNumber, BindingMode bindingMode, Link[] objectLinks, - Map additionalAttributes) { + public RegistrationUpdate(Long lifeTimeInSec, String smsNumber, EnumSet bindingMode, + Link[] objectLinks, Map additionalAttributes) { this.lifeTimeInSec = lifeTimeInSec; this.smsNumber = smsNumber; this.bindingMode = bindingMode; @@ -52,7 +53,7 @@ public RegistrationUpdate(String smsNumber) { this(null, smsNumber, null, null, null); } - public RegistrationUpdate(BindingMode bindingMode) { + public RegistrationUpdate(EnumSet bindingMode) { this(null, null, bindingMode, null, null); } @@ -72,7 +73,7 @@ public String getSmsNumber() { return smsNumber; } - public BindingMode getBindingMode() { + public EnumSet getBindingMode() { return bindingMode; } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdateHandler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdateHandler.java index 3338f216a5..91e0d30b33 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdateHandler.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdateHandler.java @@ -15,6 +15,8 @@ *******************************************************************************/ package org.eclipse.leshan.client; +import java.util.EnumSet; + import org.eclipse.leshan.client.bootstrap.BootstrapHandler; import org.eclipse.leshan.client.engine.RegistrationEngine; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; @@ -74,27 +76,35 @@ public void objectAdded(LwM2mObjectEnabler object) { public void resourceChanged(LwM2mObjectEnabler object, int instanceId, int... resourceIds) { if (!bsHandler.isBootstrapping()) if (object.getId() == LwM2mId.SERVER) { - Long lifetime = null; - BindingMode bindingMode = null; + // handle lifetime changes for (int i = 0; i < resourceIds.length; i++) { if (resourceIds[i] == LwM2mId.SRV_LIFETIME) { - lifetime = ServersInfoExtractor.getLifeTime(object, instanceId); - } else if (resourceIds[i] == LwM2mId.SRV_BINDING) { - bindingMode = ServersInfoExtractor.getBindingMode(object, instanceId); + Long lifetime = ServersInfoExtractor.getLifeTime(object, instanceId); + Long serverId = ServersInfoExtractor.getServerId(object, instanceId); + if (lifetime != null && serverId != null) { + ServerIdentity server = engine.getRegisteredServer(serverId); + if (server != null) { + engine.triggerRegistrationUpdate(server, + new RegistrationUpdate(lifetime, null, null, null, null)); + return; + } + } } } - - if (bindingMode != null || lifetime != null) { - Long serverId = null; - serverId = ServersInfoExtractor.getServerId(object, instanceId); - if (serverId != null) { - ServerIdentity server = engine.getRegisteredServer(serverId); - if (server != null) - engine.triggerRegistrationUpdate(server, - new RegistrationUpdate(lifetime, null, bindingMode, null, null)); + } else if (object.getId() == LwM2mId.DEVICE) { + // handle supported binding changes + EnumSet bindingMode = null; + for (int i = 0; i < resourceIds.length; i++) { + if (resourceIds[i] == LwM2mId.DVC_SUPPORTED_BINDING) { + bindingMode = ServersInfoExtractor.getDeviceSupportedBindingMode(object, instanceId); } } + if (bindingMode != null) { + engine.triggerRegistrationUpdate( + new RegistrationUpdate(null, null, bindingMode, null, null)); + return; + } } } }); diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java index a83110cfc3..4c2bd3d25c 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java @@ -16,6 +16,7 @@ package org.eclipse.leshan.client.engine; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -41,7 +42,9 @@ import org.eclipse.leshan.client.servers.ServersInfoExtractor; import org.eclipse.leshan.client.util.LinkFormatHelper; import org.eclipse.leshan.core.LwM2m.Version; +import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.DeregisterRequest; import org.eclipse.leshan.core.request.Identity; @@ -88,6 +91,8 @@ public class DefaultRegistrationEngine implements RegistrationEngine { private boolean reconnectOnUpdate; // True if client should try to resume connection if possible. private boolean resumeOnConnect; + // True if client use queueMode : for now this just add Q parameter on register request. + private final boolean queueMode; private static enum Status { SUCCESS, FAILURE, TIMEOUT @@ -124,7 +129,7 @@ public DefaultRegistrationEngine(String endpoint, LwM2mObjectTree objectTree, En Integer communicationPeriodInMs, boolean reconnectOnUpdate, boolean resumeOnConnect) { this(endpoint, objectTree, endpointsManager, requestSender, bootstrapState, observer, additionalAttributes, null, executor, requestTimeoutInMs, deregistrationTimeoutInMs, bootstrapSessionTimeoutInSec, - retryWaitingTimeInMs, communicationPeriodInMs, reconnectOnUpdate, resumeOnConnect); + retryWaitingTimeInMs, communicationPeriodInMs, reconnectOnUpdate, resumeOnConnect, false); } /** @since 1.1 */ @@ -133,7 +138,7 @@ public DefaultRegistrationEngine(String endpoint, LwM2mObjectTree objectTree, En Map additionalAttributes, Map bsAdditionalAttributes, ScheduledExecutorService executor, long requestTimeoutInMs, long deregistrationTimeoutInMs, int bootstrapSessionTimeoutInSec, int retryWaitingTimeInMs, Integer communicationPeriodInMs, - boolean reconnectOnUpdate, boolean resumeOnConnect) { + boolean reconnectOnUpdate, boolean resumeOnConnect, boolean useQueueMode) { this.endpoint = endpoint; this.objectEnablers = objectTree.getObjectEnablers(); this.bootstrapHandler = bootstrapState; @@ -151,6 +156,7 @@ public DefaultRegistrationEngine(String endpoint, LwM2mObjectTree objectTree, En this.communicationPeriodInMs = communicationPeriodInMs; this.reconnectOnUpdate = reconnectOnUpdate; this.resumeOnConnect = resumeOnConnect; + this.queueMode = useQueueMode; if (executor == null) { schedExecutor = createScheduledExecutor(); @@ -298,8 +304,11 @@ private Status register(ServerIdentity server) throws InterruptedException { LOG.info("Trying to register to {} ...", server.getUri()); RegisterRequest request = null; try { - request = new RegisterRequest(endpoint, dmInfo.lifetime, Version.lastSupported().toString(), dmInfo.binding, - null, LinkFormatHelper.getClientDescription(objectEnablers.values(), null), additionalAttributes); + EnumSet supportedBindingMode = ServersInfoExtractor + .getDeviceSupportedBindingMode(objectEnablers.get(LwM2mId.DEVICE), 0); + request = new RegisterRequest(endpoint, dmInfo.lifetime, Version.lastSupported().toString(), + supportedBindingMode, queueMode, null, + LinkFormatHelper.getClientDescription(objectEnablers.values(), null), additionalAttributes); if (observer != null) { observer.onRegistrationStarted(server, request); } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java index 2b888b969c..5bc848339c 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java @@ -38,6 +38,7 @@ public class DefaultRegistrationEngineFactory implements RegistrationEngineFacto private Integer communicationPeriodInMs = null; private boolean reconnectOnUpdate = false; private boolean resumeOnConnect = true; + private boolean queueMode = false; public DefaultRegistrationEngineFactory() { } @@ -50,7 +51,7 @@ public RegistrationEngine createRegistratioEngine(String endpoint, LwM2mObjectTr return new DefaultRegistrationEngine(endpoint, objectTree, endpointsManager, requestSender, bootstrapState, observer, additionalAttributes, bsAdditionalAttributes, sharedExecutor, requestTimeoutInMs, deregistrationTimeoutInMs, bootstrapSessionTimeoutInSec, retryWaitingTimeInMs, communicationPeriodInMs, - reconnectOnUpdate, resumeOnConnect); + reconnectOnUpdate, resumeOnConnect, queueMode); } /** @@ -150,4 +151,17 @@ public DefaultRegistrationEngineFactory setResumeOnConnect(boolean resumeOnConne this.resumeOnConnect = resumeOnConnect; return this; } + + /** + * Configure client to use queueMode. + *

+ * Default value is false + * + * @param enable True if client must use queueMode + * @return this for fluent API + */ + public DefaultRegistrationEngineFactory setQueueMode(boolean enable) { + this.queueMode = enable; + return this; + } } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Device.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Device.java index 6066610096..f91c53c394 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Device.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Device.java @@ -20,6 +20,7 @@ import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.TimeZone; @@ -30,6 +31,7 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -44,7 +46,7 @@ public class Device extends BaseInstanceEnabler { private String manufacturer; private String modelNumber; private String serialNumber; - private String supportedBinding; + private EnumSet supportedBinding; private String timezone = TimeZone.getDefault().getID(); private String utcOffset = new SimpleDateFormat("X").format(Calendar.getInstance().getTime()); @@ -53,7 +55,14 @@ public Device() { // should never be used } - public Device(String manufacturer, String modelNumber, String serialNumber, String supportedBinding) { + public Device(String manufacturer, String modelNumber, String serialNumber) { + this.manufacturer = manufacturer; + this.modelNumber = modelNumber; + this.serialNumber = serialNumber; + this.supportedBinding = EnumSet.of(BindingMode.U); + } + + public Device(String manufacturer, String modelNumber, String serialNumber, EnumSet supportedBinding) { this.manufacturer = manufacturer; this.modelNumber = modelNumber; this.serialNumber = serialNumber; @@ -83,7 +92,7 @@ public ReadResponse read(ServerIdentity identity, int resourceid) { return ReadResponse.success(resourceid, timezone); case 16: // supported binding and modes - return ReadResponse.success(resourceid, supportedBinding); + return ReadResponse.success(resourceid, BindingMode.toString(supportedBinding)); default: return super.read(identity, resourceid); diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Server.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Server.java index d184b732df..ad7e96818b 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Server.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Server.java @@ -18,6 +18,7 @@ package org.eclipse.leshan.client.object; import java.util.Arrays; +import java.util.EnumSet; import java.util.List; import java.util.Objects; @@ -41,24 +42,31 @@ public class Server extends BaseInstanceEnabler { private static final Logger LOG = LoggerFactory.getLogger(Server.class); - private final static List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8); + private final static List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 22); private int shortServerId; private long lifetime; private Long defaultMinPeriod; private Long defaultMaxPeriod; - private BindingMode binding; + private EnumSet binding; + private BindingMode preferredTransport; private boolean notifyWhenDisable; public Server() { // should only be used at bootstrap time } - public Server(int shortServerId, long lifetime, BindingMode binding, boolean notifyWhenDisable) { + public Server(int shortServerId, long lifetime, EnumSet binding, boolean notifyWhenDisable, + BindingMode preferredTransport) { this.shortServerId = shortServerId; this.lifetime = lifetime; this.binding = binding; this.notifyWhenDisable = notifyWhenDisable; + this.preferredTransport = preferredTransport; + } + + public Server(int shortServerId, long lifetime) { + this(shortServerId, lifetime, EnumSet.of(BindingMode.U), false, BindingMode.U); } @Override @@ -87,7 +95,12 @@ public ReadResponse read(ServerIdentity identity, int resourceid) { return ReadResponse.success(resourceid, notifyWhenDisable); case 7: // binding - return ReadResponse.success(resourceid, binding.toString()); + return ReadResponse.success(resourceid, BindingMode.toString(binding)); + + case 22: // preferred transport + if (preferredTransport == null) + return ReadResponse.notFound(); + return ReadResponse.success(resourceid, preferredTransport.toString()); default: return super.read(identity, resourceid); @@ -155,14 +168,27 @@ public WriteResponse write(ServerIdentity identity, int resourceid, LwM2mResourc return WriteResponse.badRequest("invalid type"); } try { - BindingMode previousBinding = binding; - binding = BindingMode.valueOf((String) value.getValue()); + EnumSet previousBinding = binding; + binding = BindingMode.parse((String) value.getValue()); if (!Objects.equals(previousBinding, binding)) fireResourcesChange(resourceid); return WriteResponse.success(); } catch (IllegalArgumentException e) { return WriteResponse.badRequest("invalid value"); } + case 22: // preferredTransport + if (value.getType() != Type.STRING) { + return WriteResponse.badRequest("invalid type"); + } + try { + BindingMode previousPreferedTransport = preferredTransport; + preferredTransport = BindingMode.valueOf((String) value.getValue()); + if (!Objects.equals(previousPreferedTransport, preferredTransport)) + fireResourcesChange(resourceid); + return WriteResponse.success(); + } catch (IllegalArgumentException e) { + return WriteResponse.badRequest("invalid value"); + } default: return super.write(identity, resourceid, value); diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/DmServerInfo.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/DmServerInfo.java index e72de8c07b..5e939f03ee 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/DmServerInfo.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/DmServerInfo.java @@ -15,6 +15,8 @@ *******************************************************************************/ package org.eclipse.leshan.client.servers; +import java.util.EnumSet; + import org.eclipse.leshan.core.request.BindingMode; /** @@ -26,11 +28,12 @@ public class DmServerInfo extends ServerInfo { public long lifetime; - public BindingMode binding; + public EnumSet binding; // TODO add missing information like SMS number @Override public String toString() { - return String.format("DM Server [uri=%s, lifetime=%s, binding=%s]", serverUri, lifetime, binding); + return String.format("DM Server [uri=%s, lifetime=%s, binding=%s]", serverUri, lifetime, + BindingMode.toString(binding)); } } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java index dca257d27a..6383f83508 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java @@ -33,6 +33,7 @@ import java.security.cert.CertificateFactory; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; +import java.util.EnumSet; import java.util.Map; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; @@ -119,7 +120,7 @@ public static ServersInfo getInfo(Map objectEnabler for (LwM2mObjectInstance server : servers.getInstances().values()) { if (info.serverId == (Long) server.getResource(SRV_SERVER_ID).getValue()) { info.lifetime = (long) server.getResource(SRV_LIFETIME).getValue(); - info.binding = BindingMode.valueOf((String) server.getResource(SRV_BINDING).getValue()); + info.binding = BindingMode.parse((String) server.getResource(SRV_BINDING).getValue()); infos.deviceManagements.put(info.serverId, info); break; @@ -159,11 +160,21 @@ public static Long getLifeTime(LwM2mObjectEnabler serverEnabler, int instanceId) } } - public static BindingMode getBindingMode(LwM2mObjectEnabler serverEnabler, int instanceId) { + public static EnumSet getServerBindingMode(LwM2mObjectEnabler serverEnabler, int instanceId) { ReadResponse response = serverEnabler.read(ServerIdentity.SYSTEM, new ReadRequest(SERVER, instanceId, SRV_BINDING)); if (response.isSuccess()) { - return BindingMode.valueOf((String) ((LwM2mResource) response.getContent()).getValue()); + return BindingMode.parse((String) ((LwM2mResource) response.getContent()).getValue()); + } else { + return null; + } + } + + public static EnumSet getDeviceSupportedBindingMode(LwM2mObjectEnabler serverEnabler, int instanceId) { + ReadResponse response = serverEnabler.read(ServerIdentity.SYSTEM, + new ReadRequest(DEVICE, instanceId, DVC_SUPPORTED_BINDING)); + if (response.isSuccess()) { + return BindingMode.parse((String) ((LwM2mResource) response.getContent()).getValue()); } else { return null; } 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 6d52a1afa2..2f36a43054 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 @@ -34,7 +34,6 @@ import org.eclipse.leshan.core.LwM2m.Version; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.ContentFormat; import org.junit.Test; @@ -189,7 +188,7 @@ public void encode_bootstrap_object_with_version_and_instance() { @Test public void encode_bootstrap_server_object() { Map instancesMap = new HashMap<>(); - instancesMap.put(0, new Server(333, 120, BindingMode.UQ, false)); + instancesMap.put(0, new Server(333, 120)); ObjectEnabler objectEnabler = new ObjectEnabler(1, getObjectModel(1), instancesMap, null, ContentFormat.DEFAULT); @@ -202,7 +201,7 @@ public void encode_bootstrap_server_object() { @Test public void encode_bootstrap_server_object_with_version() { Map instancesMap = new HashMap<>(); - instancesMap.put(0, new Server(333, 120, BindingMode.UQ, false)); + instancesMap.put(0, new Server(333, 120)); ObjectEnabler objectEnabler = new ObjectEnabler(1, getVersionedObjectModel(1, "2.0"), instancesMap, null, ContentFormat.DEFAULT); @@ -245,7 +244,7 @@ public void encode_bootstrap_root() { // object 1 Map serverInstances = new HashMap<>(); - serverInstances.put(0, new Server(333, 120, BindingMode.UQ, false)); + serverInstances.put(0, new Server(333, 120)); ObjectEnabler serverObjectEnabler = new ObjectEnabler(1, getVersionedObjectModel(1, "2.0"), serverInstances, null, ContentFormat.DEFAULT); objectEnablers.add(serverObjectEnabler); diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java index 1521a5f3cc..a3c2061238 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java @@ -70,7 +70,6 @@ import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; -import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.SecurityUtil; import org.slf4j.Logger; @@ -610,18 +609,18 @@ public static void createAndStartClient(String endpoint, String localAddress, in } else { if (pskIdentity != null) { initializer.setInstancesForObject(SECURITY, psk(serverURI, 123, pskIdentity, pskKey)); - initializer.setInstancesForObject(SERVER, new Server(123, lifetime, BindingMode.U, false)); + initializer.setInstancesForObject(SERVER, new Server(123, lifetime)); } else if (clientPublicKey != null) { initializer.setInstancesForObject(SECURITY, rpk(serverURI, 123, clientPublicKey.getEncoded(), clientPrivateKey.getEncoded(), serverPublicKey.getEncoded())); - initializer.setInstancesForObject(SERVER, new Server(123, lifetime, BindingMode.U, false)); + initializer.setInstancesForObject(SERVER, new Server(123, lifetime)); } else if (clientCertificate != null) { initializer.setInstancesForObject(SECURITY, x509(serverURI, 123, clientCertificate.getEncoded(), clientPrivateKey.getEncoded(), serverCertificate.getEncoded())); - initializer.setInstancesForObject(SERVER, new Server(123, lifetime, BindingMode.U, false)); + initializer.setInstancesForObject(SERVER, new Server(123, lifetime)); } else { initializer.setInstancesForObject(SECURITY, noSec(serverURI, 123)); - initializer.setInstancesForObject(SERVER, new Server(123, lifetime, BindingMode.U, false)); + initializer.setInstancesForObject(SERVER, new Server(123, lifetime)); } } initializer.setInstancesForObject(DEVICE, new MyDevice()); diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyDevice.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyDevice.java index 5453fa05ec..207649d7a8 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyDevice.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyDevice.java @@ -44,7 +44,8 @@ public void run() { @Override public ReadResponse read(ServerIdentity identity, int resourceid) { - LOG.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); + if (!identity.isSystem()) + LOG.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); switch (resourceid) { case 0: return ReadResponse.success(resourceid, getManufacturer()); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java index ecce03a5d7..3ad78c12a1 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java @@ -60,7 +60,7 @@ public static String validate(String version) { return "version MUST NOT be null or empty"; String[] versionPart = version.split("\\."); if (versionPart.length != 2) { - throw new IllegalArgumentException(String.format("version (%s) MUST be composed of 2 parts", version)); + return String.format("version (%s) MUST be composed of 2 parts", version); } for (int i = 0; i < 2; i++) { try { @@ -130,6 +130,10 @@ public boolean newerThan(Version version) { return this.compareTo(version) > 0; } + public boolean olderThan(Version version) { + return this.compareTo(version) < 0; + } + public boolean newerThan(String version) { return newerThan(Version.get(version)); } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java index 24a71737c4..392df7d8f6 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java @@ -47,4 +47,8 @@ public interface LwM2mId { public static final int SRV_SERVER_ID = 0; public static final int SRV_LIFETIME = 1; public static final int SRV_BINDING = 7; + + /* DEVICE RESOURCES */ + + public static final int DVC_SUPPORTED_BINDING = 16; } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/BindingMode.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BindingMode.java index dba777c04b..273636e3f3 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/BindingMode.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BindingMode.java @@ -15,6 +15,10 @@ *******************************************************************************/ package org.eclipse.leshan.core.request; +import java.util.EnumSet; + +import org.eclipse.leshan.core.LwM2m.Version; + /** * Transport binding and Queue Mode */ @@ -23,30 +27,85 @@ public enum BindingMode { /** UDP */ U, - /** UDP with Queue Mode */ - UQ, + /** TCP */ + T, /** SMS */ S, - /** SMS with Queue Mode */ - SQ, + /** Non-Ip */ + N, + + /** Queue Mode : removed since LWM2M 1.1 */ + Q; - /** UDP and SMS */ - US, + /** + * @param targetVersion the target LWM2M version + * @return null if the BindingMode value is compatible with the given LWM2M version, else return an error message. + */ + public String isValidFor(Version targetVersion) { + switch (this) { + case T: + case N: + if (targetVersion.olderThan(Version.V1_1)) { + return String.format("%s is supported since LWM2M 1.1", this); + } + break; + case Q: + if (targetVersion.newerThan(Version.V1_0)) { + return String.format("%s is not supported since LWM2M 1.1", this); + } + break; + default: + } + return null; + } + + private static BindingMode valueOf(char c) { + switch (c) { + case 'U': + return U; + case 'T': + return T; + case 'S': + return S; + case 'N': + return N; + case 'Q': + return Q; + default: + throw new IllegalArgumentException("No enum constant " + c + "."); - /** UDP with Queue Mode and SMS */ - UQS; + } + } - public boolean useSMS() { - return equals(S) || equals(SQ) || equals(UQS); + /** + * @param bindings bindings to check + * @param targetVersion the target LWM2M version + * @return null if the bindings are compatible with the given LWM2M version, else return an error message. + */ + public static String isValidFor(EnumSet bindings, Version targetVersion) { + for (BindingMode binding : bindings) { + String err = binding.isValidFor(targetVersion); + if (err != null) + return err; + } + return null; } - public boolean useQueueMode() { - return equals(UQ) || equals(SQ) || equals(UQS); + public static String toString(EnumSet bindings) { + StringBuilder b = new StringBuilder(); + for (BindingMode binding : bindings) { + b.append(binding); + } + return b.toString(); } - public boolean useUDP() { - return equals(U) || equals(UQ) || equals(UQS); + public static EnumSet parse(String bindings) { + EnumSet res = EnumSet.noneOf(BindingMode.class); + for (int i = 0; i < bindings.length(); i++) { + res.add(BindingMode.valueOf(bindings.charAt(i))); + } + return res; } } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java index cf980d9850..c4aaa77b10 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java @@ -17,10 +17,12 @@ import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.LwM2m.Version; import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.response.RegisterResponse; @@ -33,7 +35,8 @@ public class RegisterRequest implements UplinkRequest { private final String endpointName; private final Long lifetime; private final String lwVersion; - private final BindingMode bindingMode; + private final EnumSet bindingMode; + private final Boolean queueMode; // since LWM2M 1.1 private final String smsNumber; private final Link[] objectLinks; private final Map additionalAttributes; @@ -44,14 +47,15 @@ public class RegisterRequest implements UplinkRequest { * @param endpointName is the LWM2M client identifier. * @param lifetime specifies the lifetime of the registration in seconds. * @param lwVersion indicates the version of the LWM2M Enabler that the LWM2M Client supports. - * @param bindingMode indicates current {@link BindingMode} of the LWM2M Client. + * @param bindingMode Indicates the supported {@link BindingMode}s in the LwM2M Client. + * @param queueMode Indicates whether Queue Mode is supported. * @param smsNumber is the MSISDN where the LWM2M Client can be reached for use with the SMS binding. * @param objectLinks is the list of Objects supported and Object Instances available on the LWM2M Client. * @param additionalAttributes are any attributes/parameters which is out of the LWM2M specification. * @exception InvalidRequestException if endpoint name or objectlinks is empty. */ - public RegisterRequest(String endpointName, Long lifetime, String lwVersion, BindingMode bindingMode, - String smsNumber, Link[] objectLinks, Map additionalAttributes) + public RegisterRequest(String endpointName, Long lifetime, String lwVersion, EnumSet bindingMode, + Boolean queueMode, String smsNumber, Link[] objectLinks, Map additionalAttributes) throws InvalidRequestException { if (endpointName == null || endpointName.isEmpty()) @@ -62,6 +66,29 @@ public RegisterRequest(String endpointName, Long lifetime, String lwVersion, Bin "supported object list is mandatory and mandatory objects should be present for endpoint %s", endpointName); + String err = Version.validate(lwVersion); + if (err != null) { + throw new InvalidRequestException("Invalid LWM2M version: %s", err); + } + + if (bindingMode != null) { + err = BindingMode.isValidFor(bindingMode, Version.get(lwVersion)); + if (err != null) { + throw new InvalidRequestException("Invalid Binding mode: %s", err); + } + } + + // handle queue mode param + Version version = Version.get(lwVersion); + if (version.equals(Version.V1_0)) { + if (queueMode != null) + throw new InvalidRequestException("QueueMode is not defined in LWM2M v1.0"); + else + this.queueMode = null; + } else { + this.queueMode = queueMode == null ? false : queueMode; + } + this.endpointName = endpointName; this.lifetime = lifetime; this.lwVersion = lwVersion; @@ -86,7 +113,7 @@ public String getLwVersion() { return lwVersion; } - public BindingMode getBindingMode() { + public EnumSet getBindingMode() { return bindingMode; } @@ -98,6 +125,10 @@ public Link[] getObjectLinks() { return objectLinks; } + public Boolean getQueueMode() { + return queueMode; + } + public Map getAdditionalAttributes() { return additionalAttributes; } @@ -110,8 +141,8 @@ public void accept(UplinkRequestVisitor visitor) { @Override public String toString() { return String.format( - "RegisterRequest [endpointName=%s, lifetime=%s, lwVersion=%s, bindingMode=%s, smsNumber=%s, objectLinks=%s, additionalAttributes=%s]", - endpointName, lifetime, lwVersion, bindingMode, smsNumber, Arrays.toString(objectLinks), + "RegisterRequest [endpointName=%s, lifetime=%s, lwVersion=%s, bindingMode=%s, queueMode=%s, smsNumber=%s, objectLinks=%s, additionalAttributes=%s]", + endpointName, lifetime, lwVersion, bindingMode, queueMode, smsNumber, Arrays.toString(objectLinks), additionalAttributes); } @@ -125,6 +156,7 @@ public int hashCode() { result = prime * result + ((lifetime == null) ? 0 : lifetime.hashCode()); result = prime * result + ((lwVersion == null) ? 0 : lwVersion.hashCode()); result = prime * result + Arrays.hashCode(objectLinks); + result = prime * result + ((queueMode == null) ? 0 : queueMode.hashCode()); result = prime * result + ((smsNumber == null) ? 0 : smsNumber.hashCode()); return result; } @@ -162,6 +194,11 @@ public boolean equals(Object obj) { return false; if (!Arrays.equals(objectLinks, other.objectLinks)) return false; + if (queueMode == null) { + if (other.queueMode != null) + return false; + } else if (!queueMode.equals(other.queueMode)) + return false; if (smsNumber == null) { if (other.smsNumber != null) return false; diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java index ade346a1a4..4a6a771910 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java @@ -17,10 +17,12 @@ import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.LwM2m.Version; import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.response.UpdateResponse; @@ -32,7 +34,7 @@ public class UpdateRequest implements UplinkRequest { private final Long lifeTimeInSec; private final String smsNumber; - private final BindingMode bindingMode; + private final EnumSet bindingMode; private final String registrationId; private final Link[] objectLinks; private final Map additionalAttributes; @@ -47,7 +49,7 @@ public class UpdateRequest implements UplinkRequest { * @param objectLinks the objects and object instances the client hosts/supports * @exception InvalidRequestException if the registrationId is empty. */ - public UpdateRequest(String registrationId, Long lifetime, String smsNumber, BindingMode binding, + public UpdateRequest(String registrationId, Long lifetime, String smsNumber, EnumSet binding, Link[] objectLinks, Map additionalAttributes) throws InvalidRequestException { if (registrationId == null || registrationId.isEmpty()) @@ -80,7 +82,7 @@ public String getSmsNumber() { return smsNumber; } - public BindingMode getBindingMode() { + public EnumSet getBindingMode() { return bindingMode; } @@ -88,6 +90,15 @@ public Map getAdditionalAttributes() { return additionalAttributes; } + public void validate(String targetedVersion) { + if (bindingMode != null) { + String err = BindingMode.isValidFor(bindingMode, Version.get(targetedVersion)); + if (err != null) { + throw new InvalidRequestException("Invalid Binding mode: %s", err); + } + } + } + @Override public void accept(UplinkRequestVisitor visitor) { visitor.visit(this); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java index bbe1f17567..0069c7c26e 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java @@ -234,7 +234,7 @@ public void createClient(Security security, ObjectsInitializer initializer, // Initialize LWM2M Object Tree initializer.setInstancesForObject(LwM2mId.SECURITY, security); initializer.setInstancesForObject(LwM2mId.DEVICE, - new Device("Eclipse Leshan", IntegrationTestHelper.MODEL_NUMBER, "12345", "U")); + new Device("Eclipse Leshan", IntegrationTestHelper.MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.SERVER, DummyInstanceEnabler.class); createClient(initializer, additionalAttributes); } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java index abdc288157..8def4b2972 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java @@ -32,6 +32,7 @@ import org.eclipse.leshan.client.californium.LeshanClientBuilder; import org.eclipse.leshan.client.object.Device; import org.eclipse.leshan.client.object.Security; +import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; @@ -46,7 +47,6 @@ import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; -import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; @@ -151,8 +151,8 @@ public TestDevice() { super(); } - public TestDevice(String manufacturer, String modelNumber, String serialNumber, String supportedBinding) { - super(manufacturer, modelNumber, serialNumber, supportedBinding); + public TestDevice(String manufacturer, String modelNumber, String serialNumber) { + super(manufacturer, modelNumber, serialNumber); } @Override @@ -171,9 +171,8 @@ public void createClient(Map additionalAttributes) { initializer.setInstancesForObject(LwM2mId.SECURITY, Security.noSec( "coap://" + server.getUnsecuredAddress().getHostString() + ":" + server.getUnsecuredAddress().getPort(), 12345)); - initializer.setInstancesForObject(LwM2mId.SERVER, - new org.eclipse.leshan.client.object.Server(12345, LIFETIME, BindingMode.U, false)); - initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345", "U")); + initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); + initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); initializer.setInstancesForObject(TEST_OBJECT_ID, new DummyInstanceEnabler(0), new SimpleInstanceEnabler(1, OPAQUE_RESOURCE_ID, new byte[0])); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java index 28afe5805e..3fedf1c54e 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java @@ -18,7 +18,9 @@ import java.net.InetSocketAddress; import java.util.Random; +import org.eclipse.californium.core.coap.Message; import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.network.serialization.UdpDataSerializer; import org.eclipse.californium.core.test.lockstep.LockstepEndpoint; import org.eclipse.californium.elements.RawData; @@ -37,7 +39,7 @@ public LockStepLwM2mClient(final InetSocketAddress destination) { this.destination = destination; } - public void sendLwM2mRequest(UplinkRequest lwm2mReq) { + public Request createCoapRequest(UplinkRequest lwm2mReq) { // create CoAP request CoapRequestBuilder coapRequestBuilder = new CoapRequestBuilder(Identity.unsecure(destination)); lwm2mReq.accept(coapRequestBuilder); @@ -45,12 +47,21 @@ public void sendLwM2mRequest(UplinkRequest lwm2mReq) { byte[] token = new byte[8]; r.nextBytes(token); coapReq.setToken(token); + coapReq.setMID(r.nextInt(Message.MAX_MID)); + return coapReq; + } + + public Token sendLwM2mRequest(UplinkRequest lwm2mReq) { + return sendCoapRequest(createCoapRequest(lwm2mReq)); + } + public Token sendCoapRequest(Request coapReq) { // serialize request UdpDataSerializer serializer = new UdpDataSerializer(); RawData raw = serializer.serializeRequest(coapReq); // send it super.send(raw); + return coapReq.getToken(); } } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/FailingTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepTest.java similarity index 51% rename from leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/FailingTest.java rename to leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepTest.java index e91ac85aa4..397cffe86a 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/FailingTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepTest.java @@ -17,17 +17,22 @@ import static org.junit.Assert.assertEquals; +import java.util.EnumSet; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.coap.CoAP.Type; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.RegisterRequest; +import org.eclipse.leshan.core.request.UpdateRequest; import org.eclipse.leshan.core.request.exception.TimeoutException; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.californium.LeshanServerBuilder; @@ -36,7 +41,7 @@ import org.junit.Before; import org.junit.Test; -public class FailingTest { +public class LockStepTest { public IntegrationTestHelper helper = new IntegrationTestHelper() { @Override @@ -67,13 +72,97 @@ public void stop() { helper.dispose(); } + @Test + public void register_with_uq_binding_in_lw_1_0() throws Exception { + // Register client + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + Token token = client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.0", + EnumSet.of(BindingMode.U, BindingMode.Q), null, null, Link.parse(",,".getBytes()), null)); + client.expectResponse().token(token).code(ResponseCode.CREATED).go(); + helper.waitForRegistrationAtServerSide(1); + } + + @Test + public void register_with_ut_binding_in_lw_1_1() throws Exception { + // Register client + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + Token token = client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", + EnumSet.of(BindingMode.U, BindingMode.T), null, null, Link.parse(",,".getBytes()), null)); + client.expectResponse().token(token).code(ResponseCode.CREATED).go(); + helper.waitForRegistrationAtServerSide(1); + } + + @Test + public void register_update_with_invalid_binding_for_lw_1_1() throws Exception { + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + + // register with valid binding for 1.1 + RegisterRequest validRegisterRequest = new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", + EnumSet.of(BindingMode.U), null, null, Link.parse(",,".getBytes()), null); + Token token = client.sendLwM2mRequest(validRegisterRequest); + client.expectResponse().token(token).code(ResponseCode.CREATED).go(); + helper.waitForRegistrationAtServerSide(1); + + // update with valid binding for 1.1 + UpdateRequest validUpdateRequest = new UpdateRequest("/rd/" + helper.getLastRegistration().getId(), 60l, null, + EnumSet.of(BindingMode.U), null, null); + token = client.sendLwM2mRequest(validUpdateRequest); + client.expectResponse().token(token).code(ResponseCode.CHANGED).go(); + + // register with invalid binding for 1.1 + Request invalidRegisterRequest = client.createCoapRequest(validRegisterRequest); + invalidRegisterRequest.getOptions().removeUriQuery("b=U"); + invalidRegisterRequest.getOptions().addUriQuery("b=UQ"); + token = client.sendCoapRequest(invalidRegisterRequest); + client.expectResponse().token(token).code(ResponseCode.BAD_REQUEST).go(); + + // update with invalid binding for 1.1 + Request invalidUpdateRequest = client.createCoapRequest(validRegisterRequest); + invalidUpdateRequest.getOptions().removeUriQuery("b=U"); + invalidUpdateRequest.getOptions().addUriQuery("b=UQ"); + token = client.sendCoapRequest(invalidUpdateRequest); + client.expectResponse().token(token).code(ResponseCode.BAD_REQUEST).go(); + } + + @Test + public void register_update_with_invalid_binding_for_lw_1_0() throws Exception { + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + + // register with valid binding for 1.0 + RegisterRequest validRegisterRequest = new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.0", + EnumSet.of(BindingMode.U), null, null, Link.parse(",,".getBytes()), null); + Token token = client.sendLwM2mRequest(validRegisterRequest); + client.expectResponse().token(token).code(ResponseCode.CREATED).go(); + helper.waitForRegistrationAtServerSide(1); + + // update with valid binding for 1.0 + UpdateRequest validUpdateRequest = new UpdateRequest("/rd/" + helper.getLastRegistration().getId(), 60l, null, + EnumSet.of(BindingMode.U), null, null); + token = client.sendLwM2mRequest(validUpdateRequest); + client.expectResponse().token(token).code(ResponseCode.CHANGED).go(); + + // register with invalid binding for 1.0 + Request invalidRegisterRequest = client.createCoapRequest(validRegisterRequest); + invalidRegisterRequest.getOptions().removeUriQuery("b=U"); + invalidRegisterRequest.getOptions().addUriQuery("b=UT"); + token = client.sendCoapRequest(invalidRegisterRequest); + client.expectResponse().token(token).code(ResponseCode.BAD_REQUEST).go(); + + // update with invalid binding for 1.0 + Request invalidUpdateRequest = client.createCoapRequest(validRegisterRequest); + invalidUpdateRequest.getOptions().removeUriQuery("b=U"); + invalidUpdateRequest.getOptions().addUriQuery("b=UT"); + token = client.sendCoapRequest(invalidUpdateRequest); + client.expectResponse().token(token).code(ResponseCode.BAD_REQUEST).go(); + } + @Test public void sync_send_without_acknowleged() throws Exception { // Register client LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); - client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, null, BindingMode.U, null, - Link.parse(",,".getBytes()), null)); - client.expectResponse().go(); + Token token = client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", + EnumSet.of(BindingMode.U), null, null, Link.parse(",,".getBytes()), null)); + client.expectResponse().token(token).go(); helper.waitForRegistrationAtServerSide(1); // Send read @@ -93,9 +182,9 @@ public ReadResponse call() throws Exception { public void sync_send_with_acknowleged_request_without_response() throws Exception { // Register client LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); - client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, null, BindingMode.U, null, - Link.parse(",,".getBytes()), null)); - client.expectResponse().go(); + Token token = client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", + EnumSet.of(BindingMode.U), null, null, Link.parse(",,".getBytes()), null)); + client.expectResponse().token(token).go(); helper.waitForRegistrationAtServerSide(1); // Send read @@ -122,9 +211,9 @@ public ReadResponse call() throws Exception { public void async_send_without_acknowleged() throws Exception { // register client LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); - client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, null, BindingMode.U, null, - Link.parse(",,".getBytes()), null)); - client.expectResponse().go(); + Token token = client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", + EnumSet.of(BindingMode.U), null, null, Link.parse(",,".getBytes()), null)); + client.expectResponse().token(token).go(); helper.waitForRegistrationAtServerSide(1); // send read @@ -141,9 +230,9 @@ public void async_send_without_acknowleged() throws Exception { public void async_send_with_acknowleged_request_without_response() throws Exception { // register client LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); - client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, null, BindingMode.U, null, - Link.parse(",,".getBytes()), null)); - client.expectResponse().go(); + Token token = client.sendLwM2mRequest(new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", + EnumSet.of(BindingMode.U), null, null, Link.parse(",,".getBytes()), null)); + client.expectResponse().token(token).go(); helper.waitForRegistrationAtServerSide(1); // send read diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeIntegrationTestHelper.java index e3bf3b3f2e..08133ab9b1 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeIntegrationTestHelper.java @@ -23,6 +23,7 @@ import java.util.concurrent.TimeoutException; import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.resource.DummyInstanceEnabler; @@ -30,7 +31,6 @@ import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.model.StaticModel; -import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.queue.StaticClientAwakeTimeProvider; @@ -65,15 +65,15 @@ public void createClient() { initializer.setInstancesForObject(LwM2mId.SECURITY, Security.noSec( "coap://" + server.getUnsecuredAddress().getHostString() + ":" + server.getUnsecuredAddress().getPort(), 12345)); - initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME, BindingMode.UQ, false)); - initializer.setInstancesForObject(LwM2mId.DEVICE, - new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345", "UQ")); + initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); + initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); initializer.setDummyInstancesForObject(2000); List objects = initializer.createAll(); // Build Client LeshanClientBuilder builder = new LeshanClientBuilder(currentEndpointIdentifier.get()); + builder.setRegistrationEngineFactory(new DefaultRegistrationEngineFactory().setQueueMode(true)); builder.setObjects(objects); client = builder.build(); setupClientMonitoring(); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java index 23ef0113e5..a316560e41 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java @@ -52,6 +52,7 @@ import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; import org.eclipse.californium.scandium.dtls.pskstore.PskStore; import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Device; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; @@ -60,7 +61,6 @@ import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.californium.EndpointFactory; -import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.X509CertUtil; import org.eclipse.leshan.server.californium.LeshanServerBuilder; @@ -205,14 +205,14 @@ public void createPSKClient(boolean queueMode) { "coaps://" + server.getSecuredAddress().getHostString() + ":" + server.getSecuredAddress().getPort(), 12345, GOOD_PSK_ID.getBytes(StandardCharsets.UTF_8), GOOD_PSK_KEY)); - initializer.setInstancesForObject(LwM2mId.SERVER, - new Server(12345, LIFETIME, queueMode ? BindingMode.UQ : BindingMode.U, false)); - initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345", "U")); + initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); + initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setDummyInstancesForObject(LwM2mId.ACCESS_CONTROL); List objects = initializer.createAll(); InetSocketAddress clientAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); LeshanClientBuilder builder = new LeshanClientBuilder(getCurrentEndpoint()); + builder.setRegistrationEngineFactory(new DefaultRegistrationEngineFactory().setQueueMode(queueMode)); builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort()); builder.setObjects(objects); builder.setDtlsConfig( @@ -269,8 +269,8 @@ public void createRPKClient(boolean useServerCertificate) { "coaps://" + server.getSecuredAddress().getHostString() + ":" + server.getSecuredAddress().getPort(), 12345, clientPublicKey.getEncoded(), clientPrivateKey.getEncoded(), useServerCertificate ? serverX509Cert.getPublicKey().getEncoded() : serverPublicKey.getEncoded())); - initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME, BindingMode.U, false)); - initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345", "U")); + initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); + initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); List objects = initializer.createAll(); @@ -314,8 +314,8 @@ public void createX509CertClient(Certificate clientCertificate, PrivateKey priva + server.getSecuredAddress().getPort(), 12345, clientCertificate.getEncoded(), privatekey.getEncoded(), serverCertificate.getEncoded())); - initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME, BindingMode.U, false)); - initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345", "U")); + initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); + initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); List objects = initializer.createAll(); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SynchronousRegistrationListener.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SynchronousRegistrationListener.java index 5db6304cc8..16df2b644c 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SynchronousRegistrationListener.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SynchronousRegistrationListener.java @@ -34,6 +34,8 @@ public class SynchronousRegistrationListener implements RegistrationListener { @Override public void registered(Registration reg, Registration previousReg, Collection previousObsersations) { + System.out.println(reg); + if (accept(reg)) { lastRegistration = reg; registerLatch.countDown(); @@ -42,6 +44,7 @@ public void registered(Registration reg, Registration previousReg, Collection observations, boolean expired, Registration newReg) { + System.out.println(reg); + if (accept(reg)) deregisterLatch.countDown(); } diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java index 818c2b87fb..d89b80e8c7 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java @@ -17,6 +17,7 @@ import static org.eclipse.leshan.core.californium.ResponseCodeUtil.toCoapResponseCode; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -61,6 +62,8 @@ public class RegisterResource extends LwM2mCoapResource { private static final String QUERY_PARAM_LIFETIME = "lt="; + private static final String QUERY_PARAM_QUEUEMMODE = "Q"; // since LWM2M 1.1 + private static final Logger LOG = LoggerFactory.getLogger(RegisterResource.class); public static final String RESOURCE_NAME = "rd"; @@ -128,7 +131,8 @@ protected void handleRegister(CoapExchange exchange, Request request) { Long lifetime = null; String smsNumber = null; String lwVersion = null; - BindingMode binding = null; + EnumSet binding = null; + Boolean queueMode = null; // Get object Links Link[] objectLinks = Link.parse(request.getPayload()); @@ -146,7 +150,9 @@ protected void handleRegister(CoapExchange exchange, Request request) { } else if (param.startsWith(QUERY_PARAM_LWM2M_VERSION)) { lwVersion = param.substring(6); } else if (param.startsWith(QUERY_PARAM_BINDING_MODE)) { - binding = BindingMode.valueOf(param.substring(2)); + binding = BindingMode.parse(param.substring(2)); + } else if (param.equals(QUERY_PARAM_QUEUEMMODE)) { + queueMode = true; } else { String[] tokens = param.split("\\="); if (tokens != null && tokens.length == 2) { @@ -156,8 +162,8 @@ protected void handleRegister(CoapExchange exchange, Request request) { } // Create request - RegisterRequest registerRequest = new RegisterRequest(endpoint, lifetime, lwVersion, binding, smsNumber, - objectLinks, additionalParams); + RegisterRequest registerRequest = new RegisterRequest(endpoint, lifetime, lwVersion, binding, queueMode, + smsNumber, objectLinks, additionalParams); // Handle request // ------------------------------- @@ -183,7 +189,7 @@ protected void handleUpdate(CoapExchange exchange, Request request, String regis // Create LwM2m request from CoAP request Long lifetime = null; String smsNumber = null; - BindingMode binding = null; + EnumSet binding = null; Link[] objectLinks = null; Map additionalParams = new HashMap<>(); @@ -193,7 +199,7 @@ protected void handleUpdate(CoapExchange exchange, Request request, String regis } else if (param.startsWith(QUERY_PARAM_SMS)) { smsNumber = param.substring(4); } else if (param.startsWith(QUERY_PARAM_BINDING_MODE)) { - binding = BindingMode.valueOf(param.substring(2)); + binding = BindingMode.parse(param.substring(2)); } else { String[] tokens = param.split("\\="); if (tokens != null && tokens.length == 2) { diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java index c0740d1866..3ca66b62d8 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import java.net.InetSocketAddress; +import java.util.EnumSet; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.Identity; @@ -82,7 +83,7 @@ public void testStartStopDestroy() throws InterruptedException { private void forceThreadsCreation(LeshanServer server) { Registration reg = new Registration.Builder("id", "endpoint", Identity.unsecure(new InetSocketAddress(5555))) - .bindingMode(BindingMode.UQ).build(); + .bindingMode(EnumSet.of(BindingMode.U, BindingMode.Q)).build(); // Force timer thread creation of preference service. ((PresenceServiceImpl) server.getPresenceService()).setAwake(reg); // Force time thread creation of CoapAsyncRequestObserver diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStoreTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStoreTest.java index aa1ca6e759..91ca39bdc2 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStoreTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStoreTest.java @@ -17,11 +17,11 @@ import java.net.InetAddress; import java.nio.charset.StandardCharsets; +import java.util.EnumSet; import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.Identity; -import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; @@ -38,7 +38,7 @@ public class InMemoryRegistrationStoreTest { int port = 23452; Long lifetime = 10000L; String sms = "0171-32423545"; - BindingMode binding = BindingMode.UQS; + EnumSet binding = EnumSet.of(BindingMode.U, BindingMode.Q, BindingMode.S); Link[] objectLinks = Link.parse("".getBytes(StandardCharsets.UTF_8)); String registrationId = "4711"; Registration registration; diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java index bcb60dd4b7..64e101d958 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java @@ -18,6 +18,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -103,7 +104,7 @@ public static class ServerConfig implements Serializable { * This Resource defines the transport binding configured for the LwM2M Client. If the LwM2M Client supports the * binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode. */ - public BindingMode binding = BindingMode.U; + public EnumSet binding = EnumSet.of(BindingMode.U); @Override public String toString() { diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java index 275dca2c5e..f44888606c 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java @@ -27,6 +27,7 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; import org.eclipse.leshan.core.request.BootstrapWriteRequest; @@ -89,7 +90,7 @@ public static LwM2mObjectInstance toServerInstance(int instanceId, ServerConfig resources.add(LwM2mSingleResource.newIntegerResource(5, serverConfig.disableTimeout)); resources.add(LwM2mSingleResource.newBooleanResource(6, serverConfig.notifIfDisabled)); if (serverConfig.binding != null) - resources.add(LwM2mSingleResource.newStringResource(7, serverConfig.binding.name())); + resources.add(LwM2mSingleResource.newStringResource(7, BindingMode.toString(serverConfig.binding))); return new LwM2mObjectInstance(instanceId, resources); } @@ -121,13 +122,12 @@ public static BootstrapWriteRequest toWriteRequest(int instanceId, ACLConfig acl return new BootstrapWriteRequest(path, securityInstance, contentFormat); } - public static List> toRequests( - BootstrapConfig bootstrapConfig) { + public static List> toRequests(BootstrapConfig bootstrapConfig) { return toRequests(bootstrapConfig, ContentFormat.TLV); } - public static List> toRequests( - BootstrapConfig bootstrapConfig, ContentFormat contentFormat) { + public static List> toRequests(BootstrapConfig bootstrapConfig, + ContentFormat contentFormat) { List> requests = new ArrayList<>(); // handle delete for (String path : bootstrapConfig.toDelete) { 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 9c3687b551..c172a7c0c5 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 @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -57,7 +58,9 @@ public class Registration implements Serializable { private final String lwM2mVersion; - private final BindingMode bindingMode; + private final EnumSet bindingMode; + + private final Boolean queueMode; // since LWM2M 1.1 /** * The LWM2M Client's unique end point name. @@ -80,8 +83,9 @@ public class Registration implements Serializable { private final Date lastUpdate; protected Registration(String id, String endpoint, Identity identity, String lwM2mVersion, Long lifetimeInSec, - String smsNumber, BindingMode bindingMode, Link[] objectLinks, Date registrationDate, Date lastUpdate, - Map additionalRegistrationAttributes, Map supportedObjects) { + String smsNumber, EnumSet bindingMode, Boolean queueMode, Link[] objectLinks, + Date registrationDate, Date lastUpdate, Map additionalRegistrationAttributes, + Map supportedObjects) { Validate.notNull(id); Validate.notEmpty(endpoint); @@ -109,7 +113,9 @@ protected Registration(String id, String endpoint, Identity identity, String lwM this.supportedObjects = new AtomicReference>(supportedObjects); this.lifeTimeInSec = lifetimeInSec == null ? DEFAULT_LIFETIME_IN_SEC : lifetimeInSec; this.lwM2mVersion = lwM2mVersion == null ? Version.getDefault().toString() : lwM2mVersion; - this.bindingMode = bindingMode == null ? BindingMode.U : bindingMode; + this.bindingMode = bindingMode == null ? EnumSet.of(BindingMode.U) : bindingMode; + this.queueMode = queueMode == null && Version.get(this.lwM2mVersion).newerThan(Version.V1_0) ? Boolean.FALSE + : queueMode; this.registrationDate = registrationDate == null ? new Date() : registrationDate; this.lastUpdate = lastUpdate == null ? new Date() : lastUpdate; if (additionalRegistrationAttributes == null || additionalRegistrationAttributes.isEmpty()) { @@ -233,10 +239,14 @@ public String getLwM2mVersion() { return lwM2mVersion; } - public BindingMode getBindingMode() { + public EnumSet getBindingMode() { return bindingMode; } + public Boolean getQueueMode() { + return queueMode; + } + /** * @return the path where the objects are hosted on the device */ @@ -271,7 +281,7 @@ public long getExpirationTimeStamp(long gracePeriodInSec) { public boolean canInitiateConnection() { // We consider that initiates a connection (acting as DTLS client to initiate a handshake) does not make sense // for QueueMode as if we lost connection device is probably absent. - return !bindingMode.useQueueMode() && bindingMode.useUDP(); + return !usesQueueMode(); } /** @@ -296,7 +306,10 @@ public Map getAdditionalRegistrationAttributes() { } public boolean usesQueueMode() { - return bindingMode.useQueueMode() && bindingMode.useUDP(); + if (Version.get(lwM2mVersion).olderThan(Version.V1_1)) + return bindingMode.contains(BindingMode.Q); + else + return queueMode; } /** @@ -409,7 +422,8 @@ public static class Builder { private Date lastUpdate; private Long lifeTimeInSec; private String smsNumber; - private BindingMode bindingMode; + private EnumSet bindingMode; + private Boolean queueMode; private String lwM2mVersion; private Link[] objectLinks; private Map supportedObjects; @@ -445,11 +459,16 @@ public Builder smsNumber(String smsNumber) { return this; } - public Builder bindingMode(BindingMode bindingMode) { + public Builder bindingMode(EnumSet bindingMode) { this.bindingMode = bindingMode; return this; } + public Builder queueMode(Boolean queueMode) { + this.queueMode = queueMode; + return this; + } + public Builder lwM2mVersion(String lwM2mVersion) { this.lwM2mVersion = lwM2mVersion; return this; @@ -473,8 +492,8 @@ public Builder additionalRegistrationAttributes(Map additionalRe public Registration build() { return new Registration(Builder.this.registrationId, Builder.this.endpoint, Builder.this.identity, Builder.this.lwM2mVersion, Builder.this.lifeTimeInSec, Builder.this.smsNumber, this.bindingMode, - this.objectLinks, this.registrationDate, this.lastUpdate, this.additionalRegistrationAttributes, - this.supportedObjects); + this.queueMode, this.objectLinks, this.registrationDate, this.lastUpdate, + this.additionalRegistrationAttributes, this.supportedObjects); } } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java index 674183c5ab..25393653f3 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java @@ -56,8 +56,9 @@ public SendableResponse register(Identity sender, RegisterRequ registrationIdProvider.getRegistrationId(registerRequest), registerRequest.getEndpointName(), sender); builder.lwM2mVersion(registerRequest.getLwVersion()).lifeTimeInSec(registerRequest.getLifetime()) - .bindingMode(registerRequest.getBindingMode()).objectLinks(registerRequest.getObjectLinks()) - .smsNumber(registerRequest.getSmsNumber()).registrationDate(new Date()).lastUpdate(new Date()) + .bindingMode(registerRequest.getBindingMode()).queueMode(registerRequest.getQueueMode()) + .objectLinks(registerRequest.getObjectLinks()).smsNumber(registerRequest.getSmsNumber()) + .registrationDate(new Date()).lastUpdate(new Date()) .additionalRegistrationAttributes(registerRequest.getAdditionalAttributes()); // We must check if the client is using the right identity. @@ -100,6 +101,9 @@ public SendableResponse update(Identity sender, UpdateRequest up return new SendableResponse<>(UpdateResponse.badRequest("forbidden")); } + // validate request + updateRequest.validate(registration.getLwM2mVersion()); + // Create update final RegistrationUpdate update = new RegistrationUpdate(updateRequest.getRegistrationId(), sender, updateRequest.getLifeTimeInSec(), updateRequest.getSmsNumber(), updateRequest.getBindingMode(), diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java index acc24c816b..431b45c503 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; @@ -38,12 +39,12 @@ public class RegistrationUpdate { private final Identity identity; private final Long lifeTimeInSec; private final String smsNumber; - private final BindingMode bindingMode; + private final EnumSet bindingMode; private final Link[] objectLinks; private final Map additionalAttributes; public RegistrationUpdate(String registrationId, Identity identity, Long lifeTimeInSec, String smsNumber, - BindingMode bindingMode, Link[] objectLinks, Map additionalAttributes) { + EnumSet bindingMode, Link[] objectLinks, Map additionalAttributes) { Validate.notNull(registrationId); Validate.notNull(identity); this.registrationId = registrationId; @@ -68,7 +69,7 @@ public Registration update(Registration registration) { Identity identity = this.identity != null ? this.identity : registration.getIdentity(); Link[] linkObject = this.objectLinks != null ? this.objectLinks : registration.getObjectLinks(); long lifeTimeInSec = this.lifeTimeInSec != null ? this.lifeTimeInSec : registration.getLifeTimeInSec(); - BindingMode bindingMode = this.bindingMode != null ? this.bindingMode : registration.getBindingMode(); + EnumSet bindingMode = this.bindingMode != null ? this.bindingMode : registration.getBindingMode(); String smsNumber = this.smsNumber != null ? this.smsNumber : registration.getSmsNumber(); Map additionalAttributes = this.additionalAttributes.isEmpty() @@ -83,8 +84,9 @@ public Registration update(Registration registration) { identity); builder.lwM2mVersion(registration.getLwM2mVersion()).lifeTimeInSec(lifeTimeInSec).smsNumber(smsNumber) - .bindingMode(bindingMode).objectLinks(linkObject).registrationDate(registration.getRegistrationDate()) - .lastUpdate(lastUpdate).additionalRegistrationAttributes(additionalAttributes); + .bindingMode(bindingMode).queueMode(registration.getQueueMode()).objectLinks(linkObject) + .registrationDate(registration.getRegistrationDate()).lastUpdate(lastUpdate) + .additionalRegistrationAttributes(additionalAttributes); return builder.build(); @@ -114,7 +116,7 @@ public String getSmsNumber() { return smsNumber; } - public BindingMode getBindingMode() { + public EnumSet getBindingMode() { return bindingMode; } diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationUtil.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationUtil.java index 0dc3d008b0..8469f31abf 100644 --- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationUtil.java +++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationUtil.java @@ -57,7 +57,18 @@ private static boolean isSerializable(Type type, Map notSerializ return true; } - if (Serializable.class.isAssignableFrom(clazz)) { + // For Collection, Map interface as most implementation is Serializable, checking if parameterized type is + // serializable should enough. + if (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)) { + for (Type t : ptype.getActualTypeArguments()) { + if (!isSerializable(t, notSerializableObject, excludes)) { + notSerializableObject.put(clazz, String + .format("[%s] is parameterized with not serializable type [%s].", clazz.getName(), t)); + return false; + } + } + return true; + } else if (Serializable.class.isAssignableFrom(clazz)) { boolean isSerializable = true; for (Field field : clazz.getDeclaredFields()) { // check if this field is Serializable @@ -68,25 +79,11 @@ private static boolean isSerializable(Type type, Map notSerializ } } return isSerializable; - } - // we accept Collection, Map interface because most implementation is Serializable and checking if - // parameterized type is serializable should enough. - else if (clazz.isInterface()) { - if (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)) { - for (Type t : ptype.getActualTypeArguments()) { - if (!isSerializable(t, notSerializableObject, excludes)) { - notSerializableObject.put(clazz, String.format( - "[%s] is parameterized with not serializable type [%s].", clazz.getName(), t)); - return false; - } - } - return true; - } else { - notSerializableObject.put(clazz, String.format( - "[%s] interface is maybe serializable but we only support Collection and Map as parameterized interface class.", - clazz)); - return false; - } + } else if (clazz.isInterface()) { + notSerializableObject.put(clazz, String.format( + "[%s] interface is maybe serializable but we only support Collection and Map as parameterized interface class.", + clazz)); + return false; } else { notSerializableObject.put(clazz, String.format("[%s] is not primitive or does not implement Serializable.", clazz.getName())); diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/queue/PresenceServiceTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/queue/PresenceServiceTest.java index 2858f64340..432ecee84b 100644 --- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/queue/PresenceServiceTest.java +++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/queue/PresenceServiceTest.java @@ -19,6 +19,7 @@ import java.net.Inet4Address; import java.net.UnknownHostException; +import java.util.EnumSet; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.Identity; @@ -74,7 +75,7 @@ private Registration givenASimpleClientWithQueueMode() throws UnknownHostExcepti Registration.Builder builder = new Registration.Builder("ID", "urn:client", Identity.unsecure(Inet4Address.getLoopbackAddress(), 12354)); - Registration reg = builder.bindingMode(BindingMode.UQ).build(); + Registration reg = builder.bindingMode(EnumSet.of(BindingMode.U, BindingMode.Q)).build(); presenceService.setAwake(reg); return reg; } diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/RegistrationSerializer.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/RegistrationSerializer.java index dce951a5af..deca7ed0c3 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/RegistrationSerializer.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/RegistrationSerializer.java @@ -17,6 +17,7 @@ import java.lang.reflect.Type; +import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.server.queue.PresenceService; import org.eclipse.leshan.server.registration.Registration; @@ -45,7 +46,7 @@ public JsonElement serialize(Registration src, Type typeOfSrc, JsonSerialization element.addProperty("smsNumber", src.getSmsNumber()); element.addProperty("lwM2mVersion", src.getLwM2mVersion()); element.addProperty("lifetime", src.getLifeTimeInSec()); - element.addProperty("bindingMode", src.getBindingMode().toString()); + element.addProperty("bindingMode", BindingMode.toString(src.getBindingMode())); element.add("rootPath", context.serialize(src.getRootPath())); element.add("objectLinks", context.serialize(src.getSortedObjectLinks())); element.add("secure", context.serialize(src.getIdentity().isSecure())); 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 88e0219ee5..2967f0bff7 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 @@ -43,7 +43,9 @@ public static JsonObject jSerialize(Registration r) { o.add("sms", r.getSmsNumber()); } o.add("ver", r.getLwM2mVersion()); - o.add("bnd", r.getBindingMode().name()); + o.add("bnd", BindingMode.toString(r.getBindingMode())); + if (r.getQueueMode() != null) + o.add("qm", r.getQueueMode()); o.add("ep", r.getEndpoint()); o.add("regId", r.getId()); @@ -84,7 +86,9 @@ public static byte[] bSerialize(Registration r) { public static Registration deserialize(JsonObject jObj) { Registration.Builder b = new Registration.Builder(jObj.getString("regId", null), jObj.getString("ep", null), IdentitySerDes.deserialize(jObj.get("identity").asObject())); - b.bindingMode(BindingMode.valueOf(jObj.getString("bnd", null))); + b.bindingMode(BindingMode.parse(jObj.getString("bnd", null))); + if (jObj.get("qm") != null) + b.queueMode(jObj.getBoolean("qm", false)); b.lastUpdate(new Date(jObj.getLong("lastUp", 0))); b.lifeTimeInSec(jObj.getLong("lt", 0)); b.lwM2mVersion(jObj.getString("ver", Version.getDefault().toString()));