From ecf961a9dcc04e13db2679b7fe0a6934b05d5c5e Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Wed, 30 Jun 2021 10:21:09 +0200 Subject: [PATCH] Add BootstrapReadRequest support. --- .../californium/object/ObjectResource.java | 48 +++-- .../client/resource/BaseObjectEnabler.java | 25 +++ .../client/resource/LwM2mObjectEnabler.java | 4 + .../leshan/client/resource/ObjectEnabler.java | 10 ++ .../core/request/BootstrapReadRequest.java | 165 ++++++++++++++++++ .../DownLinkRequestVisitorAdapter.java | 4 + .../core/request/DownlinkRequestVisitor.java | 2 + .../core/response/BootstrapReadResponse.java | 107 ++++++++++++ .../integration/tests/BootstrapTest.java | 77 ++++++-- .../util/BootstrapIntegrationTestHelper.java | 19 +- .../tests/util/TestObjectsInitializer.java | 9 + .../request/CoapRequestBuilder.java | 10 ++ .../request/LwM2mResponseBuilder.java | 18 ++ 13 files changed, 465 insertions(+), 33 deletions(-) create mode 100644 leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapReadRequest.java create mode 100644 leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapReadResponse.java diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java index f5cdd0fe3c..3ee089cca4 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java @@ -48,6 +48,7 @@ import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; import org.eclipse.leshan.core.request.BootstrapWriteRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.CreateRequest; @@ -62,6 +63,7 @@ import org.eclipse.leshan.core.request.WriteRequest.Mode; import org.eclipse.leshan.core.response.BootstrapDeleteResponse; import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; import org.eclipse.leshan.core.response.BootstrapWriteResponse; import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; @@ -152,21 +154,41 @@ public void handleGET(CoapExchange exchange) { return; } } - // Manage Read Request + else { - ReadRequest readRequest = new ReadRequest(requestedContentFormat, URI, coapRequest); - ReadResponse response = nodeEnabler.read(identity, readRequest); - if (response.getCode() == org.eclipse.leshan.core.ResponseCode.CONTENT) { - LwM2mPath path = new LwM2mPath(URI); - LwM2mNode content = response.getContent(); - LwM2mModel model = new StaticModel(nodeEnabler.getObjectModel()); - ContentFormat format = getContentFormat(readRequest, requestedContentFormat); - exchange.respond(ResponseCode.CONTENT, encoder.encode(content, format, path, model), - format.getCode()); - return; + if (identity.isLwm2mBootstrapServer()) { + // Manage Bootstrap Read Request + BootstrapReadRequest readRequest = new BootstrapReadRequest(requestedContentFormat, URI, + coapRequest); + BootstrapReadResponse response = nodeEnabler.read(identity, readRequest); + if (response.getCode() == org.eclipse.leshan.core.ResponseCode.CONTENT) { + LwM2mPath path = new LwM2mPath(URI); + LwM2mNode content = response.getContent(); + LwM2mModel model = new StaticModel(nodeEnabler.getObjectModel()); + ContentFormat format = getContentFormat(readRequest, requestedContentFormat); + exchange.respond(ResponseCode.CONTENT, encoder.encode(content, format, path, model), + format.getCode()); + return; + } else { + exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); + return; + } } else { - exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); - return; + // Manage Read Request + ReadRequest readRequest = new ReadRequest(requestedContentFormat, URI, coapRequest); + ReadResponse response = nodeEnabler.read(identity, readRequest); + if (response.getCode() == org.eclipse.leshan.core.ResponseCode.CONTENT) { + LwM2mPath path = new LwM2mPath(URI); + LwM2mNode content = response.getContent(); + LwM2mModel model = new StaticModel(nodeEnabler.getObjectModel()); + ContentFormat format = getContentFormat(readRequest, requestedContentFormat); + exchange.respond(ResponseCode.CONTENT, encoder.encode(content, format, path, model), + format.getCode()); + return; + } else { + exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); + return; + } } } } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java index dfb83ac2b7..96e303ef93 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java @@ -40,6 +40,7 @@ import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; import org.eclipse.leshan.core.request.BootstrapWriteRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.CreateRequest; @@ -53,6 +54,7 @@ import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.BootstrapDeleteResponse; import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; import org.eclipse.leshan.core.response.BootstrapWriteResponse; import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; @@ -180,6 +182,29 @@ protected ReadResponse doRead(ServerIdentity identity, ReadRequest request) { return ReadResponse.internalServerError("not implemented"); } + @Override + public BootstrapReadResponse read(ServerIdentity identity, BootstrapReadRequest request) { + // read is not supported for bootstrap + if (identity.isLwm2mServer()) { + return BootstrapReadResponse.methodNotAllowed(); + } + + if (!identity.isSystem()) { + LwM2mPath path = request.getPath(); + + // BootstrapRead can only target object 1 and 2 + if (path.getObjectId() != 1 && path.getObjectId() != 2) { + return BootstrapReadResponse.badRequest("bootstrap read can only target Object 1 (Server) or 2 (ACL)"); + } + } + return doRead(identity, request); + } + + protected BootstrapReadResponse doRead(ServerIdentity identity, BootstrapReadRequest request) { + // This should be a not implemented error, but this is not defined in the spec. + return BootstrapReadResponse.internalServerError("not implemented"); + } + @Override public synchronized WriteResponse write(ServerIdentity identity, WriteRequest request) { try { diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mObjectEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mObjectEnabler.java index 6b31e748ad..ea882db433 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mObjectEnabler.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mObjectEnabler.java @@ -27,6 +27,7 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; import org.eclipse.leshan.core.request.BootstrapWriteRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.CreateRequest; @@ -40,6 +41,7 @@ import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.BootstrapDeleteResponse; import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; import org.eclipse.leshan.core.response.BootstrapWriteResponse; import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; @@ -79,6 +81,8 @@ public interface LwM2mObjectEnabler { ReadResponse read(ServerIdentity identity, ReadRequest request); + BootstrapReadResponse read(ServerIdentity identity, BootstrapReadRequest request); + WriteResponse write(ServerIdentity identity, WriteRequest request); BootstrapWriteResponse write(ServerIdentity identity, BootstrapWriteRequest request); diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java index 69ed4e2bca..2de0be1306 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java @@ -39,6 +39,7 @@ import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; import org.eclipse.leshan.core.request.BootstrapWriteRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.CreateRequest; @@ -50,6 +51,7 @@ import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest.Mode; import org.eclipse.leshan.core.response.BootstrapDeleteResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; import org.eclipse.leshan.core.response.BootstrapWriteResponse; import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; @@ -224,6 +226,14 @@ protected ReadResponse doRead(ServerIdentity identity, ReadRequest request) { return instance.read(identity, path.getResourceId(), path.getResourceInstanceId()); } + @Override + protected BootstrapReadResponse doRead(ServerIdentity identity, BootstrapReadRequest request) { + // Basic implementation we delegate to classic Read Request + ReadResponse response = doRead(identity, + new ReadRequest(request.getContentFormat(), request.getPath(), request.getCoapRequest())); + return new BootstrapReadResponse(response.getCode(), response.getContent(), response.getErrorMessage()); + } + @Override protected ObserveResponse doObserve(final ServerIdentity identity, final ObserveRequest request) { final LwM2mPath path = request.getPath(); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapReadRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapReadRequest.java new file mode 100644 index 0000000000..efa01fa256 --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapReadRequest.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2013-2015 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.core.request; + +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.request.exception.InvalidRequestException; +import org.eclipse.leshan.core.response.BootstrapReadResponse; + +/** + * A Lightweight M2M request for retrieving the values of resources from a LWM2M Client. + * + * The "Bootstrap-Read" operation in the Bootstrap Interface is a restricted form of the "Read" operation found in the + * Device Management and Service Enablement interface, and MUST be limited to target Objects that are strictly necessary + * to setup a proper configuration in a LwM2M Client. + */ +public class BootstrapReadRequest extends AbstractSimpleDownlinkRequest + implements BootstrapDownlinkRequest { + + private final ContentFormat format; + + /** + * Creates a request for reading all instances of a particular object from a client. + * + * @param objectId the object ID of the resource + */ + public BootstrapReadRequest(int objectId) { + this(null, new LwM2mPath(objectId), null); + } + + /** + * Creates a request for reading all instances of a particular object from a client. + * + * @param format the desired format for the response + * @param objectId the object ID of the resource + */ + public BootstrapReadRequest(ContentFormat format, int objectId) { + this(format, new LwM2mPath(objectId), null); + } + + /** + * Creates a request for reading a particular object instance from a client. + * + * @param objectId the object ID of the resource + * @param objectInstanceId the object instance ID + */ + public BootstrapReadRequest(int objectId, int objectInstanceId) { + this(null, new LwM2mPath(objectId, objectInstanceId), null); + } + + /** + * Creates a request for reading a particular object instance from a client. + * + * @param format the desired format for the response + * @param objectId the object ID of the resource + * @param objectInstanceId the object instance ID + */ + public BootstrapReadRequest(ContentFormat format, int objectId, int objectInstanceId) { + this(format, new LwM2mPath(objectId, objectInstanceId), null); + } + + /** + * Create a request for reading an object/instance targeted by a specific path. + * + * @param path the path to the LWM2M node to read + * @throws IllegalArgumentException if the target path is not valid + */ + public BootstrapReadRequest(String path) { + this(null, new LwM2mPath(path), null); + } + + /** + * Create a request for reading an object/instance targeted by a specific path. + * + * @param format the desired format for the response + * @param path the path to the LWM2M node to read + * @throws IllegalArgumentException if the target path is not valid + */ + public BootstrapReadRequest(ContentFormat format, String path) { + this(format, new LwM2mPath(path), null); + } + + /** + * Create a request for reading an object/instance targeted by a specific path. + * + * @param format the desired format for the response + * @param path the path to the LWM2M node to read + * @param coapRequest the underlying request + * + * @throws IllegalArgumentException if the target path is not valid + */ + public BootstrapReadRequest(ContentFormat format, String path, Object coapRequest) { + this(format, new LwM2mPath(path), coapRequest); + } + + /** + * Create a request for reading an object/instance targeted by a specific path. + *

