diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java index e6adb796c3..732d76bf9e 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java @@ -39,6 +39,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.request.ReadRequest; @@ -46,6 +47,7 @@ import org.eclipse.leshan.core.response.ErrorCallback; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ResponseCallback; +import org.eclipse.leshan.integration.tests.util.Failure; import org.eclipse.leshan.integration.tests.util.LeshanTestClient; import org.eclipse.leshan.integration.tests.util.LeshanTestClientBuilder; import org.eclipse.leshan.integration.tests.util.LeshanTestServer; @@ -289,4 +291,22 @@ public void register_with_additional_attributes(Protocol protocol, String client assertThat(registration.getAdditionalRegistrationAttributes()) .containsExactlyEntriesOf(expectedAdditionalAttributes); } + + @TestAllTransportLayer + public void register_without_sending_endpoint(Protocol protocol, String clientEndpointProvider, + String serverEndpointProvider) throws InterruptedException, LinkParseException { + + client = givenClient.dontSendEndpointName().build(); + + // Check client is not registered + assertThat(client).isNotRegisteredAt(server); + + // Start it and wait for registration failure + client.start(); + Failure failure = client.waitForRegistrationFailureTo(server); + assertThat(failure).failedWith(ResponseCode.FORBIDDEN); + + // Check we are registered with the expected attributes + assertThat(client).isNotRegisteredAt(server); + } } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/bootstrap/BootstrapTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/bootstrap/BootstrapTest.java index f39ff0db32..81e904436b 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/bootstrap/BootstrapTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/bootstrap/BootstrapTest.java @@ -59,6 +59,7 @@ import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.util.TestLwM2mId; import org.eclipse.leshan.integration.tests.util.BootstrapRequestChecker; +import org.eclipse.leshan.integration.tests.util.Failure; import org.eclipse.leshan.integration.tests.util.LeshanTestBootstrapServer; import org.eclipse.leshan.integration.tests.util.LeshanTestBootstrapServerBuilder; import org.eclipse.leshan.integration.tests.util.LeshanTestClient; @@ -157,6 +158,29 @@ public void bootstrap(Protocol givenProtocol, String givenClientEndpointProvider assertThat(client).isRegisteredAt(server); } + @TestAllTransportLayer + public void bootstrap_without_endpoint_name(Protocol givenProtocol, String givenClientEndpointProvider, + String givenServerEndpointProvider, String givenBootstrapServerEndpointProvider) + throws InvalidConfigurationException { + // Create and start bootstrap server + bootstrapServer = givenBootstrapServer.build(); + bootstrapServer.start(); + + // Create Client and check it is not already registered + client = givenClient.connectingTo(bootstrapServer).dontSendEndpointName().build(); + + // Add config for this client + bootstrapServer.getConfigStore().add(client.getEndpointName(), // + givenBootstrapConfig() // + .adding(givenProtocol, bootstrapServer) // + .build()); + + // Start it and wait for registration + client.start(); + Failure cause = client.waitForBootstrapFailure(bootstrapServer, 2, TimeUnit.SECONDS); + assertThat(cause).failedWith(ResponseCode.BAD_REQUEST); + } + @TestAllTransportLayer public void bootstrap_tlv_only(Protocol givenProtocol, String givenClientEndpointProvider, String givenServerEndpointProvider, String givenBootstrapServerEndpointProvider) @@ -501,8 +525,8 @@ public void bootstrap_create_2_bsserver(Protocol givenProtocol, String givenClie client.start(); // ensure bootstrap session failed because of invalid state - Exception cause = client.waitForBootstrapFailure(bootstrapServer, 2, TimeUnit.SECONDS); - assertThat(cause).isExactlyInstanceOf(InvalidStateException.class); + Failure cause = client.waitForBootstrapFailure(bootstrapServer, 2, TimeUnit.SECONDS); + assertThat(cause).failedWith(InvalidStateException.class); BootstrapFailureCause failure = bootstrapServer.waitForBootstrapFailure(1, TimeUnit.SECONDS); assertThat(failure).isEqualTo(BootstrapFailureCause.FINISH_FAILED); } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/bootstrap/SecureBootstrapTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/bootstrap/SecureBootstrapTest.java index b66bcc5bec..9ecac07448 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/bootstrap/SecureBootstrapTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/bootstrap/SecureBootstrapTest.java @@ -36,8 +36,10 @@ import org.eclipse.leshan.bsserver.InvalidConfigurationException; import org.eclipse.leshan.core.CertificateUsage; +import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.integration.tests.util.Credentials; +import org.eclipse.leshan.integration.tests.util.Failure; import org.eclipse.leshan.integration.tests.util.LeshanTestBootstrapServer; import org.eclipse.leshan.integration.tests.util.LeshanTestBootstrapServerBuilder; import org.eclipse.leshan.integration.tests.util.LeshanTestClient; @@ -118,6 +120,41 @@ public void bootstrap_using_psk() throws NonUniqueSecurityInfoException, Invalid assertThat(client).isRegisteredAt(server); } + @Test + public void bootstrap_using_psk_without_endpointname() + throws NonUniqueSecurityInfoException, InvalidConfigurationException { + // Create DM Server without security & start it + server = givenServer.using(Protocol.COAP).build(); + server.start(); + + // Create and start bootstrap server + bootstrapServer = givenBootstrapServer.using(Protocol.COAPS).build(); + bootstrapServer.start(); + + // Create Client and check it is not already registered + client = givenClient.connectingTo(bootstrapServer).named(GOOD_PSK_ID).sendEndpointNameIfNecessary() + .using(Protocol.COAPS).usingPsk(GOOD_PSK_ID, GOOD_PSK_KEY).build(); + assertThat(client).isNotRegisteredAt(server); + + // Add client credentials to the server + bootstrapServer.getEditableSecurityStore() + .add(SecurityInfo.newPreSharedKeyInfo(client.getEndpointName(), GOOD_PSK_ID, GOOD_PSK_KEY)); + + // Add config for this client + bootstrapServer.getConfigStore().add(client.getEndpointName(), // + givenBootstrapConfig() // + .adding(Protocol.COAPS, bootstrapServer, GOOD_PSK_ID, GOOD_PSK_KEY) // + .adding(Protocol.COAP, server) // + .build()); + + // Start it and wait for registration + client.start(); + server.waitForNewRegistrationOf(client); + + // check the client is registered + assertThat(client).isRegisteredAt(server); + } + @Test public void bootstrap_failed_using_bad_psk() throws InvalidConfigurationException, NonUniqueSecurityInfoException { // Create DM Server without security & start it @@ -185,6 +222,42 @@ public void bootstrap_using_rpk() throws NonUniqueSecurityInfoException, Invalid assertThat(client).isRegisteredAt(server); } + @Test + public void bootstrap_using_rpk_without_endpoint() + throws NonUniqueSecurityInfoException, InvalidConfigurationException { + // Create DM Server without security & start it + server = givenServer.using(Protocol.COAP).build(); + server.start(); + + // Create and start bootstrap server + bootstrapServer = givenBootstrapServer.using(Protocol.COAPS).using(serverPublicKey, serverPrivateKey).build(); + bootstrapServer.start(); + + // Create Client and check it is not already registered + client = givenClient.connectingTo(bootstrapServer).using(Protocol.COAPS) // + .dontSendEndpointName()// + .using(clientPublicKey, clientPrivateKey)// + .trusting(serverPublicKey).build(); + + assertThat(client).isNotRegisteredAt(server); + + // Add client credentials to the server + bootstrapServer.getEditableSecurityStore() + .add(SecurityInfo.newRawPublicKeyInfo(client.getEndpointName(), clientPublicKey)); + + // Add config for this client + bootstrapServer.getConfigStore().add(client.getEndpointName(), // + givenBootstrapConfig() // + .adding(Protocol.COAPS, bootstrapServer, clientPublicKey, clientPrivateKey, serverPublicKey) // + .adding(Protocol.COAP, server) // + .build()); + + // Start it and wait for registration + client.start(); + Failure cause = client.waitForBootstrapFailure(bootstrapServer, 2, TimeUnit.SECONDS); + assertThat(cause).failedWith(ResponseCode.BAD_REQUEST); + } + @Test public void bootstrap_using_x509() throws NonUniqueSecurityInfoException, InvalidConfigurationException, CertificateEncodingException { @@ -225,6 +298,47 @@ public void bootstrap_using_x509() assertThat(client).isRegisteredAt(server); } + @Test + public void bootstrap_using_x509_without_endpoint() + throws NonUniqueSecurityInfoException, InvalidConfigurationException, CertificateEncodingException { + + // Create DM Server without security & start it + server = givenServer.using(Protocol.COAP).build(); + server.start(); + + // Create and start bootstrap server + bootstrapServer = givenBootstrapServer.using(Protocol.COAPS) + .using(serverX509CertSignedByRoot, serverPrivateKeyFromCert)// + .trusting(trustedCertificatesByServer).build(); + bootstrapServer.start(); + + // Create Client and check it is not already registered + client = givenClient.connectingTo(bootstrapServer).using(Protocol.COAPS) // + .sendEndpointNameIfNecessary() // by default LeshanClientTest is using CN of certificate + .using(clientX509CertSignedByRoot, clientPrivateKeyFromCert)// + .trusting(serverX509CertSignedByRoot).build(); + + assertThat(client).isNotRegisteredAt(server); + + // Add client credentials to the server + bootstrapServer.getEditableSecurityStore().add(SecurityInfo.newX509CertInfo(client.getEndpointName())); + + // Add config for this client + bootstrapServer.getConfigStore().add(client.getEndpointName(), // + givenBootstrapConfig() // + .adding(Protocol.COAPS, bootstrapServer, clientX509CertSignedByRoot, clientPrivateKeyFromCert, + serverX509CertSignedByRoot) // + .adding(Protocol.COAP, server) // + .build()); + + // Start it and wait for registration + client.start(); + server.waitForNewRegistrationOf(client); + + // check the client is registered + assertThat(client).isRegisteredAt(server); + } + @Test public void bootstrap_using_x509_with_sni() throws NonUniqueSecurityInfoException, InvalidConfigurationException, CertificateEncodingException { diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/oscore/OscoreBootstrapTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/oscore/OscoreBootstrapTest.java index 0e146e7014..1f2f424f82 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/oscore/OscoreBootstrapTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/oscore/OscoreBootstrapTest.java @@ -216,4 +216,42 @@ public void bootstrapViaOscoreToUnsecuredServer() // check the client is registered assertThat(client).isRegisteredAt(server); } + + @Test + public void bootstrap_via_oscore_to_unsecured_server_without_endpoint() + throws OSException, NonUniqueSecurityInfoException, InvalidConfigurationException { + // Create DM Server without security & start it + server = givenServer.build(); + server.start(); + + // Create and start bootstrap server + bootstrapServer = givenBootstrapServer.with(new InMemorySecurityStore()).build(); + bootstrapServer.start(); + + // Create Client and check it is not already registered + client = givenClient.connectingTo(bootstrapServer) // + .named(new String(getClientOscoreSetting().getSenderId())) // + .sendEndpointNameIfNecessary() // + .using(getBootstrapClientOscoreSetting()).build(); + assertThat(client).isNotRegisteredAt(server); + + // Add client credentials to the Bootstrap server + bootstrapServer.getEditableSecurityStore() + .add(SecurityInfo.newOscoreInfo(client.getEndpointName(), getBootstrapServerOscoreSetting())); + + // Add config for this client + bootstrapServer.getConfigStore().add(client.getEndpointName(), // + givenBootstrapConfig() // + .adding(Protocol.COAP, bootstrapServer, + getOscoreBootstrapObject(getBootstrapClientOscoreSetting())) // + .adding(Protocol.COAP, server) // + .build()); + + // Start it and wait for registration + client.start(); + server.waitForNewRegistrationOf(client); + + // check the client is registered + assertThat(client).isRegisteredAt(server); + } } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/oscore/OscoreTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/oscore/OscoreTest.java index 2c031588b1..b5ba3231f9 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/oscore/OscoreTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/oscore/OscoreTest.java @@ -103,6 +103,34 @@ public void registered_device_with_oscore_to_server_with_oscore() assertThat(response.isSuccess()).isTrue(); } + @Test + public void registered_device_with_oscore_to_server_with_oscore_without_endpoint_name() + throws NonUniqueSecurityInfoException, InterruptedException { + // Create OSCORE Client + client = givenClient.connectingTo(server) // + .named(new String(getClientOscoreSetting().getSenderId())) // + .dontSendEndpointName() // + .using(getClientOscoreSetting()).build(); + + // Add client credentials to the server + server.getSecurityStore().add(SecurityInfo.newOscoreInfo(client.getEndpointName(), getServerOscoreSetting())); + + // Check client is not registered + assertThat(client).isNotRegisteredAt(server); + + // Start it and wait for registration + client.start(); + server.waitForNewRegistrationOf(client); + + // Check client is well registered + assertThat(client).isRegisteredAt(server); + Registration registration = server.getRegistrationFor(client); + + // check we can send request to client. + ReadResponse response = server.send(registration, new ReadRequest(3, 0, 1), 500); + assertThat(response.isSuccess()).isTrue(); + } + @Test public void registered_device_with_oscore_to_server_with_oscore_then_removed_security_info_then_server_fails_to_send_request() throws NonUniqueSecurityInfoException, InterruptedException { diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/PskTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/PskTest.java index 4ec6a1ed30..f628105e35 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/PskTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/PskTest.java @@ -140,6 +140,39 @@ public void registered_device_with_psk_to_server_with_psk(Protocol givenProtocol assertThat(response.isSuccess()).isTrue(); } + @TestAllTransportLayer + public void registered_device_with_psk_to_server_with_psk_without_endpointname(Protocol givenProtocol, + String givenClientEndpointProvider, String givenServerEndpointProvider) + throws NonUniqueSecurityInfoException, InterruptedException { + + // Create PSK server & start it + server = givenServer.build(); // default server support PSK + server.start(); + + // Create PSK Client + client = givenClient.connectingTo(server).named(GOOD_PSK_ID).dontSendEndpointName() + .usingPsk(GOOD_PSK_ID, GOOD_PSK_KEY).build(); + + // Add client credentials to the server + server.getSecurityStore() + .add(SecurityInfo.newPreSharedKeyInfo(client.getEndpointName(), GOOD_PSK_ID, GOOD_PSK_KEY)); + + // Check client is not registered + assertThat(client).isNotRegisteredAt(server); + + // Start it and wait for registration + client.start(); + server.waitForNewRegistrationOf(client); + + // Check client is well registered + assertThat(client).isRegisteredAt(server); + Registration registration = server.getRegistrationFor(client); + + // check we can send request to client. + ReadResponse response = server.send(registration, new ReadRequest(3, 0, 1), 500); + assertThat(response.isSuccess()).isTrue(); + } + @TestAllTransportLayer public void register_update_deregister_reregister_device_with_psk_to_server_with_psk(Protocol givenProtocol, String givenClientEndpointProvider, String givenServerEndpointProvider) diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/RpkTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/RpkTest.java index 267204df48..17ce20f3f6 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/RpkTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/RpkTest.java @@ -31,9 +31,11 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.integration.tests.util.Failure; import org.eclipse.leshan.integration.tests.util.LeshanTestClient; import org.eclipse.leshan.integration.tests.util.LeshanTestClientBuilder; import org.eclipse.leshan.integration.tests.util.LeshanTestServer; @@ -128,6 +130,36 @@ public void registered_device_with_rpk_to_server_with_rpk(Protocol givenProtocol assertThat(response.isSuccess()).isTrue(); } + @TestAllTransportLayer + public void registered_device_with_rpk_to_server_with_rpk_without_endpointname(Protocol givenProtocol, + String givenClientEndpointProvider, String givenServerEndpointProvider) + throws NonUniqueSecurityInfoException, InterruptedException { + // Create RPK server & start it + server = givenServer.using(serverPublicKey, serverPrivateKey).build(); + server.start(); + + // Create RPK Client + client = givenClient.connectingTo(server) // + .using(clientPublicKey, clientPrivateKey)// + .trusting(serverPublicKey)// + .dontSendEndpointName() // + .build(); + + // Add client credentials to the server + server.getSecurityStore().add(SecurityInfo.newRawPublicKeyInfo(client.getEndpointName(), clientPublicKey)); + + // Check client is not registered + assertThat(client).isNotRegisteredAt(server); + + // Start it and wait for registration + client.start(); + Failure failure = client.waitForRegistrationFailureTo(server); + assertThat(failure).failedWith(ResponseCode.FORBIDDEN); + + // Check we are registered with the expected attributes + assertThat(client).isNotRegisteredAt(server); + } + @TestAllTransportLayer public void registered_device_with_bad_rpk_to_server_with_rpk(Protocol givenProtocol, String givenClientEndpointProvider, String givenServerEndpointProvider) diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/X509Test.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/X509Test.java index 4f5d82ca1e..36e26e67a1 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/X509Test.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/security/X509Test.java @@ -196,6 +196,43 @@ public void registered_device_with_x509cert_to_server_with_x509cert(Protocol giv assertThat(response.isSuccess()).isTrue(); } + @TestAllTransportLayer + public void registered_device_with_x509cert_to_server_with_x509cert_without_endpointname(Protocol givenProtocol, + String givenClientEndpointProvider, String givenServerEndpointProvider) + throws NonUniqueSecurityInfoException, CertificateEncodingException, InterruptedException { + + // Create X509 server & start it + server = givenServer // + .actingAsServerOnly()// + .using(serverX509CertSignedByRoot, serverPrivateKeyFromCert)// + .trusting(trustedCertificatesByServer).build(); + server.start(); + + // Create X509 Client + client = givenClient.connectingTo(server) // + .dontSendEndpointName() // by default LeshanClientTest is using CN of certificate + .using(clientX509CertSignedByRoot, clientPrivateKeyFromCert)// + .trusting(serverX509CertSignedByRoot).build(); + + // Add client credentials to the server + server.getSecurityStore().add(SecurityInfo.newX509CertInfo(client.getEndpointName())); + + // Check client is not registered + assertThat(client).isNotRegisteredAt(server); + + // Start it and wait for registration + client.start(); + server.waitForNewRegistrationOf(client); + + // Check client is well registered + assertThat(client).isRegisteredAt(server); + Registration registration = server.getRegistrationFor(client); + + // check we can send request to client. + ReadResponse response = server.send(registration, new ReadRequest(3, 0, 1), 500); + assertThat(response.isSuccess()).isTrue(); + } + @TestAllTransportLayer public void registered_device_with_x509cert_to_server_with_self_signed_x509cert(Protocol givenProtocol, String givenClientEndpointProvider, String givenServerEndpointProvider) diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/Credentials.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/Credentials.java index 67b3866e78..409c4438da 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/Credentials.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/Credentials.java @@ -54,7 +54,7 @@ public class Credentials { public static final byte[] OSCORE_MASTER_SECRET = Hex.decodeHex("1234567890".toCharArray()); public static final byte[] OSCORE_MASTER_SALT = Hex.decodeHex("0987654321".toCharArray()); - public static final byte[] OSCORE_SENDER_ID = Hex.decodeHex("ABCDEF".toCharArray()); + public static final byte[] OSCORE_SENDER_ID = "secret".getBytes(); public static final byte[] OSCORE_RECIPIENT_ID = Hex.decodeHex("FEDCBA".toCharArray()); public static final byte[] OSCORE_BOOTSTRAP_MASTER_SECRET = Hex.decodeHex("BB1234567890".toCharArray()); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/Failure.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/Failure.java new file mode 100644 index 0000000000..d4a4827494 --- /dev/null +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/Failure.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2024 Semtech 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: + * Semtech - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.integration.tests.util; + +import org.eclipse.leshan.core.ResponseCode; + +public class Failure { + private final Exception exception; + private final ResponseCode code; + + public Failure(Exception exception, ResponseCode code) { + this.exception = exception; + this.code = code; + } + + public ResponseCode getCode() { + return code; + } + + public Exception getException() { + return exception; + } + + @Override + public String toString() { + return String.format("Failure [exception=%s, code=%s]", exception, code); + } +} diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestBootstrapServer.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestBootstrapServer.java index e11a74dc1d..afe0f30e54 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestBootstrapServer.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestBootstrapServer.java @@ -39,6 +39,7 @@ import org.eclipse.leshan.core.node.codec.LwM2mEncoder; import org.eclipse.leshan.core.request.DownlinkBootstrapRequest; import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.servers.security.EditableSecurityStore; import org.eclipse.leshan.servers.security.ServerSecurityInfo; import org.mockito.ArgumentCaptor; @@ -50,14 +51,15 @@ public class LeshanTestBootstrapServer extends LeshanBootstrapServer { private BootstrapSessionListener bootstrapSession; public LeshanTestBootstrapServer(LwM2mBootstrapServerEndpointsProvider endpointsProvider, - BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, - LwM2mDecoder decoder, LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, - BootstrapSecurityStore securityStore, ServerSecurityInfo serverSecurityInfo, // + BootstrapSessionManager bsSessionManager, ServerEndpointNameProvider endpointNameProvider, + BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, LwM2mDecoder decoder, + LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, BootstrapSecurityStore securityStore, + ServerSecurityInfo serverSecurityInfo, // // arguments only needed for LeshanTestBootstrapServer EditableBootstrapConfigStore configStore, EditableSecurityStore editableSecurityStore) { - super(endpointsProvider, bsSessionManager, bsHandlerFactory, encoder, decoder, linkParser, uriHandler, - securityStore, serverSecurityInfo); + super(endpointsProvider, bsSessionManager, endpointNameProvider, bsHandlerFactory, encoder, decoder, linkParser, + uriHandler, securityStore, serverSecurityInfo); // keep store reference for getter. this.configStore = configStore; this.securityStore = editableSecurityStore; diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestBootstrapServerBuilder.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestBootstrapServerBuilder.java index a32339bcb5..b0a8b94931 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestBootstrapServerBuilder.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestBootstrapServerBuilder.java @@ -44,6 +44,7 @@ import org.eclipse.leshan.core.request.DownlinkBootstrapRequest; import org.eclipse.leshan.integration.tests.util.cf.CertPair; import org.eclipse.leshan.integration.tests.util.cf.MapBasedCertificateProvider; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.servers.security.EditableSecurityStore; import org.eclipse.leshan.servers.security.ServerSecurityInfo; import org.eclipse.leshan.transport.californium.bsserver.endpoint.BootstrapServerProtocolProvider; @@ -82,9 +83,10 @@ public LeshanTestBootstrapServer build() { @Override protected LeshanTestBootstrapServer createBootstrapServer(LwM2mBootstrapServerEndpointsProvider endpointsProvider, - BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, - LwM2mDecoder decoder, LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, - BootstrapSecurityStore securityStore, ServerSecurityInfo serverSecurityInfo) { + BootstrapSessionManager bsSessionManager, ServerEndpointNameProvider endpointNameProvider, + BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, LwM2mDecoder decoder, + LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, BootstrapSecurityStore securityStore, + ServerSecurityInfo serverSecurityInfo) { // create endpoint provider. if (endpointsProvider == null) { @@ -108,8 +110,8 @@ protected LeshanTestBootstrapServer createBootstrapServer(LwM2mBootstrapServerEn } } - return new LeshanTestBootstrapServer(endpointsProvider, bsSessionManager, bsHandlerFactory, encoder, decoder, - linkParser, uriHandler, securityStore, serverSecurityInfo, // + return new LeshanTestBootstrapServer(endpointsProvider, bsSessionManager, endpointNameProvider, + bsHandlerFactory, encoder, decoder, linkParser, uriHandler, securityStore, serverSecurityInfo, // // arguments only needed for LeshanTestBootstrapServer configStore, editableSecurityStore); } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestClient.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestClient.java index 6bcd531794..1b1da3d4d9 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestClient.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestClient.java @@ -20,7 +20,6 @@ import static org.eclipse.leshan.integration.tests.util.assertion.Assertions.assertArg; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNotNull; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -40,6 +39,7 @@ import org.eclipse.leshan.client.bootstrap.BootstrapConsistencyChecker; import org.eclipse.leshan.client.endpoint.LwM2mClientEndpoint; import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; +import org.eclipse.leshan.client.engine.ClientEndpointNameProvider; import org.eclipse.leshan.client.engine.RegistrationEngineFactory; import org.eclipse.leshan.client.notification.NotificationDataStore; import org.eclipse.leshan.client.observer.LwM2mClientObserver; @@ -47,6 +47,7 @@ import org.eclipse.leshan.client.send.DataSender; import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.client.util.LinkFormatHelper; +import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.endpoint.EndPointUriHandler; import org.eclipse.leshan.core.endpoint.EndpointUri; import org.eclipse.leshan.core.link.LinkSerializer; @@ -67,19 +68,19 @@ public class LeshanTestClient extends LeshanClient { private final ReverseProxy proxy; private NotificationDataStore notificationDataStore; - public LeshanTestClient(String endpoint, List objectEnablers, - List dataSenders, List trustStore, RegistrationEngineFactory engineFactory, - BootstrapConsistencyChecker checker, Map additionalAttributes, - Map bsAdditionalAttributes, LwM2mEncoder encoder, LwM2mDecoder decoder, - ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, LinkFormatHelper linkFormatHelper, - LwM2mAttributeParser attributeParser, EndPointUriHandler uriHandler, + public LeshanTestClient(ClientEndpointNameProvider endpointNameProvider, + List objectEnablers, List dataSenders, + List trustStore, RegistrationEngineFactory engineFactory, BootstrapConsistencyChecker checker, + Map additionalAttributes, Map bsAdditionalAttributes, LwM2mEncoder encoder, + LwM2mDecoder decoder, ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, + LinkFormatHelper linkFormatHelper, LwM2mAttributeParser attributeParser, EndPointUriHandler uriHandler, LwM2mClientEndpointsProvider endpointsProvider, ReverseProxy proxy) { - super(endpoint, objectEnablers, dataSenders, trustStore, engineFactory, checker, additionalAttributes, - bsAdditionalAttributes, encoder, decoder, sharedExecutor, linkSerializer, linkFormatHelper, - attributeParser, uriHandler, endpointsProvider); + super(endpointNameProvider, objectEnablers, dataSenders, trustStore, engineFactory, checker, + additionalAttributes, bsAdditionalAttributes, encoder, decoder, sharedExecutor, linkSerializer, + linkFormatHelper, attributeParser, uriHandler, endpointsProvider); // Store some internal attribute - this.endpointName = endpoint; + this.endpointName = endpointNameProvider.getEndpointName(); this.proxy = proxy; @@ -147,6 +148,25 @@ public void waitForRegistrationTo(LeshanTestServer server, long timeout, TimeUni inOrder.verifyNoMoreInteractions(); } + public Failure waitForRegistrationFailureTo(LeshanTestServer server) { + return waitForRegistrationFailureTo(server, 1, TimeUnit.SECONDS); + } + + public Failure waitForRegistrationFailureTo(LeshanTestServer server, long timeout, TimeUnit unit) { + final ArgumentCaptor cExp = ArgumentCaptor.forClass(Exception.class); + final ArgumentCaptor cCode = ArgumentCaptor.forClass(ResponseCode.class); + + inOrder.verify(clientObserver, timeout(unit.toMillis(timeout)).times(1)).onRegistrationStarted(assertArg( // + s -> assertThat(isServerIdentifiedByUri(server, s.getUri()))), // + isNotNull()); + inOrder.verify(clientObserver, timeout(unit.toMillis(timeout)).times(1)).onRegistrationFailure(assertArg( // + s -> assertThat(isServerIdentifiedByUri(server, s.getUri()))), // + notNull(), cCode.capture(), any(), cExp.capture()); + inOrder.verifyNoMoreInteractions(); + + return new Failure(cExp.getValue(), cCode.getValue()); + } + public void waitForUpdateTo(LeshanTestServer server, long timeout, TimeUnit unit) { inOrder.verify(clientObserver, timeout(unit.toMillis(timeout)).times(1)).onUpdateStarted(assertArg( // s -> assertThat(isServerIdentifiedByUri(server, s.getUri()))), // @@ -224,8 +244,9 @@ public void waitForBootstrapSuccess(LeshanBootstrapServer server, long timeout, notNull()); } - public Exception waitForBootstrapFailure(LeshanBootstrapServer server, long timeout, TimeUnit unit) { - final ArgumentCaptor c = ArgumentCaptor.forClass(Exception.class); + public Failure waitForBootstrapFailure(LeshanBootstrapServer server, long timeout, TimeUnit unit) { + final ArgumentCaptor cExp = ArgumentCaptor.forClass(Exception.class); + final ArgumentCaptor cCode = ArgumentCaptor.forClass(ResponseCode.class); inOrder.verify(clientObserver, timeout(unit.toMillis(timeout)).times(1)).onBootstrapStarted(assertArg( // s -> assertThat(server.getEndpoints()) // @@ -238,10 +259,9 @@ public Exception waitForBootstrapFailure(LeshanBootstrapServer server, long time .filteredOn(ep -> ep.getURI().toString().equals(s.getUri())) // .hasSize(1)), // notNull(), // - isNull(), // TODO we should be able to check response code - any(), // - c.capture()); - return c.getValue(); + cCode.capture(), any(), // + cExp.capture()); + return new Failure(cExp.getValue(), cCode.getValue()); } private boolean isServerIdentifiedByUri(LeshanTestServer server, String expectedUri) { diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestClientBuilder.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestClientBuilder.java index d262ab577f..19979f01e3 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestClientBuilder.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestClientBuilder.java @@ -41,6 +41,9 @@ import org.eclipse.leshan.client.LeshanClientBuilder; import org.eclipse.leshan.client.bootstrap.BootstrapConsistencyChecker; import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; +import org.eclipse.leshan.client.engine.ClientEndpointNameProvider; +import org.eclipse.leshan.client.engine.DefaultClientEndpointNameProvider; +import org.eclipse.leshan.client.engine.DefaultClientEndpointNameProvider.Mode; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.engine.RegistrationEngineFactory; import org.eclipse.leshan.client.object.Device; @@ -90,6 +93,7 @@ public class LeshanTestClientBuilder extends LeshanClientBuilder { private static final Random r = new Random(); private String endpointName; + private Mode endpointNameMode = Mode.ALWAYS; private Protocol protocolToUse; private LeshanServer server; private LeshanBootstrapServer bootstrapServer; @@ -225,25 +229,29 @@ public boolean isSupported(ContentFormat format) { } @Override - protected LeshanTestClient createLeshanClient(String endpoint, List objectEnablers, - List dataSenders, List trustStore, RegistrationEngineFactory engineFactory, - BootstrapConsistencyChecker checker, Map additionalAttributes, - Map bsAdditionalAttributes, LwM2mEncoder encoder, LwM2mDecoder decoder, - ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, LinkFormatHelper linkFormatHelper, - LwM2mAttributeParser attributeParser, EndPointUriHandler uriHandler, + protected LeshanTestClient createLeshanClient(ClientEndpointNameProvider endpointNameProvider, + List objectEnablers, List dataSenders, + List trustStore, RegistrationEngineFactory engineFactory, BootstrapConsistencyChecker checker, + Map additionalAttributes, Map bsAdditionalAttributes, LwM2mEncoder encoder, + LwM2mDecoder decoder, ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, + LinkFormatHelper linkFormatHelper, LwM2mAttributeParser attributeParser, EndPointUriHandler uriHandler, LwM2mClientEndpointsProvider endpointsProvider) { - String endpointName; + + // custom behavior for endpoint name provider + ClientEndpointNameProvider testEndpointNameProvider; if (this.endpointName != null) { - endpointName = this.endpointName; + testEndpointNameProvider = new DefaultClientEndpointNameProvider(this.endpointName, this.endpointNameMode); } else if (clientCertificate != null) { X500Principal subjectDN = clientCertificate.getSubjectX500Principal(); - endpointName = X509CertUtil.getPrincipalField(subjectDN, "CN"); + testEndpointNameProvider = new DefaultClientEndpointNameProvider( + X509CertUtil.getPrincipalField(subjectDN, "CN"), this.endpointNameMode); } else { - endpointName = endpoint; + testEndpointNameProvider = new DefaultClientEndpointNameProvider(endpointNameProvider.getEndpointName(), + this.endpointNameMode); } - return new LeshanTestClient(endpointName, objectEnablers, dataSenders, trustStore, engineFactory, checker, - additionalAttributes, bsAdditionalAttributes, encoder, decoder, sharedExecutor, linkSerializer, + return new LeshanTestClient(testEndpointNameProvider, objectEnablers, dataSenders, trustStore, engineFactory, + checker, additionalAttributes, bsAdditionalAttributes, encoder, decoder, sharedExecutor, linkSerializer, linkFormatHelper, attributeParser, uriHandler, endpointsProvider, proxy); } @@ -319,6 +327,16 @@ public LeshanTestClientBuilder named(String endpointName) { return this; } + public LeshanTestClientBuilder dontSendEndpointName() { + this.endpointNameMode = Mode.NEVER; + return this; + } + + public LeshanTestClientBuilder sendEndpointNameIfNecessary() { + this.endpointNameMode = Mode.IF_NECESSARY; + return this; + } + public LeshanTestClientBuilder using(Protocol protocol) { this.protocolToUse = protocol; return this; diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestServer.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestServer.java index 7aac044663..52dae44a7e 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestServer.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestServer.java @@ -59,6 +59,7 @@ import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.security.Authorizer; import org.eclipse.leshan.server.send.SendListener; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.servers.security.EditableSecurityStore; import org.eclipse.leshan.servers.security.SecurityStore; import org.eclipse.leshan.servers.security.ServerSecurityInfo; @@ -82,10 +83,12 @@ public LeshanTestServer(LwM2mServerEndpointsProvider endpointsProvider, Registra LwM2mDecoder decoder, boolean noQueueMode, ClientAwakeTimeProvider awakeTimeProvider, RegistrationIdProvider registrationIdProvider, RegistrationDataExtractor registrationDataExtractor, LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, ServerSecurityInfo serverSecurityInfo, - boolean updateRegistrationOnNotification, boolean updateRegistrationOnSend) { + ServerEndpointNameProvider endponNameProvider, boolean updateRegistrationOnNotification, + boolean updateRegistrationOnSend) { super(endpointsProvider, registrationStore, securityStore, authorizer, modelProvider, encoder, decoder, noQueueMode, awakeTimeProvider, registrationIdProvider, registrationDataExtractor, - updateRegistrationOnNotification, updateRegistrationOnSend, linkParser, uriHandler, serverSecurityInfo); + updateRegistrationOnNotification, updateRegistrationOnSend, linkParser, uriHandler, serverSecurityInfo, + endponNameProvider); if (securityStore != null && !(securityStore instanceof EditableSecurityStore)) { throw new IllegalStateException( diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestServerBuilder.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestServerBuilder.java index da52cdad7a..c4669a5b1b 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestServerBuilder.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/LeshanTestServerBuilder.java @@ -49,6 +49,7 @@ import org.eclipse.leshan.server.registration.RegistrationIdProvider; import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.security.Authorizer; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.servers.security.EditableSecurityStore; import org.eclipse.leshan.servers.security.SecurityStore; import org.eclipse.leshan.servers.security.ServerSecurityInfo; @@ -94,7 +95,8 @@ protected LeshanTestServer createServer(LwM2mServerEndpointsProvider endpointsPr ClientAwakeTimeProvider awakeTimeProvider, RegistrationIdProvider registrationIdProvider, RegistrationDataExtractor registrationDataExtractor, LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, ServerSecurityInfo serverSecurityInfo, - boolean updateRegistrationOnNotification, boolean updateRegistrationOnSend) { + ServerEndpointNameProvider endpointNameProvider, boolean updateRegistrationOnNotification, + boolean updateRegistrationOnSend) { // create endpoint provider. if (endpointsProvider == null) { @@ -121,7 +123,8 @@ protected LeshanTestServer createServer(LwM2mServerEndpointsProvider endpointsPr } return new LeshanTestServer(endpointsProvider, registrationStore, securityStore, authorizer, modelProvider, encoder, decoder, noQueueMode, awakeTimeProvider, registrationIdProvider, registrationDataExtractor, - linkParser, uriHandler, serverSecurityInfo, updateRegistrationOnNotification, updateRegistrationOnSend); + linkParser, uriHandler, serverSecurityInfo, endpointNameProvider, updateRegistrationOnNotification, + updateRegistrationOnSend); } public static LeshanTestServerBuilder givenServerUsing(Protocol protocolToUse) { diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/assertion/Assertions.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/assertion/Assertions.java index a5907e179c..0fcc2fe7c6 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/assertion/Assertions.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/assertion/Assertions.java @@ -21,6 +21,7 @@ import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.integration.tests.util.Failure; import org.eclipse.leshan.integration.tests.util.LeshanTestClient; import org.mockito.hamcrest.MockitoHamcrest; @@ -50,4 +51,8 @@ public static DiscoverResponseAssert assertThat(DiscoverResponse actual) { public static LinkArrayAssert assertThat(Link[] actual) { return new LinkArrayAssert(actual); } + + public static FailureAssert assertThat(Failure actual) { + return new FailureAssert(actual); + } } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/assertion/FailureAssert.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/assertion/FailureAssert.java new file mode 100644 index 0000000000..782ce1a8fd --- /dev/null +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/assertion/FailureAssert.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2024 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.integration.tests.util.assertion; + +import org.assertj.core.api.AbstractAssert; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.integration.tests.util.Failure; + +public class FailureAssert extends AbstractAssert { + + public FailureAssert(Failure actual) { + super(actual, FailureAssert.class); + } + + public FailureAssert failedWith(ResponseCode expectedCode) { + isNotNull(); + + if (actual.getCode() == null) { + failWithMessage("Code Failure is expected and so MUST NOT be "); + } + if (!actual.getCode().equals(expectedCode)) { + failWithMessage("Expected <%s> ResponseCode for <%s> failure", expectedCode, actual); + } + return this; + } + + public FailureAssert failedWith(Class expectedException) { + isNotNull(); + + if (actual.getException() == null) { + failWithMessage("Exception Failure is expected and so MUST NOT be "); + } + objects.assertIsExactlyInstanceOf(info, actual.getException(), expectedException); + return this; + } +} diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapHandlerFactory.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapHandlerFactory.java index a193b71217..07ac8c07af 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapHandlerFactory.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapHandlerFactory.java @@ -16,6 +16,7 @@ package org.eclipse.leshan.bsserver; import org.eclipse.leshan.bsserver.request.BootstrapDownlinkRequestSender; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; /** * Creates {@link BootstrapHandler}. @@ -29,9 +30,10 @@ public interface BootstrapHandlerFactory { * * @param sender the class responsible to send LWM2M request during a bootstrapSession. * @param sessionManager the manager responsible to handle bootstrap session. + * @param endpointNameProvider must guess endpoint name if missing and if possible else return null * @param listener a listener of bootstrap session life-cycle. * @return the new {@link BootstrapHandler}. */ BootstrapHandler create(BootstrapDownlinkRequestSender sender, BootstrapSessionManager sessionManager, - BootstrapSessionListener listener); + ServerEndpointNameProvider endpointNameProvider, BootstrapSessionListener listener); } diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapSession.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapSession.java index 324ce402b0..3836d0fbdd 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapSession.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapSession.java @@ -28,7 +28,7 @@ * Represent a single Bootstrapping session. * * Should be created by {@link BootstrapSessionManager} implementations in - * {@link BootstrapSessionManager#begin(BootstrapRequest, LwM2mPeer, EndpointUri)}. + * {@link BootstrapSessionManager#begin(String, BootstrapRequest, LwM2mPeer, EndpointUri)}. */ public interface BootstrapSession { diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapSessionManager.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapSessionManager.java index 64c0980985..807e436e66 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapSessionManager.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/BootstrapSessionManager.java @@ -96,16 +96,18 @@ public String toString() { * Starts a bootstrapping session for an endpoint. In particular, this is responsible for authorizing the endpoint * if applicable. * + * @param endpointName The endpoint name of the client. * @param request the bootstrap request which initiates the session. * @param client the transport information about the client. * * @return a BootstrapSession, possibly authorized. */ - public BootstrapSession begin(BootstrapRequest request, LwM2mPeer client, EndpointUri endpointUsed); + public BootstrapSession begin(String endpointName, BootstrapRequest request, LwM2mPeer client, + EndpointUri endpointUsed); /** - * Generally called after {@link #begin(BootstrapRequest, LwM2mPeer, EndpointUri)} to know if there is something to - * do on this device. + * Generally called after {@link #begin(String, BootstrapRequest, LwM2mPeer, EndpointUri)} to know if there is + * something to do on this device. * * @param bsSession the bootstrap session concerned. * @return true if there is a bootstrap requests to send for this client. diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapAuthorizer.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapAuthorizer.java index 5f6e0fe4ff..760dc0d6e5 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapAuthorizer.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapAuthorizer.java @@ -40,9 +40,10 @@ public DefaultBootstrapAuthorizer(BootstrapSecurityStore bsSecurityStore, Securi } @Override - public Authorization isAuthorized(BootstrapRequest request, LwM2mPeer client) { + public Authorization isAuthorized(String endpointName, BootstrapRequest request, LwM2mPeer client) { + if (bsSecurityStore != null && securityChecker != null) { - Iterator securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName()); + Iterator securityInfos = bsSecurityStore.getAllByEndpoint(endpointName); if (securityChecker.checkSecurityInfos(request.getEndpointName(), client, securityInfos)) { return Authorization.approved(); } else { diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapHandler.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapHandler.java index a9bed0889f..641f6a520b 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapHandler.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapHandler.java @@ -37,6 +37,8 @@ import org.eclipse.leshan.core.response.ResponseCallback; import org.eclipse.leshan.core.response.SendableResponse; import org.eclipse.leshan.core.util.Validate; +import org.eclipse.leshan.servers.DefaultServerEndpointNameProvider; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,19 +66,21 @@ public class DefaultBootstrapHandler implements BootstrapHandler { protected final ConcurrentHashMap onGoingSession = new ConcurrentHashMap<>(); protected final BootstrapSessionManager sessionManager; protected final BootstrapSessionListener listener; + protected final ServerEndpointNameProvider endpointNameProvider; public DefaultBootstrapHandler(BootstrapDownlinkRequestSender sender, BootstrapSessionManager sessionManager, - BootstrapSessionListener listener) { - this(sender, sessionManager, listener, DEFAULT_TIMEOUT); + ServerEndpointNameProvider endpointNameProvider, BootstrapSessionListener listener) { + this(sender, sessionManager, new DefaultServerEndpointNameProvider(), listener, DEFAULT_TIMEOUT); } public DefaultBootstrapHandler(BootstrapDownlinkRequestSender sender, BootstrapSessionManager sessionManager, - BootstrapSessionListener listener, long requestTimeout) { + ServerEndpointNameProvider endpointNameProvider, BootstrapSessionListener listener, long requestTimeout) { Validate.notNull(sender); Validate.notNull(sessionManager); Validate.notNull(listener); this.sender = sender; this.sessionManager = sessionManager; + this.endpointNameProvider = endpointNameProvider; this.listener = listener; this.requestTimeout = requestTimeout; } @@ -84,11 +88,19 @@ public DefaultBootstrapHandler(BootstrapDownlinkRequestSender sender, BootstrapS @Override public SendableResponse bootstrap(LwM2mPeer sender, BootstrapRequest request, EndpointUri endpointUsed) { + // Extract Endpoint Name String endpoint = request.getEndpointName(); + if (endpoint == null) { + // find endpoint name from identity + endpoint = endpointNameProvider.getEndpointName(sender.getIdentity()); + if (endpoint == null) { + return new SendableResponse<>(BootstrapResponse.badRequest("endpoint name missing")); + } + } // Start session, checking the BS credentials final BootstrapSession session; - session = sessionManager.begin(request, sender, endpointUsed); + session = sessionManager.begin(endpoint, request, sender, endpointUsed); listener.sessionInitiated(request, sender); if (!session.isAuthorized()) { diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapSession.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapSession.java index 405ba0cd50..4be9bb7df0 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapSession.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapSession.java @@ -56,33 +56,37 @@ public class DefaultBootstrapSession implements BootstrapSession { * {@link BootstrapRequest#getPreferredContentFormat()} or {@link ContentFormat#TLV} content format if there is no * preferences and using System.currentTimeMillis() to set the creation time. * + * @param endpointName The endpoint name of the client. * @param request The bootstrap request which initiate the session. * @param client The transport layer information about client. * @param authorized True if device is authorized to bootstrap. * @param applicationData Data that could be attached to a session. */ - public DefaultBootstrapSession(BootstrapRequest request, LwM2mPeer client, boolean authorized, + public DefaultBootstrapSession(String endpointName, BootstrapRequest request, LwM2mPeer client, boolean authorized, Map applicationData, EndpointUri endpointUsed) { - this(request, client, authorized, null, applicationData, endpointUsed); + this(endpointName, request, client, authorized, null, applicationData, endpointUsed); } /** * Create a {@link DefaultBootstrapSession} using System.currentTimeMillis() to set the creation time. * + * @param endpointName The endpoint name of the client. * @param request The bootstrap request which initiate the session. * @param client The transport layer information about client. * @param authorized True if device is authorized to bootstrap. * @param contentFormat The content format to use to write object. * @param applicationData Data that could be attached to a session. */ - public DefaultBootstrapSession(BootstrapRequest request, LwM2mPeer client, boolean authorized, + public DefaultBootstrapSession(String endpointName, BootstrapRequest request, LwM2mPeer client, boolean authorized, ContentFormat contentFormat, Map applicationData, EndpointUri endpointUsed) { - this(request, client, authorized, contentFormat, applicationData, System.currentTimeMillis(), endpointUsed); + this(endpointName, request, client, authorized, contentFormat, applicationData, System.currentTimeMillis(), + endpointUsed); } /** * Create a {@link DefaultBootstrapSession}. * + * @param endpointName The endpoint name of the client. * @param request The bootstrap request which initiate the session. * @param client The transport layer information about client. * @param authorized True if device is authorized to bootstrap. @@ -90,13 +94,13 @@ public DefaultBootstrapSession(BootstrapRequest request, LwM2mPeer client, boole * @param applicationData Data that could be attached to a session. * @param creationTime The creation time of this session in ms. */ - public DefaultBootstrapSession(BootstrapRequest request, LwM2mPeer client, boolean authorized, + public DefaultBootstrapSession(String endpointName, BootstrapRequest request, LwM2mPeer client, boolean authorized, ContentFormat contentFormat, Map applicationData, long creationTime, EndpointUri endpointUsed) { Validate.notNull(request); this.id = RandomStringUtils.random(10, true, true); this.request = request; - this.endpoint = request.getEndpointName(); + this.endpoint = endpointName; this.client = client; this.endpointUsed = endpointUsed; this.authorized = authorized; diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapSessionManager.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapSessionManager.java index 26fbb66fa7..ca9615321c 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapSessionManager.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/DefaultBootstrapSessionManager.java @@ -74,10 +74,11 @@ public DefaultBootstrapSessionManager(BootstrapTaskProvider tasksProvider, } @Override - public BootstrapSession begin(BootstrapRequest request, LwM2mPeer client, EndpointUri endpointUsed) { - Authorization authorization = authorizer.isAuthorized(request, client); - DefaultBootstrapSession session = new DefaultBootstrapSession(request, client, authorization.isApproved(), - authorization.getApplicationData(), endpointUsed); + public BootstrapSession begin(String endpointName, BootstrapRequest request, LwM2mPeer client, + EndpointUri endpointUsed) { + Authorization authorization = authorizer.isAuthorized(endpointName, request, client); + DefaultBootstrapSession session = new DefaultBootstrapSession(endpointName, request, client, + authorization.isApproved(), authorization.getApplicationData(), endpointUsed); LOG.trace("Bootstrap session started : {}", session); return session; diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/LeshanBootstrapServer.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/LeshanBootstrapServer.java index d89b53d6ff..d56b8e7b97 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/LeshanBootstrapServer.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/LeshanBootstrapServer.java @@ -38,6 +38,7 @@ import org.eclipse.leshan.core.node.codec.LwM2mDecoder; import org.eclipse.leshan.core.node.codec.LwM2mEncoder; import org.eclipse.leshan.core.util.Validate; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.servers.security.ServerSecurityInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,15 +62,17 @@ public class LeshanBootstrapServer { * {@link LeshanBootstrapServerBuilder} is the priviledged way to create a {@link LeshanBootstrapServer}. * * @param bsSessionManager manages life cycle of a bootstrap process + * @param endpointNameProvider must guess endpoint name if missing and if possible else return null * @param bsHandlerFactory responsible to create the {@link BootstrapHandler} * @param encoder encode used to encode request payload. * @param decoder decoder used to decode response payload. * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. */ public LeshanBootstrapServer(LwM2mBootstrapServerEndpointsProvider endpointsProvider, - BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, - LwM2mDecoder decoder, LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, - BootstrapSecurityStore securityStore, ServerSecurityInfo serverSecurityInfo) { + BootstrapSessionManager bsSessionManager, ServerEndpointNameProvider endpointNameProvider, + BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, LwM2mDecoder decoder, + LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, BootstrapSecurityStore securityStore, + ServerSecurityInfo serverSecurityInfo) { Validate.notNull(endpointsProvider, "endpoints provider must not be null"); Validate.notNull(bsSessionManager, "session manager must not be null"); @@ -83,7 +86,8 @@ public LeshanBootstrapServer(LwM2mBootstrapServerEndpointsProvider endpointsProv // create endpoints BootstrapServerEndpointToolbox toolbox = new BootstrapServerEndpointToolbox(decoder, encoder, linkParser, uriHandler); - BootstrapHandler bootstrapHandler = bsHandlerFactory.create(requestSender, bsSessionManager, dispatcher); + BootstrapHandler bootstrapHandler = bsHandlerFactory.create(requestSender, bsSessionManager, + endpointNameProvider, dispatcher); BootstrapUplinkRequestReceiver requestReceiver = createRequestReceiver(bootstrapHandler); endpointsProvider.createEndpoints(requestReceiver, toolbox, serverSecurityInfo, this); } diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/LeshanBootstrapServerBuilder.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/LeshanBootstrapServerBuilder.java index 58e56f20e6..811dc96ba8 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/LeshanBootstrapServerBuilder.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/LeshanBootstrapServerBuilder.java @@ -40,6 +40,8 @@ import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; import org.eclipse.leshan.core.node.codec.LwM2mDecoder; import org.eclipse.leshan.core.node.codec.LwM2mEncoder; +import org.eclipse.leshan.servers.DefaultServerEndpointNameProvider; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.servers.security.SecurityChecker; import org.eclipse.leshan.servers.security.ServerSecurityInfo; import org.slf4j.Logger; @@ -61,6 +63,7 @@ public class LeshanBootstrapServerBuilder { private BootstrapSessionManager sessionManager; private BootstrapHandlerFactory bootstrapHandlerFactory; private BootstrapAuthorizer authorizer; + private ServerEndpointNameProvider endpointNameProvider; private LwM2mBootstrapModelProvider modelProvider; @@ -271,6 +274,17 @@ public LeshanBootstrapServerBuilder setAuthorizer(BootstrapAuthorizer authorizer return this; } + /** + * Sets {@link ServerEndpointNameProvider} responsible to find client endpoint name if missing from client Identity. + *

+ * By default, {@link DefaultServerEndpointNameProvider} is used. + * + * @param endpointNameProvider the {@link ServerEndpointNameProvider} to set. + */ + public void setEndpointNameProvider(ServerEndpointNameProvider endpointNameProvider) { + this.endpointNameProvider = endpointNameProvider; + } + /** * By default LeshanBootstrapServer doesn't support any protocol. Users need to provide 1 or several * {@link LwM2mBootstrapServerEndpointsProvider} implementation. @@ -312,8 +326,9 @@ public LeshanBootstrapServer build() { bootstrapHandlerFactory = new BootstrapHandlerFactory() { @Override public BootstrapHandler create(BootstrapDownlinkRequestSender sender, - BootstrapSessionManager sessionManager, BootstrapSessionListener listener) { - return new DefaultBootstrapHandler(sender, sessionManager, listener); + BootstrapSessionManager sessionManager, ServerEndpointNameProvider endpointNameProvider, + BootstrapSessionListener listener) { + return new DefaultBootstrapHandler(sender, sessionManager, endpointNameProvider, listener); } }; @@ -326,6 +341,9 @@ public BootstrapHandler create(BootstrapDownlinkRequestSender sender, if (uriHandler == null) { uriHandler = new DefaultEndPointUriHandler(); } + if (endpointNameProvider == null) { + endpointNameProvider = new DefaultServerEndpointNameProvider(); + } // Handle class depending of Session Manager if (sessionManager == null) { @@ -368,8 +386,8 @@ public BootstrapHandler create(BootstrapDownlinkRequestSender sender, "authorizer is set but you also provide a custom SessionManager so this authorizer will not be used"); } } - return createBootstrapServer(endpointsProvider, sessionManager, bootstrapHandlerFactory, encoder, decoder, - linkParser, uriHandler, securityStore, + return createBootstrapServer(endpointsProvider, sessionManager, endpointNameProvider, bootstrapHandlerFactory, + encoder, decoder, linkParser, uriHandler, securityStore, new ServerSecurityInfo(privateKey, publicKey, certificateChain, trustedCertificates)); } @@ -387,10 +405,11 @@ public BootstrapHandler create(BootstrapDownlinkRequestSender sender, * @return the LWM2M Bootstrap server. */ protected LeshanBootstrapServer createBootstrapServer(LwM2mBootstrapServerEndpointsProvider endpointsProvider, - BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, - LwM2mDecoder decoder, LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, - BootstrapSecurityStore securityStore, ServerSecurityInfo serverSecurityInfo) { - return new LeshanBootstrapServer(endpointsProvider, bsSessionManager, bsHandlerFactory, encoder, decoder, - linkParser, uriHandler, securityStore, serverSecurityInfo); + BootstrapSessionManager bsSessionManager, ServerEndpointNameProvider endpointNameProvider, + BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, LwM2mDecoder decoder, + LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, BootstrapSecurityStore securityStore, + ServerSecurityInfo serverSecurityInfo) { + return new LeshanBootstrapServer(endpointsProvider, bsSessionManager, endpointNameProvider, bsHandlerFactory, + encoder, decoder, linkParser, uriHandler, securityStore, serverSecurityInfo); } } diff --git a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/security/BootstrapAuthorizer.java b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/security/BootstrapAuthorizer.java index d4ebaf76e3..38898dc881 100644 --- a/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/security/BootstrapAuthorizer.java +++ b/leshan-lwm2m-bsserver/src/main/java/org/eclipse/leshan/bsserver/security/BootstrapAuthorizer.java @@ -33,9 +33,11 @@ public interface BootstrapAuthorizer { * return Authorization.approved(myAppData); * * + * + * @param endpointName The endpoint name of the client. * @param request the request received * @param client the transport information about client which sent the request. * @return an {@link Authorization} status. */ - Authorization isAuthorized(BootstrapRequest request, LwM2mPeer client); + Authorization isAuthorized(String endpointName, BootstrapRequest request, LwM2mPeer client); } diff --git a/leshan-lwm2m-bsserver/src/test/java/org/eclipse/leshan/bsserver/BootstrapHandlerTest.java b/leshan-lwm2m-bsserver/src/test/java/org/eclipse/leshan/bsserver/BootstrapHandlerTest.java index 338e33497f..b106430ad1 100644 --- a/leshan-lwm2m-bsserver/src/test/java/org/eclipse/leshan/bsserver/BootstrapHandlerTest.java +++ b/leshan-lwm2m-bsserver/src/test/java/org/eclipse/leshan/bsserver/BootstrapHandlerTest.java @@ -47,6 +47,7 @@ import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.core.response.ResponseCallback; import org.eclipse.leshan.core.response.SendableResponse; +import org.eclipse.leshan.servers.DefaultServerEndpointNameProvider; import org.junit.jupiter.api.Test; public class BootstrapHandlerTest { @@ -63,7 +64,7 @@ public void error_if_not_authorized() { BootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(false, new InMemoryBootstrapConfigStore()); BootstrapHandler bsHandler = new DefaultBootstrapHandler(new MockRequestSender(Mode.ALWAYS_SUCCESS), - bsSessionManager, new BootstrapSessionDispatcher()); + bsSessionManager, new DefaultServerEndpointNameProvider(), new BootstrapSessionDispatcher()); // Try to bootstrap BootstrapResponse response = bsHandler @@ -86,7 +87,7 @@ public void bootstrap_success() throws InvalidConfigurationException { MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true, bsStore); BootstrapHandler bsHandler = new DefaultBootstrapHandler(requestSender, bsSessionManager, - new BootstrapSessionDispatcher()); + new DefaultServerEndpointNameProvider(), new BootstrapSessionDispatcher()); // Try to bootstrap SendableResponse sendableResponse = bsHandler.bootstrap( @@ -109,7 +110,7 @@ public void bootstrap_failed_because_of_sent_failure() throws InvalidConfigurati bsStore.add("endpoint", new BootstrapConfig()); MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true, bsStore); BootstrapHandler bsHandler = new DefaultBootstrapHandler(requestSender, bsSessionManager, - new BootstrapSessionDispatcher()); + new DefaultServerEndpointNameProvider(), new BootstrapSessionDispatcher()); // Try to bootstrap SendableResponse sendableResponse = bsHandler.bootstrap( @@ -134,7 +135,8 @@ public void two_bootstrap_at_the_same_time_not_allowed() bsStore.add("endpoint", new BootstrapConfig()); MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true, bsStore); BootstrapHandler bsHandler = new DefaultBootstrapHandler(requestSender, bsSessionManager, - new BootstrapSessionDispatcher(), DefaultBootstrapHandler.DEFAULT_TIMEOUT); + new DefaultServerEndpointNameProvider(), new BootstrapSessionDispatcher(), + DefaultBootstrapHandler.DEFAULT_TIMEOUT); // First bootstrap : which will not end (because of sender) SendableResponse first_response = bsHandler.bootstrap( @@ -242,8 +244,9 @@ public MockBootstrapSessionManager(boolean authorized, BootstrapConfigStore stor } @Override - public BootstrapSession begin(BootstrapRequest request, LwM2mPeer sender, EndpointUri endpointUsed) { - lastSession = new DefaultBootstrapSession(request, sender, authorized, null, endpointUsed); + public BootstrapSession begin(String endpointName, BootstrapRequest request, LwM2mPeer sender, + EndpointUri endpointUsed) { + lastSession = new DefaultBootstrapSession(endpointName, request, sender, authorized, null, endpointUsed); return lastSession; } diff --git a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/LeshanClient.java b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/LeshanClient.java index 9be3929339..cae3942c68 100644 --- a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/LeshanClient.java +++ b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/LeshanClient.java @@ -32,6 +32,7 @@ import org.eclipse.leshan.client.endpoint.DefaultEndpointsManager; import org.eclipse.leshan.client.endpoint.LwM2mClientEndpoint; import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; +import org.eclipse.leshan.client.engine.ClientEndpointNameProvider; import org.eclipse.leshan.client.engine.RegistrationEngine; import org.eclipse.leshan.client.engine.RegistrationEngineFactory; import org.eclipse.leshan.client.notification.DefaultNotificationStrategy; @@ -90,15 +91,15 @@ public class LeshanClient implements LwM2mClient { private final DataSenderManager dataSenderManager; private final NotificationManager notificationManager; - public LeshanClient(String endpoint, List objectEnablers, - List dataSenders, List trustStore, RegistrationEngineFactory engineFactory, - BootstrapConsistencyChecker checker, Map additionalAttributes, - Map bsAdditionalAttributes, LwM2mEncoder encoder, LwM2mDecoder decoder, - ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, LinkFormatHelper linkFormatHelper, - LwM2mAttributeParser attributeParser, EndPointUriHandler uriHandler, + public LeshanClient(ClientEndpointNameProvider endpointNameProvider, + List objectEnablers, List dataSenders, + List trustStore, RegistrationEngineFactory engineFactory, BootstrapConsistencyChecker checker, + Map additionalAttributes, Map bsAdditionalAttributes, LwM2mEncoder encoder, + LwM2mDecoder decoder, ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, + LinkFormatHelper linkFormatHelper, LwM2mAttributeParser attributeParser, EndPointUriHandler uriHandler, LwM2mClientEndpointsProvider endpointsProvider) { - Validate.notNull(endpoint); + // Validate.notNull(endpoint); Validate.notEmpty(objectEnablers); Validate.notNull(checker); @@ -120,8 +121,8 @@ public LeshanClient(String endpoint, List objectEn requestSender = createRequestSender(this.endpointsProvider); dataSenderManager = createDataSenderManager(dataSenders, rootEnabler, requestSender); - engine = engineFactory.createRegistratioEngine(endpoint, objectTree, endpointsManager, requestSender, - bootstrapHandler, observers, additionalAttributes, bsAdditionalAttributes, + engine = engineFactory.createRegistratioEngine(endpointNameProvider, objectTree, endpointsManager, + requestSender, bootstrapHandler, observers, additionalAttributes, bsAdditionalAttributes, getSupportedContentFormat(decoder, encoder), sharedExecutor, linkFormatHelper); DownlinkRequestReceiver requestReceiver = createRequestReceiver(bootstrapHandler, rootEnabler, objectTree, diff --git a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/LeshanClientBuilder.java b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/LeshanClientBuilder.java index e3588aec31..9b862bf678 100644 --- a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/LeshanClientBuilder.java +++ b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/LeshanClientBuilder.java @@ -29,6 +29,8 @@ import org.eclipse.leshan.client.bootstrap.DefaultBootstrapConsistencyChecker; import org.eclipse.leshan.client.endpoint.DefaultCompositeClientEndpointsProvider; import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; +import org.eclipse.leshan.client.engine.ClientEndpointNameProvider; +import org.eclipse.leshan.client.engine.DefaultClientEndpointNameProvider; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.engine.RegistrationEngine; import org.eclipse.leshan.client.engine.RegistrationEngineFactory; @@ -64,7 +66,7 @@ */ public class LeshanClientBuilder { - private final String endpoint; + private final ClientEndpointNameProvider endpointNameProvider; private List objectEnablers; private List dataSenders; @@ -110,7 +112,30 @@ public class LeshanClientBuilder { */ public LeshanClientBuilder(String endpoint) { Validate.notEmpty(endpoint); - this.endpoint = endpoint; + this.endpointNameProvider = new DefaultClientEndpointNameProvider(endpoint); + } + + /** + * Creates a new instance for setting the configuration options for a {@link LeshanClient} instance. + * + * The builder is initialized with the following default values: + *

    + *
  • local address: a local address and an ephemeral port (picked up during binding)
  • + *
  • object enablers: + *
      + *
    • Security(0) with one instance (DM server security): uri=coap://leshan.eclipseprojects.io:5683, + * mode=NoSec
    • + *
    • Server(1) with one instance (DM server): id=12345, lifetime=5minutes
    • + *
    • Device(3): manufacturer=Eclipse Leshan, modelNumber=model12345, serialNumber=12345
    • + *
    + *
  • + *
+ * + * @param endpointNameProvider The endpoint name provider for this client. + */ + public LeshanClientBuilder(ClientEndpointNameProvider endpointNameProvider) { + Validate.notNull(endpointNameProvider); + this.endpointNameProvider = endpointNameProvider; } /** @@ -346,7 +371,7 @@ public LeshanClient build() { uriHandler = new DefaultEndPointUriHandler(); } - return createLeshanClient(endpoint, objectEnablers, dataSenders, this.trustStore, engineFactory, + return createLeshanClient(endpointNameProvider, objectEnablers, dataSenders, this.trustStore, engineFactory, bootstrapConsistencyChecker, additionalAttributes, bsAdditionalAttributes, encoder, decoder, executor, linkSerializer, linkFormatHelper, attributeParser, uriHandler, endpointsProvider); } @@ -359,7 +384,7 @@ public LeshanClient build() { *

* See all the setters of this builder for more documentation about parameters. * - * @param endpoint The endpoint name for this client. + * @param endpointNameProvider The endpoint name provider for this client. * @param objectEnablers The list of object enablers. An enabler adds to support for a given LWM2M object to the * client. * @param trustStore The optional trust store for verifying X.509 server certificates. @@ -376,14 +401,14 @@ public LeshanClient build() { * * @return the new {@link LeshanClient} */ - protected LeshanClient createLeshanClient(String endpoint, List objectEnablers, - List dataSenders, List trustStore, RegistrationEngineFactory engineFactory, - BootstrapConsistencyChecker checker, Map additionalAttributes, - Map bsAdditionalAttributes, LwM2mEncoder encoder, LwM2mDecoder decoder, - ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, LinkFormatHelper linkFormatHelper, - LwM2mAttributeParser attributeParser, EndPointUriHandler uriHandler, + protected LeshanClient createLeshanClient(ClientEndpointNameProvider endpointNameProvider, + List objectEnablers, List dataSenders, + List trustStore, RegistrationEngineFactory engineFactory, BootstrapConsistencyChecker checker, + Map additionalAttributes, Map bsAdditionalAttributes, LwM2mEncoder encoder, + LwM2mDecoder decoder, ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, + LinkFormatHelper linkFormatHelper, LwM2mAttributeParser attributeParser, EndPointUriHandler uriHandler, LwM2mClientEndpointsProvider endpointsProvider) { - return new LeshanClient(endpoint, objectEnablers, dataSenders, trustStore, engineFactory, checker, + return new LeshanClient(endpointNameProvider, objectEnablers, dataSenders, trustStore, engineFactory, checker, additionalAttributes, bsAdditionalAttributes, encoder, decoder, sharedExecutor, linkSerializer, linkFormatHelper, attributeParser, uriHandler, endpointsProvider); } diff --git a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/ClientEndpointNameProvider.java b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/ClientEndpointNameProvider.java new file mode 100644 index 0000000000..ff6a5f4a53 --- /dev/null +++ b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/ClientEndpointNameProvider.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2024 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.client.engine; + +import org.eclipse.leshan.client.servers.ServerInfo; +import org.eclipse.leshan.core.request.UplinkRequest; + +/** + * Since LWM2M v1.1, endpoint name is optional in REGISTER and BOOTSTRAP request. An {@link ClientEndpointNameProvider} + * is determine the endpoint name value which should be used in Register/BootstrapRequest. + *

+ * + * @see + * @see DefaultClientEndpointNameProvider + */ +public interface ClientEndpointNameProvider { + + /** + * @return the default endpoint name + */ + String getEndpointName(); + + /** + * @return endpointName or null if it could(wanted) to be ignore for given kind of request. + */ + String getEndpointNameFor(ServerInfo clientIdentity, Class> requestType); +} diff --git a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultClientEndpointNameProvider.java b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultClientEndpointNameProvider.java new file mode 100644 index 0000000000..24c24b15a3 --- /dev/null +++ b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultClientEndpointNameProvider.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2024 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.client.engine; + +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.security.cert.X509Certificate; + +import org.eclipse.leshan.client.servers.ServerInfo; +import org.eclipse.leshan.core.request.UplinkRequest; +import org.eclipse.leshan.core.security.certificate.util.X509CertUtil; +import org.eclipse.leshan.core.util.Validate; + +/** + * This is default implementation of {@link ClientEndpointNameProvider}. + *

+ * This {@link ClientEndpointNameProvider} can control if Client Endpoint Name should be send + * + * @see + */ +public class DefaultClientEndpointNameProvider implements ClientEndpointNameProvider { + + public enum Mode { + /** + * Always provide/send endpoint name for all request. Most safe and interoperable mode. + */ + ALWAYS, + /** + * Never provide/send endpoint name for all request. Usefull for test but must probably not be used in + * production. + */ + NEVER, + /** + * Provide/send endpoint name for all request only if necessary. It should be not necessary if it can be guessed + * by Server. Allow to save some byte on corresponding request but could lead to some interoperability issue and + * also maybe some privacy one as your endpoint will be visible in clear (note this is already the case for X509 + * use case). + * + * @see + */ + IF_NECESSARY + } + + private final String endpoint; + private final Mode mode; + + public DefaultClientEndpointNameProvider(String endpointName) { + this(endpointName, Mode.ALWAYS); + } + + public DefaultClientEndpointNameProvider(String endpointName, Mode mode) { + Validate.notEmpty(endpointName); + Validate.notNull(mode); + this.endpoint = endpointName; + this.mode = mode; + } + + @Override + public String getEndpointName() { + return endpoint; + } + + @Override + public String getEndpointNameFor(ServerInfo serverInfo, Class> requestType) { + switch (mode) { + case ALWAYS: + return endpoint; + case NEVER: + return null; + case IF_NECESSARY: + String identifier = getClientSecurityProtocolIdentifier(serverInfo); + if (identifier != null && identifier.equals(endpoint)) { + return null; + } else { + return endpoint; + } + default: + throw new IllegalStateException(String.format("mode %s is not supported", mode)); + } + } + + protected String getClientSecurityProtocolIdentifier(ServerInfo serverInfo) { + if (serverInfo.useOscore) { + byte[] senderId = serverInfo.oscoreSetting.getSenderId(); + // Try to convert byte array in UTF8 String + try { + CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); + ByteBuffer buf = ByteBuffer.wrap(senderId); + return decoder.decode(buf).toString(); + } catch (CharacterCodingException e) { + return null; + } + } else { + switch (serverInfo.secureMode) { + case PSK: + return serverInfo.pskId; + case X509: + return X509CertUtil.extractCN(((X509Certificate) serverInfo.clientCertificate).getIssuerDN().getName()); + default: + return null; + } + } + } +} diff --git a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java index af00ac04c4..e3a2da4d22 100644 --- a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java +++ b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java @@ -103,7 +103,6 @@ private static enum Status { } // device state - private final String endpoint; private final ContentFormat preferredContentFormat; // used for bootstrap private final Set supportedContentFormats; private final Map additionalAttributes; @@ -119,6 +118,7 @@ private static enum Status { private final EndpointsManager endpointsManager; private final LwM2mClientObserver observer; private final LinkFormatHelper linkFormatHelper; + private final ClientEndpointNameProvider endpointNameProvider; // tasks stuff private boolean started = false; @@ -129,15 +129,15 @@ private static enum Status { private final ScheduledExecutorService schedExecutor; private final boolean attachedExecutor; - public DefaultRegistrationEngine(String endpoint, LwM2mObjectTree objectTree, EndpointsManager endpointsManager, - UplinkRequestSender requestSender, BootstrapHandler bootstrapState, LwM2mClientObserver observer, - Map additionalAttributes, Map bsAdditionalAttributes, - ScheduledExecutorService executor, long requestTimeoutInMs, long deregistrationTimeoutInMs, - int bootstrapSessionTimeoutInSec, int retryWaitingTimeInMs, Integer communicationPeriodInMs, - boolean reconnectOnUpdate, boolean resumeOnConnect, boolean useQueueMode, + public DefaultRegistrationEngine(ClientEndpointNameProvider endpointNameProvider, LwM2mObjectTree objectTree, + EndpointsManager endpointsManager, UplinkRequestSender requestSender, BootstrapHandler bootstrapState, + LwM2mClientObserver observer, Map additionalAttributes, + Map bsAdditionalAttributes, ScheduledExecutorService executor, long requestTimeoutInMs, + long deregistrationTimeoutInMs, int bootstrapSessionTimeoutInSec, int retryWaitingTimeInMs, + Integer communicationPeriodInMs, boolean reconnectOnUpdate, boolean resumeOnConnect, boolean useQueueMode, ContentFormat preferredContentFormat, Set supportedContentFormats, LinkFormatHelper linkFormatHelper) { - this.endpoint = endpoint; + this.endpointNameProvider = endpointNameProvider; this.objectEnablers = objectTree.getObjectEnablers(); this.bootstrapHandler = bootstrapState; this.endpointsManager = endpointsManager; @@ -228,7 +228,9 @@ private LwM2mServer clientInitiatedBootstrap() throws InterruptedException { // Send bootstrap request BootstrapRequest request = null; try { - request = new BootstrapRequest(endpoint, preferredContentFormat, bsAdditionalAttributes); + request = new BootstrapRequest( + endpointNameProvider.getEndpointNameFor(bootstrapServerInfo, BootstrapRequest.class), + preferredContentFormat, bsAdditionalAttributes); if (observer != null) { observer.onBootstrapStarted(bootstrapServer, request); } @@ -323,8 +325,9 @@ private Status register(LwM2mServer server) throws InterruptedException { Link[] links = linkFormatHelper.getClientDescription(objectEnablers.values(), null, ContentFormat.getOptionalContentFormatForClient(supportedContentFormats, lwM2mVersion)); - request = new RegisterRequest(endpoint, dmInfo.lifetime, lwM2mVersion.toString(), supportedBindingMode, - queueMode, null, links, additionalAttributes); + request = new RegisterRequest(endpointNameProvider.getEndpointNameFor(dmInfo, RegisterRequest.class), + dmInfo.lifetime, lwM2mVersion.toString(), supportedBindingMode, queueMode, null, links, + additionalAttributes); if (observer != null) { observer.onRegistrationStarted(server, request); } @@ -912,6 +915,6 @@ protected DmServerInfo selectServer(Map servers) { */ @Override public String getEndpoint() { - return endpoint; + return endpointNameProvider.getEndpointName(); } } diff --git a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java index 7e1f4864c5..50d0ed03e6 100644 --- a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java +++ b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java @@ -48,16 +48,16 @@ public DefaultRegistrationEngineFactory() { } @Override - public RegistrationEngine createRegistratioEngine(String endpoint, LwM2mObjectTree objectTree, - EndpointsManager endpointsManager, UplinkRequestSender requestSender, BootstrapHandler bootstrapState, - LwM2mClientObserver observer, Map additionalAttributes, + public RegistrationEngine createRegistratioEngine(ClientEndpointNameProvider endpointNameProvider, + LwM2mObjectTree objectTree, EndpointsManager endpointsManager, UplinkRequestSender requestSender, + BootstrapHandler bootstrapState, LwM2mClientObserver observer, Map additionalAttributes, Map bsAdditionalAttributes, Set supportedContentFormat, ScheduledExecutorService sharedExecutor, LinkFormatHelper linkFormatHelper) { - return new DefaultRegistrationEngine(endpoint, objectTree, endpointsManager, requestSender, bootstrapState, - observer, additionalAttributes, bsAdditionalAttributes, sharedExecutor, requestTimeoutInMs, - deregistrationTimeoutInMs, bootstrapSessionTimeoutInSec, retryWaitingTimeInMs, communicationPeriodInMs, - reconnectOnUpdate, resumeOnConnect, queueMode, preferredContentFormat, supportedContentFormat, - linkFormatHelper); + return new DefaultRegistrationEngine(endpointNameProvider, objectTree, endpointsManager, requestSender, + bootstrapState, observer, additionalAttributes, bsAdditionalAttributes, sharedExecutor, + requestTimeoutInMs, deregistrationTimeoutInMs, bootstrapSessionTimeoutInSec, retryWaitingTimeInMs, + communicationPeriodInMs, reconnectOnUpdate, resumeOnConnect, queueMode, preferredContentFormat, + supportedContentFormat, linkFormatHelper); } /** diff --git a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java index 7807b492ce..c63c953b64 100644 --- a/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java +++ b/leshan-lwm2m-client/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java @@ -32,9 +32,9 @@ */ public interface RegistrationEngineFactory { - RegistrationEngine createRegistratioEngine(String endpoint, LwM2mObjectTree objectTree, - EndpointsManager endpointsManager, UplinkRequestSender requestSender, BootstrapHandler bootstrapState, - LwM2mClientObserver observer, Map additionalAttributes, + RegistrationEngine createRegistratioEngine(ClientEndpointNameProvider endpointNameProvider, + LwM2mObjectTree objectTree, EndpointsManager endpointsManager, UplinkRequestSender requestSender, + BootstrapHandler bootstrapState, LwM2mClientObserver observer, Map additionalAttributes, Map bsAdditionalAttributes, Set supportedContentFormat, ScheduledExecutorService sharedExecutor, LinkFormatHelper linkFormatHelper); } diff --git a/leshan-lwm2m-core/src/main/java/org/eclipse/leshan/core/request/BootstrapRequest.java b/leshan-lwm2m-core/src/main/java/org/eclipse/leshan/core/request/BootstrapRequest.java index 31aa16e6ac..4ec702efb8 100644 --- a/leshan-lwm2m-core/src/main/java/org/eclipse/leshan/core/request/BootstrapRequest.java +++ b/leshan-lwm2m-core/src/main/java/org/eclipse/leshan/core/request/BootstrapRequest.java @@ -49,8 +49,6 @@ public BootstrapRequest(String endpointName, ContentFormat preferredFormat, public BootstrapRequest(String endpointName, ContentFormat preferredFormat, Map additionalAttributes, Object coapRequest) throws InvalidRequestException { super(coapRequest); - if (endpointName == null || endpointName.isEmpty()) - throw new InvalidRequestException("endpoint is mandatory"); this.endpointName = endpointName; if (additionalAttributes == null) diff --git a/leshan-lwm2m-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java b/leshan-lwm2m-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java index e0005af781..45f7ae344b 100644 --- a/leshan-lwm2m-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java +++ b/leshan-lwm2m-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java @@ -84,9 +84,6 @@ public RegisterRequest(String endpointName, Long lifetime, String lwVersion, Enu Boolean queueMode, String smsNumber, Link[] objectLinks, Map additionalAttributes, Object coapRequest) throws InvalidRequestException { super(coapRequest); - if (endpointName == null || endpointName.isEmpty()) - throw new InvalidRequestException("endpoint is mandatory"); - if (objectLinks == null || objectLinks.length == 0) throw new InvalidRequestException( "supported object list is mandatory and mandatory objects should be present for endpoint %s", diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServer.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServer.java index d5492b80f4..d9a31460f4 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServer.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServer.java @@ -75,6 +75,7 @@ import org.eclipse.leshan.server.security.Authorizer; import org.eclipse.leshan.server.send.SendHandler; import org.eclipse.leshan.server.send.SendService; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.servers.security.SecurityInfo; import org.eclipse.leshan.servers.security.SecurityStore; import org.eclipse.leshan.servers.security.ServerSecurityInfo; @@ -128,7 +129,8 @@ public class LeshanServer { * @param updateRegistrationOnNotification will activate registration update on observe notification. * @param updateRegistrationOnSend will activate registration update on Send Operation. * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. - * @param serverSecurityInfo credentials of the Server + * @param serverSecurityInfo credentials of the Server. + * @param endpointNameProvider try to find endpoint name from client identity. * @since 1.1 */ public LeshanServer(LwM2mServerEndpointsProvider endpointsProvider, RegistrationStore registrationStore, @@ -136,7 +138,8 @@ public LeshanServer(LwM2mServerEndpointsProvider endpointsProvider, Registration LwM2mDecoder decoder, boolean noQueueMode, ClientAwakeTimeProvider awakeTimeProvider, RegistrationIdProvider registrationIdProvider, RegistrationDataExtractor registrationDataExtractor, boolean updateRegistrationOnNotification, boolean updateRegistrationOnSend, LwM2mLinkParser linkParser, - EndPointUriHandler uriHandler, ServerSecurityInfo serverSecurityInfo) { + EndPointUriHandler uriHandler, ServerSecurityInfo serverSecurityInfo, + ServerEndpointNameProvider endpointNameProvider) { Validate.notNull(endpointsProvider, "endpointsProvider cannot be null"); Validate.notNull(registrationStore, "registration store cannot be null"); @@ -166,7 +169,7 @@ public LeshanServer(LwM2mServerEndpointsProvider endpointsProvider, Registration ServerEndpointToolbox toolbox = new ServerEndpointToolbox(decoder, encoder, linkParser, new DefaultClientProfileProvider(registrationStore, modelProvider), uriHandler); RegistrationHandler registrationHandler = new RegistrationHandler(registrationService, authorizer, - registrationIdProvider, registrationDataExtractor); + registrationIdProvider, registrationDataExtractor, endpointNameProvider); UplinkDeviceManagementRequestReceiver requestReceiver = createRequestReceiver(registrationHandler, sendService); endpointsProvider.createEndpoints(requestReceiver, observationService, toolbox, serverSecurityInfo, this); diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServerBuilder.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServerBuilder.java index f4dbb3a385..b7c2673008 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServerBuilder.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServerBuilder.java @@ -54,6 +54,8 @@ import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.security.Authorizer; import org.eclipse.leshan.server.security.DefaultAuthorizer; +import org.eclipse.leshan.servers.DefaultServerEndpointNameProvider; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.servers.security.InMemorySecurityStore; import org.eclipse.leshan.servers.security.SecurityInfo; import org.eclipse.leshan.servers.security.SecurityStore; @@ -73,6 +75,7 @@ public class LeshanServerBuilder { private ClientAwakeTimeProvider awakeTimeProvider; private RegistrationIdProvider registrationIdProvider; private RegistrationDataExtractor registrationDataExtractor; + private ServerEndpointNameProvider endpointNameProvider; private LwM2mEncoder encoder; private LwM2mDecoder decoder; @@ -256,6 +259,17 @@ public void setRegistrationDataExtractor(RegistrationDataExtractor registrationD this.registrationDataExtractor = registrationDataExtractor; } + /** + * Sets {@link ServerEndpointNameProvider} responsible to find client endpoint name if missing from client Identity. + *

+ * By default, {@link DefaultServerEndpointNameProvider} is used. + * + * @param endpointNameProvider the {@link ServerEndpointNameProvider} to set. + */ + public void setEndpointNameProvider(ServerEndpointNameProvider endpointNameProvider) { + this.endpointNameProvider = endpointNameProvider; + } + /** * Update Registration on notification. *

@@ -370,6 +384,9 @@ public LeshanServer build() { if (registrationDataExtractor == null) { registrationDataExtractor = new DefaultRegistrationDataExtractor(); } + if (endpointNameProvider == null) { + endpointNameProvider = new DefaultServerEndpointNameProvider(); + } if (uriHandler == null) { uriHandler = new DefaultEndPointUriHandler(); } @@ -379,7 +396,8 @@ public LeshanServer build() { return createServer(endpointsProvider, registrationStore, securityStore, authorizer, modelProvider, encoder, decoder, noQueueMode, awakeTimeProvider, registrationIdProvider, registrationDataExtractor, linkParser, - uriHandler, serverSecurityInfo, updateRegistrationOnNotification, updateRegistrationOnSend); + uriHandler, serverSecurityInfo, endpointNameProvider, updateRegistrationOnNotification, + updateRegistrationOnSend); } /** @@ -390,7 +408,8 @@ public LeshanServer build() { * * @see LeshanServer#LeshanServer(LwM2mServerEndpointsProvider, RegistrationStore, SecurityStore, Authorizer, * LwM2mModelProvider, LwM2mEncoder, LwM2mDecoder, boolean, ClientAwakeTimeProvider, RegistrationIdProvider, - * RegistrationDataExtractor, boolean, boolean, LwM2mLinkParser, EndPointUriHandler, ServerSecurityInfo) + * RegistrationDataExtractor, boolean, boolean, LwM2mLinkParser, EndPointUriHandler, ServerSecurityInfo, + * ServerEndpointNameProvider) */ protected LeshanServer createServer(LwM2mServerEndpointsProvider endpointsProvider, RegistrationStore registrationStore, SecurityStore securityStore, Authorizer authorizer, @@ -398,9 +417,11 @@ protected LeshanServer createServer(LwM2mServerEndpointsProvider endpointsProvid ClientAwakeTimeProvider awakeTimeProvider, RegistrationIdProvider registrationIdProvider, RegistrationDataExtractor registrationDataExtractor, LwM2mLinkParser linkParser, EndPointUriHandler uriHandler, ServerSecurityInfo serverSecurityInfo, - boolean updateRegistrationOnNotification, boolean updateRegistrationOnSend) { + ServerEndpointNameProvider endpointNameProvider, boolean updateRegistrationOnNotification, + boolean updateRegistrationOnSend) { return new LeshanServer(endpointsProvider, registrationStore, securityStore, authorizer, modelProvider, encoder, decoder, noQueueMode, awakeTimeProvider, registrationIdProvider, registrationDataExtractor, - updateRegistrationOnNotification, updateRegistrationOnSend, linkParser, uriHandler, serverSecurityInfo); + updateRegistrationOnNotification, updateRegistrationOnSend, linkParser, uriHandler, serverSecurityInfo, + endpointNameProvider); } } diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java index a75d346d37..9e0fc47f52 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java @@ -31,6 +31,7 @@ import org.eclipse.leshan.core.response.UpdateResponse; import org.eclipse.leshan.server.registration.RegistrationDataExtractor.RegistrationData; import org.eclipse.leshan.server.security.Authorizer; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.servers.security.Authorization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,18 +48,31 @@ public class RegistrationHandler { private final RegistrationIdProvider registrationIdProvider; private final Authorizer authorizer; private final RegistrationDataExtractor dataExtractor; + private final ServerEndpointNameProvider endpointNameProvider; public RegistrationHandler(RegistrationServiceImpl registrationService, Authorizer authorizer, - RegistrationIdProvider registrationIdProvider, RegistrationDataExtractor dataExtractor) { + RegistrationIdProvider registrationIdProvider, RegistrationDataExtractor dataExtractor, + ServerEndpointNameProvider endpointNameProvider) { this.registrationService = registrationService; this.authorizer = authorizer; this.registrationIdProvider = registrationIdProvider; this.dataExtractor = dataExtractor; + this.endpointNameProvider = endpointNameProvider; } public SendableResponse register(LwM2mPeer sender, RegisterRequest registerRequest, EndpointUri endpointUsed) { + // Extract Endpoint Name + String endpointName = registerRequest.getEndpointName(); + if (endpointName == null) { + // find endpoint name from identity + endpointName = endpointNameProvider.getEndpointName(sender.getIdentity()); + if (endpointName == null) { + return new SendableResponse<>(RegisterResponse.forbidden("endpoint name missing")); + } + } + // Extract data from object link LwM2mVersion lwM2mVersion = LwM2mVersion.get(registerRequest.getLwVersion()); RegistrationData objLinksData = dataExtractor.extractDataFromObjectLinks(registerRequest.getObjectLinks(), @@ -66,8 +80,7 @@ public SendableResponse register(LwM2mPeer sender, RegisterReq // Create Registration from RegisterRequest Registration.Builder builder = new Registration.Builder( - registrationIdProvider.getRegistrationId(registerRequest), registerRequest.getEndpointName(), sender, - endpointUsed); + registrationIdProvider.getRegistrationId(registerRequest), endpointName, sender, endpointUsed); builder.lwM2mVersion(lwM2mVersion) // .lifeTimeInSec(registerRequest.getLifetime()) // diff --git a/leshan-lwm2m-server/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java b/leshan-lwm2m-server/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java index b7f2c16bde..1a5721d5d7 100644 --- a/leshan-lwm2m-server/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java +++ b/leshan-lwm2m-server/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java @@ -33,6 +33,7 @@ import org.eclipse.leshan.core.request.UpdateRequest; import org.eclipse.leshan.core.request.UplinkRequest; import org.eclipse.leshan.server.security.Authorizer; +import org.eclipse.leshan.servers.DefaultServerEndpointNameProvider; import org.eclipse.leshan.servers.security.Authorization; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,7 +49,8 @@ public void setUp() throws UnknownHostException { authorizer = new TestAuthorizer(); registrationStore = new InMemoryRegistrationStore(); registrationHandler = new RegistrationHandler(new RegistrationServiceImpl(registrationStore), authorizer, - new RandomStringRegistrationIdProvider(), new DefaultRegistrationDataExtractor()); + new RandomStringRegistrationIdProvider(), new DefaultRegistrationDataExtractor(), + new DefaultServerEndpointNameProvider()); } @Test diff --git a/leshan-lwm2m-servers-shared/src/main/java/org/eclipse/leshan/servers/DefaultServerEndpointNameProvider.java b/leshan-lwm2m-servers-shared/src/main/java/org/eclipse/leshan/servers/DefaultServerEndpointNameProvider.java new file mode 100644 index 0000000000..5f4f0fb2c2 --- /dev/null +++ b/leshan-lwm2m-servers-shared/src/main/java/org/eclipse/leshan/servers/DefaultServerEndpointNameProvider.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2024 Semtech 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: + * Semtech - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.servers; + +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; + +import org.eclipse.leshan.core.peer.LwM2mIdentity; +import org.eclipse.leshan.core.peer.OscoreIdentity; +import org.eclipse.leshan.core.peer.PskIdentity; +import org.eclipse.leshan.core.peer.X509Identity; + +/** + * This is default implementation of {@link ServerEndpointNameProvider}. + *

+ * This {@link ServerEndpointNameProvider} can guess endpoint name for PSK, X509 and OSCORE but not for NO_SEC or RPK. + * + * @see + */ +public class DefaultServerEndpointNameProvider implements ServerEndpointNameProvider { + + /** + * {@inheritDoc} + * + *

    + *
  • If PSK is used PSK Identity will be considered as endpoint name. + *
  • If X509 is used Certificate CN will be considered as endpoint name. + *
  • If OSCORE is used Sender ID (at client side) will be considered as endpoint name. (only if Sender ID can be + * converted in UTF8 String) + *
  • else null will be returned + *
+ */ + @Override + public String getEndpointName(LwM2mIdentity clientIdentity) { + if (clientIdentity instanceof PskIdentity) { + return ((PskIdentity) clientIdentity).getPskIdentity(); + } else if (clientIdentity instanceof X509Identity) { + return ((X509Identity) clientIdentity).getX509CommonName(); + } else if (clientIdentity instanceof OscoreIdentity) { + // Recipient ID at server side is Sender ID at client side + // Try to convert byte array in UTF8 String + try { + CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); + ByteBuffer buf = ByteBuffer.wrap(((OscoreIdentity) clientIdentity).getRecipientId()); + return decoder.decode(buf).toString(); + } catch (CharacterCodingException e) { + return null; + } + } + return null; + } +} diff --git a/leshan-lwm2m-servers-shared/src/main/java/org/eclipse/leshan/servers/ServerEndpointNameProvider.java b/leshan-lwm2m-servers-shared/src/main/java/org/eclipse/leshan/servers/ServerEndpointNameProvider.java new file mode 100644 index 0000000000..5bb1395e00 --- /dev/null +++ b/leshan-lwm2m-servers-shared/src/main/java/org/eclipse/leshan/servers/ServerEndpointNameProvider.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2024 Semtech 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: + * Semtech - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.servers; + +import org.eclipse.leshan.core.peer.LwM2mIdentity; + +/** + * Since LWM2M v1.1, endpoint name is optional in REGISTER and BOOTSTRAP request. An {@link ServerEndpointNameProvider} + * is responsible to find the right Endpoint Name from the client identity. + *

+ * + * @see + * @see DefaultServerEndpointNameProvider + */ +public interface ServerEndpointNameProvider { + + /** + * @return endpointName if it's possible to guess it from identity, else return null + */ + String getEndpointName(LwM2mIdentity clientIdentity); + +} diff --git a/leshan-tl-cf-bsserver-coap/src/main/java/org/eclipse/leshan/transport/californium/bsserver/BootstrapResource.java b/leshan-tl-cf-bsserver-coap/src/main/java/org/eclipse/leshan/transport/californium/bsserver/BootstrapResource.java index 26e5410bad..ee8268bbfe 100644 --- a/leshan-tl-cf-bsserver-coap/src/main/java/org/eclipse/leshan/transport/californium/bsserver/BootstrapResource.java +++ b/leshan-tl-cf-bsserver-coap/src/main/java/org/eclipse/leshan/transport/californium/bsserver/BootstrapResource.java @@ -42,8 +42,8 @@ public class BootstrapResource extends LwM2mCoapResource { private static final Logger LOG = LoggerFactory.getLogger(BootstrapResource.class); - private static final String QUERY_PARAM_ENDPOINT = "ep="; - private static final String QUERY_PARAM_PREFERRED_CONTENT_FORMAT = "pct="; + private static final String QUERY_PARAM_ENDPOINT = "ep"; + private static final String QUERY_PARAM_PREFERRED_CONTENT_FORMAT = "pct"; private final BootstrapUplinkRequestReceiver receiver; private final EndPointUriHandler uriHandler; @@ -71,18 +71,30 @@ public void handlePOST(CoapExchange exchange) { String endpoint = null; ContentFormat preferredContentFomart = null; Map additionalParams = new HashMap<>(); + // TODO maybe we should use LwM2mAttributeParser ? for (String param : request.getOptions().getUriQuery()) { - if (param.startsWith(QUERY_PARAM_ENDPOINT)) { - endpoint = param.substring(QUERY_PARAM_ENDPOINT.length()); - } else if (param.startsWith(QUERY_PARAM_PREFERRED_CONTENT_FORMAT)) { - try { - preferredContentFomart = ContentFormat - .fromCode(param.substring(QUERY_PARAM_PREFERRED_CONTENT_FORMAT.length())); - } catch (NumberFormatException e) { - handleInvalidRequest(exchange.advanced(), - "Invalid preferre content format (pct) query param : must be a number", e); - return; + String[] p = param.split("=", 2); + String paramName = p[0]; + if (paramName.equals(QUERY_PARAM_ENDPOINT)) { + if (p.length == 2) { + endpoint = p[1]; + } else { + endpoint = ""; } + } else if (paramName.equals(QUERY_PARAM_PREFERRED_CONTENT_FORMAT)) { + if (p.length == 2) { + try { + preferredContentFomart = ContentFormat.fromCode(p[1]); + } catch (NumberFormatException e) { + handleInvalidRequest(exchange.advanced(), + "Invalid preferre content format (pct) query param : must be a number", e); + return; + } + } else { + handleInvalidRequest(exchange.advanced(), "preferred content format (pct) param can not be empty", + null); + } + } else { String[] tokens = param.split("\\="); if (tokens != null && tokens.length == 2) { diff --git a/leshan-tl-cf-bsserver-coap/src/test/java/org/eclipse/leshan/transport/californium/bsserver/LeshanBootstrapServerTest.java b/leshan-tl-cf-bsserver-coap/src/test/java/org/eclipse/leshan/transport/californium/bsserver/LeshanBootstrapServerTest.java index da3c86f1e7..b36478d7c0 100644 --- a/leshan-tl-cf-bsserver-coap/src/test/java/org/eclipse/leshan/transport/californium/bsserver/LeshanBootstrapServerTest.java +++ b/leshan-tl-cf-bsserver-coap/src/test/java/org/eclipse/leshan/transport/californium/bsserver/LeshanBootstrapServerTest.java @@ -36,6 +36,7 @@ import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.response.BootstrapResponse; import org.eclipse.leshan.core.response.SendableResponse; +import org.eclipse.leshan.servers.ServerEndpointNameProvider; import org.eclipse.leshan.transport.californium.bsserver.endpoint.CaliforniumBootstrapServerEndpointsProvider; import org.junit.jupiter.api.Test; @@ -49,8 +50,9 @@ private LeshanBootstrapServer createBootstrapServer() { @Override public BootstrapHandler create(BootstrapDownlinkRequestSender sender, - BootstrapSessionManager sessionManager, BootstrapSessionListener listener) { - bsHandler = new DefaultBootstrapHandler(sender, sessionManager, listener); + BootstrapSessionManager sessionManager, ServerEndpointNameProvider endpointNameProvider, + BootstrapSessionListener listener) { + bsHandler = new DefaultBootstrapHandler(sender, sessionManager, endpointNameProvider, listener); return bsHandler; } }); diff --git a/leshan-tl-cf-client-coap/src/main/java/org/eclipse/leshan/transport/californium/client/request/CoapRequestBuilder.java b/leshan-tl-cf-client-coap/src/main/java/org/eclipse/leshan/transport/californium/client/request/CoapRequestBuilder.java index 324a52a93c..ded5567385 100644 --- a/leshan-tl-cf-client-coap/src/main/java/org/eclipse/leshan/transport/californium/client/request/CoapRequestBuilder.java +++ b/leshan-tl-cf-client-coap/src/main/java/org/eclipse/leshan/transport/californium/client/request/CoapRequestBuilder.java @@ -71,7 +71,11 @@ public void visit(BootstrapRequest request) { // @since 1.1 HashMap attributes = new HashMap<>(); attributes.putAll(request.getAdditionalAttributes()); - attributes.put("ep", request.getEndpointName()); + + String endpoint = request.getEndpointName(); + if (endpoint != null) { + attributes.put("ep", endpoint); + } if (request.getPreferredContentFormat() != null) { attributes.put("pct", Integer.toString(request.getPreferredContentFormat().getCode())); } @@ -90,7 +94,10 @@ public void visit(RegisterRequest request) { HashMap attributes = new HashMap<>(); attributes.putAll(request.getAdditionalAttributes()); - attributes.put("ep", request.getEndpointName()); + String endpoint = request.getEndpointName(); + if (endpoint != null) { + attributes.put("ep", endpoint); + } Long lifetime = request.getLifetime(); if (lifetime != null) diff --git a/leshan-tl-cf-server-coap/src/main/java/org/eclipse/leshan/transport/californium/server/registration/RegisterResource.java b/leshan-tl-cf-server-coap/src/main/java/org/eclipse/leshan/transport/californium/server/registration/RegisterResource.java index 9c261f6b8d..abf48fa859 100644 --- a/leshan-tl-cf-server-coap/src/main/java/org/eclipse/leshan/transport/californium/server/registration/RegisterResource.java +++ b/leshan-tl-cf-server-coap/src/main/java/org/eclipse/leshan/transport/californium/server/registration/RegisterResource.java @@ -57,15 +57,15 @@ */ public class RegisterResource extends LwM2mCoapResource { - private static final String QUERY_PARAM_ENDPOINT = "ep="; + private static final String QUERY_PARAM_ENDPOINT = "ep"; - private static final String QUERY_PARAM_BINDING_MODE = "b="; + private static final String QUERY_PARAM_BINDING_MODE = "b"; - private static final String QUERY_PARAM_LWM2M_VERSION = "lwm2m="; + private static final String QUERY_PARAM_LWM2M_VERSION = "lwm2m"; - private static final String QUERY_PARAM_SMS = "sms="; + private static final String QUERY_PARAM_SMS = "sms"; - private static final String QUERY_PARAM_LIFETIME = "lt="; + private static final String QUERY_PARAM_LIFETIME = "lt"; private static final String QUERY_PARAM_QUEUEMMODE = "Q"; // since LWM2M 1.1 @@ -156,27 +156,55 @@ protected void handleRegister(CoapExchange exchange, Request request) { Map additionalParams = new HashMap<>(); // Get parameters + // TODO maybe we should use LwM2mAttributeParser ? for (String param : request.getOptions().getUriQuery()) { - if (param.startsWith(QUERY_PARAM_ENDPOINT)) { - endpoint = param.substring(3); - } else if (param.startsWith(QUERY_PARAM_LIFETIME)) { - lifetime = Long.valueOf(param.substring(3)); - } else if (param.startsWith(QUERY_PARAM_SMS)) { + String[] p = param.split("=", 2); + String paramName = p[0]; + if (paramName.equals(QUERY_PARAM_ENDPOINT)) { + if (p.length == 2) { + endpoint = p[1]; + } else { + endpoint = ""; + } + } else if (paramName.equals(QUERY_PARAM_LIFETIME)) { + if (p.length == 2) { + lifetime = Long.valueOf(p[1]); + } else { + handleInvalidRequest(exchange.advanced(), "lifetime (lt) param can not be empty", null); + } + } else if (paramName.equals(QUERY_PARAM_SMS)) { + if (p.length == 2) { + smsNumber = p[1]; + } else { + smsNumber = ""; + } smsNumber = param.substring(4); - } else if (param.startsWith(QUERY_PARAM_LWM2M_VERSION)) { - lwVersion = param.substring(6); - } else if (param.startsWith(QUERY_PARAM_BINDING_MODE)) { - binding = BindingMode.parse(param.substring(2)); - } else if (param.equals(QUERY_PARAM_QUEUEMMODE)) { - queueMode = true; + } else if (paramName.equals(QUERY_PARAM_LWM2M_VERSION)) { + if (p.length == 2) { + lwVersion = p[1]; + } else { + lwVersion = ""; + } + } else if (paramName.equals(QUERY_PARAM_BINDING_MODE)) { + if (p.length == 2) { + binding = BindingMode.parse(p[1]); + } else { + binding = EnumSet.noneOf(BindingMode.class); + } + } else if (paramName.equals(QUERY_PARAM_QUEUEMMODE)) { + if (p.length == 2) { + handleInvalidRequest(exchange.advanced(), "queue param (Q) most not have value", null); + } else { + queueMode = true; + } + } else { - String[] tokens = param.split("\\="); + String[] tokens = param.split("=", 2); if (tokens != null && tokens.length == 2) { additionalParams.put(tokens[0], tokens[1]); } } } - // Create request Request coapRequest = exchange.advanced().getRequest(); RegisterRequest registerRequest = new RegisterRequest(endpoint, lifetime, lwVersion, binding, queueMode, @@ -211,12 +239,27 @@ protected void handleUpdate(CoapExchange exchange, Request request, String regis Map additionalParams = new HashMap<>(); for (String param : request.getOptions().getUriQuery()) { - if (param.startsWith(QUERY_PARAM_LIFETIME)) { - lifetime = Long.valueOf(param.substring(3)); - } else if (param.startsWith(QUERY_PARAM_SMS)) { + String[] p = param.split("=", 2); + String paramName = p[0]; + if (paramName.equals(QUERY_PARAM_LIFETIME)) { + if (p.length == 2) { + lifetime = Long.valueOf(p[1]); + } else { + handleInvalidRequest(exchange.advanced(), "lifetime (lt) param can not be empty", null); + } + } else if (paramName.equals(QUERY_PARAM_SMS)) { + if (p.length == 2) { + smsNumber = p[1]; + } else { + smsNumber = ""; + } smsNumber = param.substring(4); - } else if (param.startsWith(QUERY_PARAM_BINDING_MODE)) { - binding = BindingMode.parse(param.substring(2)); + } else if (paramName.equals(QUERY_PARAM_BINDING_MODE)) { + if (p.length == 2) { + binding = BindingMode.parse(p[1]); + } else { + binding = EnumSet.noneOf(BindingMode.class); + } } else { String[] tokens = param.split("\\="); if (tokens != null && tokens.length == 2) { diff --git a/leshan-tl-jc-client-coap/src/main/java/org/eclipse/leshan/transport/javacoap/client/request/CoapRequestBuilder.java b/leshan-tl-jc-client-coap/src/main/java/org/eclipse/leshan/transport/javacoap/client/request/CoapRequestBuilder.java index 5fa321b898..b014f60aaa 100644 --- a/leshan-tl-jc-client-coap/src/main/java/org/eclipse/leshan/transport/javacoap/client/request/CoapRequestBuilder.java +++ b/leshan-tl-jc-client-coap/src/main/java/org/eclipse/leshan/transport/javacoap/client/request/CoapRequestBuilder.java @@ -70,7 +70,11 @@ public void visit(BootstrapRequest request) { // Create map of attributes HashMap attributes = new HashMap<>(); attributes.putAll(request.getAdditionalAttributes()); - attributes.put("ep", request.getEndpointName()); + + String endpoint = request.getEndpointName(); + if (endpoint != null) { + attributes.put("ep", endpoint); + } if (request.getPreferredContentFormat() != null) { attributes.put("pct", Integer.toString(request.getPreferredContentFormat().getCode())); } @@ -90,7 +94,10 @@ public void visit(RegisterRequest request) { HashMap attributes = new HashMap<>(); attributes.putAll(request.getAdditionalAttributes()); - attributes.put("ep", request.getEndpointName()); + String endpoint = request.getEndpointName(); + if (endpoint != null) { + attributes.put("ep", endpoint); + } Long lifetime = request.getLifetime(); if (lifetime != null) diff --git a/leshan-tl-jc-server-coap/src/main/java/org/eclipse/leshan/transport/javacoap/server/resource/RegistrationResource.java b/leshan-tl-jc-server-coap/src/main/java/org/eclipse/leshan/transport/javacoap/server/resource/RegistrationResource.java index 43a6db6926..c4fd974277 100644 --- a/leshan-tl-jc-server-coap/src/main/java/org/eclipse/leshan/transport/javacoap/server/resource/RegistrationResource.java +++ b/leshan-tl-jc-server-coap/src/main/java/org/eclipse/leshan/transport/javacoap/server/resource/RegistrationResource.java @@ -136,13 +136,13 @@ protected CompletableFuture handleRegister(CoapRequest coapRequest try { for (Entry entry : coapRequest.options().getUriQueryMap().entrySet()) { if (entry.getKey().equals(QUERY_PARAM_ENDPOINT)) { - endpoint = entry.getValue(); + endpoint = entry.getValue() == null ? "" : entry.getValue(); } else if (entry.getKey().equals(QUERY_PARAM_LIFETIME)) { lifetime = Long.valueOf(entry.getValue()); } else if (entry.getKey().equals(QUERY_PARAM_SMS)) { - smsNumber = entry.getValue(); + smsNumber = entry.getValue() == null ? "" : entry.getValue(); } else if (entry.getKey().equals(QUERY_PARAM_LWM2M_VERSION)) { - lwVersion = entry.getValue(); + lwVersion = entry.getValue() == null ? "" : entry.getValue(); } else if (entry.getKey().equals(QUERY_PARAM_BINDING_MODE)) { binding = BindingMode.parse(entry.getValue()); } else if (entry.getKey().equals(QUERY_PARAM_QUEUEMMODE)) { @@ -195,7 +195,7 @@ protected CompletableFuture handleUpdate(CoapRequest coapRequest, if (entry.getKey().equals(QUERY_PARAM_LIFETIME)) { lifetime = Long.valueOf(entry.getValue()); } else if (entry.getKey().equals(QUERY_PARAM_SMS)) { - smsNumber = entry.getValue(); + smsNumber = entry.getValue() == null ? "" : entry.getValue(); } else if (entry.getKey().equals(QUERY_PARAM_BINDING_MODE)) { binding = BindingMode.parse(entry.getValue()); } else {