+ * This constructor is mainly for internal purpose. + * + * @param format the desired format for the response + * @param target the path to the LWM2M node to read + * @param coapRequest the underlying request + * + * @throws IllegalArgumentException if the target path is not valid + */ + public BootstrapReadRequest(ContentFormat format, LwM2mPath target, Object coapRequest) { + super(target, coapRequest); + if (!target.isObject() && !target.isObjectInstance()) + throw new InvalidRequestException("Bootstrap Read request cannot only target Object and Object instance"); + this.format = format; + } + + /** + * @return the desired format of the resource to read + */ + public ContentFormat getContentFormat() { + return format; + } + + @Override + public final String toString() { + return String.format("BootstrapReadRequest [path=%s format=%s]", getPath(), format); + } + + @Override + public void accept(DownlinkRequestVisitor visitor) { + visitor.visit(this); + + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((format == null) ? 0 : format.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + BootstrapReadRequest other = (BootstrapReadRequest) obj; + if (format != other.format) + return false; + return true; + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/DownLinkRequestVisitorAdapter.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/DownLinkRequestVisitorAdapter.java index 26abd6dada..3043768932 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/DownLinkRequestVisitorAdapter.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/DownLinkRequestVisitorAdapter.java @@ -73,6 +73,10 @@ public void visit(BootstrapDiscoverRequest request) { public void visit(BootstrapWriteRequest request) { } + @Override + public void visit(BootstrapReadRequest request) { + } + @Override public void visit(BootstrapDeleteRequest request) { } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/DownlinkRequestVisitor.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/DownlinkRequestVisitor.java index 92a437653e..d1fb964a4b 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/DownlinkRequestVisitor.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/DownlinkRequestVisitor.java @@ -45,6 +45,8 @@ public interface DownlinkRequestVisitor { void visit(BootstrapWriteRequest request); + void visit(BootstrapReadRequest request); + void visit(BootstrapDeleteRequest request); void visit(BootstrapFinishRequest request); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapReadResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapReadResponse.java new file mode 100644 index 0000000000..a9a2a2d560 --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapReadResponse.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2013-2015 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.core.response; + +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.request.exception.InvalidResponseException; + +public class BootstrapReadResponse extends AbstractLwM2mResponse { + + protected final LwM2mNode content; + + public BootstrapReadResponse(ResponseCode code, LwM2mNode content, String errorMessage) { + this(code, content, errorMessage, null); + } + + public BootstrapReadResponse(ResponseCode code, LwM2mNode content, String errorMessage, Object coapResponse) { + super(code, errorMessage, coapResponse); + + if (ResponseCode.CONTENT.equals(code)) { + if (content == null) + throw new InvalidResponseException("Content is mandatory for successful response"); + } + this.content = content; + } + + @Override + public boolean isSuccess() { + return getCode() == ResponseCode.CONTENT; + } + + @Override + public boolean isValid() { + switch (code.getCode()) { + case ResponseCode.CONTENT_CODE: + case ResponseCode.BAD_REQUEST_CODE: + case ResponseCode.UNAUTHORIZED_CODE: + case ResponseCode.NOT_FOUND_CODE: + case ResponseCode.METHOD_NOT_ALLOWED_CODE: + case ResponseCode.NOT_ACCEPTABLE_CODE: + case ResponseCode.INTERNAL_SERVER_ERROR_CODE: + return true; + default: + return false; + } + } + + /** + * Get the {@link LwM2mNode} value returned as response payload. + * + * @return the value or null if the client returned an error response. + */ + public LwM2mNode getContent() { + return content; + } + + @Override + public String toString() { + if (errorMessage != null) + return String.format("BootstrapReadResponse [code=%s, errormessage=%s]", code, errorMessage); + else + return String.format("BootstrapReadResponse [code=%s, content=%s]", code, content); + } + + // Syntactic sugar static constructors : + + public static BootstrapReadResponse success(LwM2mNode content) { + return new BootstrapReadResponse(ResponseCode.CONTENT, content, null, null); + } + + public static BootstrapReadResponse notFound() { + return new BootstrapReadResponse(ResponseCode.NOT_FOUND, null, null); + } + + public static BootstrapReadResponse unauthorized() { + return new BootstrapReadResponse(ResponseCode.UNAUTHORIZED, null, null); + } + + public static BootstrapReadResponse methodNotAllowed() { + return new BootstrapReadResponse(ResponseCode.METHOD_NOT_ALLOWED, null, null); + } + + public static BootstrapReadResponse notAcceptable() { + return new BootstrapReadResponse(ResponseCode.NOT_ACCEPTABLE, null, null); + } + + public static BootstrapReadResponse badRequest(String errorMessage) { + return new BootstrapReadResponse(ResponseCode.BAD_REQUEST, null, errorMessage); + } + + public static BootstrapReadResponse internalServerError(String errorMessage) { + return new BootstrapReadResponse(ResponseCode.INTERNAL_SERVER_ERROR, null, errorMessage); + } +} diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java index a0e8956d9f..b920b1077a 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java @@ -34,11 +34,14 @@ import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.request.exception.RequestCanceledException; +import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.integration.tests.util.BootstrapIntegrationTestHelper; @@ -222,13 +225,15 @@ public void bootstrapWithDiscoverOnRoot() { // check the client is registered helper.assertClientRegisterered(); - assertNotNull(helper.lastDiscoverAnswer); - assertEquals(ResponseCode.CONTENT, helper.lastDiscoverAnswer.getCode()); + assertNotNull(helper.lastCustomResponse); + assertTrue(helper.lastCustomResponse instanceof BootstrapDiscoverResponse); + BootstrapDiscoverResponse lastDiscoverAnswer = (BootstrapDiscoverResponse) helper.lastCustomResponse; + assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); assertEquals( String.format(";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ver=1.1,;ver=1.1,", helper.bootstrapServer.getUnsecuredAddress().getHostString(), helper.bootstrapServer.getUnsecuredAddress().getPort()), - Link.serialize(helper.lastDiscoverAnswer.getObjectLinks())); + Link.serialize(lastDiscoverAnswer.getObjectLinks())); } @Test @@ -252,13 +257,14 @@ public void bootstrapWithDiscoverOnRootThenRebootstrap() throws InvalidRequestEx // check the client is registered helper.assertClientRegisterered(); - assertNotNull(helper.lastDiscoverAnswer); - assertEquals(ResponseCode.CONTENT, helper.lastDiscoverAnswer.getCode()); + assertTrue(helper.lastCustomResponse instanceof BootstrapDiscoverResponse); + BootstrapDiscoverResponse lastDiscoverAnswer = (BootstrapDiscoverResponse) helper.lastCustomResponse; + assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); assertEquals( String.format(";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ver=1.1,;ver=1.1,", helper.bootstrapServer.getUnsecuredAddress().getHostString(), helper.bootstrapServer.getUnsecuredAddress().getPort()), - Link.serialize(helper.lastDiscoverAnswer.getObjectLinks())); + Link.serialize(lastDiscoverAnswer.getObjectLinks())); // re-bootstrap try { @@ -274,14 +280,15 @@ public void bootstrapWithDiscoverOnRootThenRebootstrap() throws InvalidRequestEx helper.waitForBootstrapFinishedAtClientSide(1); // check last discover response - assertNotNull(helper.lastDiscoverAnswer); - assertEquals(ResponseCode.CONTENT, helper.lastDiscoverAnswer.getCode()); + assertTrue(helper.lastCustomResponse instanceof BootstrapDiscoverResponse); + lastDiscoverAnswer = (BootstrapDiscoverResponse) helper.lastCustomResponse; + assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); assertEquals(String.format( ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ssid=2222;uri=\"coap://%s:%d\",;ver=1.1,;ssid=2222,;ver=1.1,", helper.bootstrapServer.getUnsecuredAddress().getHostString(), helper.bootstrapServer.getUnsecuredAddress().getPort(), helper.server.getUnsecuredAddress().getHostString(), helper.server.getUnsecuredAddress().getPort()), - Link.serialize(helper.lastDiscoverAnswer.getObjectLinks())); + Link.serialize(lastDiscoverAnswer.getObjectLinks())); } @@ -305,9 +312,55 @@ public void bootstrapWithDiscoverOnDevice() { // check the client is registered helper.assertClientRegisterered(); - assertNotNull(helper.lastDiscoverAnswer); - assertEquals(ResponseCode.CONTENT, helper.lastDiscoverAnswer.getCode()); - assertEquals(";lwm2m=1.0,;ver=1.1,", Link.serialize(helper.lastDiscoverAnswer.getObjectLinks())); + assertTrue(helper.lastCustomResponse instanceof BootstrapDiscoverResponse); + BootstrapDiscoverResponse lastDiscoverAnswer = (BootstrapDiscoverResponse) helper.lastCustomResponse; + assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); + assertEquals(";lwm2m=1.0,;ver=1.1,", Link.serialize(lastDiscoverAnswer.getObjectLinks())); + } + + @Test + public void bootstrapWithReadOnServerThenRebootstrap() throws InvalidRequestException, InterruptedException { + // Create DM Server without security & start it + helper.createServer(); + helper.server.start(); + + // Create and start bootstrap server + helper.createBootstrapServer(null, null, new BootstrapReadRequest(1)); + helper.bootstrapServer.start(); + + // Create Client and check it is not already registered + helper.createClient(); + helper.assertClientNotRegisterered(); + + // Start it and wait for registration + helper.client.start(); + helper.waitForBootstrapFinishedAtClientSide(1); + helper.waitForRegistrationAtServerSide(1); + + // check the client is registered + helper.assertClientRegisterered(); + assertTrue(helper.lastCustomResponse instanceof BootstrapReadResponse); + BootstrapReadResponse lastReadResponse = (BootstrapReadResponse) helper.lastCustomResponse; + assertEquals(ResponseCode.CONTENT, lastReadResponse.getCode()); + assertEquals(new LwM2mObject(1), lastReadResponse.getContent()); + + // re-bootstrap + try { + ExecuteResponse response = helper.server.send(helper.getCurrentRegistration(), + new ExecuteRequest("/1/0/9")); + assertTrue(response.isSuccess()); + } catch (RequestCanceledException e) { + // request can be cancelled if server does not received the execute response before the de-registration + // so we just ignore this error. + } + + // wait bootstrap finished + helper.waitForBootstrapFinishedAtClientSide(5); + + // check last discover response + assertTrue(helper.lastCustomResponse instanceof BootstrapReadResponse); + lastReadResponse = (BootstrapReadResponse) helper.lastCustomResponse; + assertEquals(ResponseCode.CONTENT, lastReadResponse.getCode()); } @Test diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java index 5eaacfb56b..5fb48465fe 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java @@ -54,11 +54,10 @@ import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; -import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.Identity; -import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; @@ -89,7 +88,7 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper public LeshanBootstrapServer bootstrapServer; public final PublicKey bootstrapServerPublicKey; public final PrivateKey bootstrapServerPrivateKey; - public volatile BootstrapDiscoverResponse lastDiscoverAnswer; + public volatile LwM2mResponse lastCustomResponse; private SynchronousBootstrapListener bootstrapListener = new SynchronousBootstrapListener(); @@ -169,7 +168,7 @@ public void createBootstrapServer(BootstrapSecurityStore securityStore, Bootstra } public void createBootstrapServer(BootstrapSecurityStore securityStore, BootstrapConfigStore bootstrapStore, - final BootstrapDiscoverRequest request) { + final BootstrapDownlinkRequest firstCustomRequest) { LeshanBootstrapServerBuilder builder = createBootstrapBuilder(securityStore, bootstrapStore); if (bootstrapStore == null) { bootstrapStore = unsecuredBootstrapStore(); @@ -181,14 +180,18 @@ public void createBootstrapServer(BootstrapSecurityStore securityStore, Bootstra BootstrapConfigStoreTaskProvider taskProvider = new BootstrapConfigStoreTaskProvider(bootstrapStore) { @Override public Tasks getTasks(BootstrapSession session, List previousResponses) { - if (previousResponses == null) { + if (previousResponses == null) { Tasks tasks = new Tasks(); - tasks.requestsToSend = new ArrayList<>(); - tasks.requestsToSend.add(request); + tasks.requestsToSend = new ArrayList<>(1); + tasks.requestsToSend.add(firstCustomRequest); tasks.last = false; + tasks.supportedObjects = new HashMap<>(); + tasks.supportedObjects.put(0, "1.1"); + tasks.supportedObjects.put(1, "1.1"); + tasks.supportedObjects.put(2, "1.0"); return tasks; } else { - lastDiscoverAnswer = (BootstrapDiscoverResponse) previousResponses.get(0); + lastCustomResponse = previousResponses.get(0); return super.getTasks(session, null); } } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TestObjectsInitializer.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TestObjectsInitializer.java index 27d6e6af9b..27afd7222e 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TestObjectsInitializer.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TestObjectsInitializer.java @@ -30,6 +30,7 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; import org.eclipse.leshan.core.request.BootstrapWriteRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.CreateRequest; @@ -43,6 +44,7 @@ import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.BootstrapDeleteResponse; import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; import org.eclipse.leshan.core.response.BootstrapWriteResponse; import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; @@ -106,6 +108,13 @@ public ReadResponse read(ServerIdentity identity, ReadRequest request) { return nodeEnabler.read(identity, request); } + @Override + public BootstrapReadResponse read(ServerIdentity identity, BootstrapReadRequest request) { + if (!identity.isSystem()) + assertThat(request.getCoapRequest(), instanceOf(Request.class)); + return nodeEnabler.read(identity, request); + } + @Override public ObserveResponse observe(ServerIdentity identity, ObserveRequest request) { if (!identity.isSystem()) 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 5a31fada7c..8c5afb19c4 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 @@ -31,6 +31,7 @@ import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; import org.eclipse.leshan.core.request.BootstrapFinishRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; import org.eclipse.leshan.core.request.BootstrapWriteRequest; import org.eclipse.leshan.core.request.CancelObservationRequest; import org.eclipse.leshan.core.request.ContentFormat; @@ -211,6 +212,15 @@ public void visit(BootstrapWriteRequest request) { applyLowerLayerConfig(coapRequest); } + @Override + public void visit(BootstrapReadRequest request) { + coapRequest = Request.newGet(); + if (request.getContentFormat() != null) + coapRequest.getOptions().setAccept(request.getContentFormat().getCode()); + setTarget(coapRequest, request.getPath()); + applyLowerLayerConfig(coapRequest); + } + @Override public void visit(BootstrapDiscoverRequest request) { coapRequest = Request.newGet(); diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/LwM2mResponseBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/LwM2mResponseBuilder.java index d6f2032b9a..b69fb80485 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/LwM2mResponseBuilder.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/LwM2mResponseBuilder.java @@ -34,6 +34,7 @@ import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; import org.eclipse.leshan.core.request.BootstrapFinishRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; import org.eclipse.leshan.core.request.BootstrapWriteRequest; import org.eclipse.leshan.core.request.CancelObservationRequest; import org.eclipse.leshan.core.request.ContentFormat; @@ -53,6 +54,7 @@ import org.eclipse.leshan.core.response.BootstrapDeleteResponse; import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; import org.eclipse.leshan.core.response.BootstrapFinishResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; import org.eclipse.leshan.core.response.BootstrapWriteResponse; import org.eclipse.leshan.core.response.CancelObservationResponse; import org.eclipse.leshan.core.response.CreateResponse; @@ -329,6 +331,22 @@ public void visit(BootstrapWriteRequest request) { } } + @Override + public void visit(BootstrapReadRequest request) { + if (coapResponse.isError()) { + // handle error response: + lwM2mresponse = new BootstrapReadResponse(toLwM2mResponseCode(coapResponse.getCode()), null, + coapResponse.getPayloadString(), coapResponse); + } else if (coapResponse.getCode() == org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT) { + // handle success response: + LwM2mNode content = decodeCoapResponse(request.getPath(), coapResponse, request, clientEndpoint); + lwM2mresponse = new BootstrapReadResponse(ResponseCode.CONTENT, content, null, coapResponse); + } else { + // handle unexpected response: + handleUnexpectedResponseCode(clientEndpoint, request, coapResponse); + } + } + @Override public void visit(BootstrapDeleteRequest request) { if (coapResponse.isError()) {