Skip to content

Commit

Permalink
#1000: Add a BootstrapConsistencyChecker to Leshan Client.
Browse files Browse the repository at this point in the history
  • Loading branch information
sbernard31 committed Jun 25, 2021
1 parent e0919c3 commit 0d24d6d
Show file tree
Hide file tree
Showing 12 changed files with 382 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder;
import org.eclipse.leshan.client.LwM2mClient;
import org.eclipse.leshan.client.RegistrationUpdateHandler;
import org.eclipse.leshan.client.bootstrap.BootstrapConsistencyChecker;
import org.eclipse.leshan.client.bootstrap.BootstrapHandler;
import org.eclipse.leshan.client.bootstrap.DefaultBootstrapConsistencyChecker;
import org.eclipse.leshan.client.californium.bootstrap.BootstrapResource;
import org.eclipse.leshan.client.californium.object.ObjectResource;
import org.eclipse.leshan.client.californium.request.CaliforniumLwM2mRequestSender;
Expand Down Expand Up @@ -104,15 +106,17 @@ public LeshanClient(String endpoint, InetSocketAddress localAddress,
Map<String, String> additionalAttributes, Map<String, String> bsAdditionalAttributes,
LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder, ScheduledExecutorService sharedExecutor) {
this(endpoint, localAddress, objectEnablers, coapConfig, dtlsConfigBuilder, null, endpointFactory,
engineFactory, additionalAttributes, bsAdditionalAttributes, encoder, decoder, sharedExecutor);
engineFactory, new DefaultBootstrapConsistencyChecker(), additionalAttributes, bsAdditionalAttributes,
encoder, decoder, sharedExecutor);
}

/** @since 2.0 */
public LeshanClient(String endpoint, InetSocketAddress localAddress,
List<? extends LwM2mObjectEnabler> objectEnablers, NetworkConfig coapConfig, Builder dtlsConfigBuilder,
List<Certificate> trustStore, EndpointFactory endpointFactory, RegistrationEngineFactory engineFactory,
Map<String, String> additionalAttributes, Map<String, String> bsAdditionalAttributes,
LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder, ScheduledExecutorService sharedExecutor) {
BootstrapConsistencyChecker checker, Map<String, String> additionalAttributes,
Map<String, String> bsAdditionalAttributes, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder,
ScheduledExecutorService sharedExecutor) {

Validate.notNull(endpoint);
Validate.notEmpty(objectEnablers);
Expand All @@ -123,7 +127,7 @@ public LeshanClient(String endpoint, InetSocketAddress localAddress,
this.decoder = decoder;
this.encoder = encoder;
observers = createClientObserverDispatcher();
bootstrapHandler = createBoostrapHandler(objectTree);
bootstrapHandler = createBoostrapHandler(objectTree, checker);
endpointsManager = createEndpointsManager(localAddress, coapConfig, dtlsConfigBuilder, trustStore,
endpointFactory);
requestSender = createRequestSender(endpointsManager, sharedExecutor, encoder, objectTree.getModel());
Expand Down Expand Up @@ -160,8 +164,8 @@ public void onUnexpectedError(Throwable unexpectedError) {
return observer;
}

protected BootstrapHandler createBoostrapHandler(LwM2mObjectTree objectTree) {
return new BootstrapHandler(objectTree.getObjectEnablers());
protected BootstrapHandler createBoostrapHandler(LwM2mObjectTree objectTree, BootstrapConsistencyChecker checker) {
return new BootstrapHandler(objectTree.getObjectEnablers(), checker);
}

protected CoapServer createCoapServer(NetworkConfig coapConfig, ScheduledExecutorService sharedExecutor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder;
import org.eclipse.leshan.client.bootstrap.BootstrapConsistencyChecker;
import org.eclipse.leshan.client.bootstrap.DefaultBootstrapConsistencyChecker;
import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory;
import org.eclipse.leshan.client.engine.RegistrationEngine;
import org.eclipse.leshan.client.engine.RegistrationEngineFactory;
Expand Down Expand Up @@ -71,11 +73,11 @@ public class LeshanClientBuilder {
private EndpointFactory endpointFactory;
private RegistrationEngineFactory engineFactory;
private Map<String, String> additionalAttributes;
private Map<String, String> bsAdditionalAttributes;

private ScheduledExecutorService executor;
private BootstrapConsistencyChecker bootstrapConsistencyChecker;

/** @since 1.1 */
protected Map<String, String> bsAdditionalAttributes;
private ScheduledExecutorService executor;

/**
* Creates a new instance for setting the configuration options for a {@link LeshanClient} instance.
Expand Down Expand Up @@ -217,6 +219,18 @@ public LeshanClientBuilder setBootstrapAdditionalAttributes(Map<String, String>
return this;
}

/**
* Set a {@link BootstrapConsistencyChecker} which is used to valid client state after a bootstrap session.
* <p>
* By default a {@link DefaultBootstrapConsistencyChecker} is used.
*
* @return the builder for fluent client creation.
*/
public LeshanClientBuilder setBootstrapConsistencyChecker(BootstrapConsistencyChecker checker) {
this.bootstrapConsistencyChecker = checker;
return this;
}

/**
* Set a shared executor. This executor will be used everywhere it is possible. This is generally used when you want
* to limit the number of thread to use or if you want to simulate a lot of clients sharing the same thread pool.
Expand Down Expand Up @@ -283,6 +297,9 @@ protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) {
}
};
}
if (bootstrapConsistencyChecker == null) {
bootstrapConsistencyChecker = new DefaultBootstrapConsistencyChecker();
}

// handle dtlsConfig
if (dtlsConfigBuilder == null) {
Expand Down Expand Up @@ -324,7 +341,8 @@ protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) {
}

return createLeshanClient(endpoint, localAddress, objectEnablers, coapConfig, dtlsConfigBuilder,
this.trustStore, endpointFactory, engineFactory, additionalAttributes, encoder, decoder, executor);
this.trustStore, endpointFactory, engineFactory, bootstrapConsistencyChecker, additionalAttributes,
bsAdditionalAttributes, encoder, decoder, executor);
}

/**
Expand All @@ -344,7 +362,9 @@ protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) {
* @param trustStore The optional trust store for verifying X.509 server certificates.
* @param endpointFactory The factory which will create the {@link CoapEndpoint}.
* @param engineFactory The factory which will create the {@link RegistrationEngine}.
* @param checker Used to check if client state is consistent after a bootstrap session.
* @param additionalAttributes Some extra (out-of-spec) attributes to add to the register request.
* @param bsAdditionalAttributes Some extra (out-of-spec) attributes to add to the bootstrap request.
* @param encoder used to encode request payload.
* @param decoder used to decode response payload.
* @param sharedExecutor an optional shared executor.
Expand All @@ -354,10 +374,11 @@ protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) {
protected LeshanClient createLeshanClient(String endpoint, InetSocketAddress localAddress,
List<? extends LwM2mObjectEnabler> objectEnablers, NetworkConfig coapConfig, Builder dtlsConfigBuilder,
List<Certificate> trustStore, EndpointFactory endpointFactory, RegistrationEngineFactory engineFactory,
Map<String, String> additionalAttributes, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder,
BootstrapConsistencyChecker checker, Map<String, String> additionalAttributes,
Map<String, String> bsAdditionalAttributes, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder,
ScheduledExecutorService sharedExecutor) {
return new LeshanClient(endpoint, localAddress, objectEnablers, coapConfig, dtlsConfigBuilder, trustStore,
endpointFactory, engineFactory, additionalAttributes, bsAdditionalAttributes, encoder, decoder,
executor);
endpointFactory, engineFactory, checker, additionalAttributes, bsAdditionalAttributes, encoder, decoder,
sharedExecutor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2021 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.bootstrap;

import java.util.List;
import java.util.Map;

import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;

/**
* This class is responsible to check the consistency state of the client after at the end of a bootstrap session.
*/
public interface BootstrapConsistencyChecker {

/**
* check if current state of the client is consistent
*
* @param objectEnablers all objects supported by the client.
* @return <code>null</code> if the current state is consistent or a list of issues
*/
List<String> checkconfig(Map<Integer, LwM2mObjectEnabler> objectEnablers);

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*******************************************************************************/
package org.eclipse.leshan.client.bootstrap;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
Expand All @@ -40,11 +41,15 @@ public class BootstrapHandler {

private boolean bootstrapping = false;
private CountDownLatch bootstrappingLatch = new CountDownLatch(1);
// last session state (null means no error)
private volatile List<String> lastConsistencyError = null;

private final Map<Integer, LwM2mObjectEnabler> objects;
private BootstrapConsistencyChecker checker;

public BootstrapHandler(Map<Integer, LwM2mObjectEnabler> objectEnablers) {
public BootstrapHandler(Map<Integer, LwM2mObjectEnabler> objectEnablers, BootstrapConsistencyChecker checker) {
objects = objectEnablers;
this.checker = checker;
}

public synchronized SendableResponse<BootstrapFinishResponse> finished(ServerIdentity server,
Expand All @@ -54,7 +59,6 @@ public synchronized SendableResponse<BootstrapFinishResponse> finished(ServerIde
if (!server.isLwm2mBootstrapServer()) {
return new SendableResponse<>(BootstrapFinishResponse.badRequest("not from a bootstrap server"));
}
// TODO delete bootstrap server (see 5.2.5.2 Bootstrap Delete)

Runnable whenSent = new Runnable() {
@Override
Expand All @@ -63,7 +67,17 @@ public void run() {
}
};

return new SendableResponse<>(BootstrapFinishResponse.success(), whenSent);
// check consistency state of the client
lastConsistencyError = checker.checkconfig(objects);
if (lastConsistencyError == null) {
return new SendableResponse<>(BootstrapFinishResponse.success(), whenSent);
} else {
// TODO rollback configuration.
// see https://github.com/eclipse/leshan/issues/968
return new SendableResponse<>(BootstrapFinishResponse.notAcceptable(lastConsistencyError.toString()),
whenSent);
}

} else {
return new SendableResponse<>(BootstrapFinishResponse.badRequest("no pending bootstrap session"));
}
Expand Down Expand Up @@ -91,6 +105,7 @@ public synchronized boolean tryToInitSession() {
if (!bootstrapping) {
bootstrappingLatch = new CountDownLatch(1);
bootstrapping = true;
lastConsistencyError = null;
return true;
}
return false;
Expand All @@ -100,8 +115,15 @@ public synchronized boolean isBootstrapping() {
return bootstrapping;
}

public boolean waitBoostrapFinished(long timeInSeconds) throws InterruptedException {
return bootstrappingLatch.await(timeInSeconds, TimeUnit.SECONDS);
public boolean waitBoostrapFinished(long timeInSeconds) throws InterruptedException, InvalidStateException {
boolean finished = bootstrappingLatch.await(timeInSeconds, TimeUnit.SECONDS);
if (finished) {
if (lastConsistencyError != null) {
throw new InvalidStateException(
String.format("Invalid Bootstrap state : %s", lastConsistencyError.toString()));
}
}
return finished;
}

public synchronized void closeSession() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2021 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.bootstrap;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
import org.eclipse.leshan.client.servers.ServersInfoExtractor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A default implementation of {@link BootstrapConsistencyChecker}
*/
public class DefaultBootstrapConsistencyChecker implements BootstrapConsistencyChecker {

private static final Logger LOG = LoggerFactory.getLogger(DefaultBootstrapConsistencyChecker.class);

@Override
public List<String> checkconfig(Map<Integer, LwM2mObjectEnabler> objectEnablers) {
try {
ServersInfoExtractor.getInfo(objectEnablers, true);
} catch (RuntimeException e) {
LOG.debug(e.getMessage());
return Arrays.asList(e.getMessage());
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2021 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.bootstrap;

/**
* Raised if LWM2M client is in an invalid or inconsistent state.
*
*/
public class InvalidStateException extends Exception {
private static final long serialVersionUID = 1L;

public InvalidStateException() {
}

public InvalidStateException(String m) {
super(m);
}

public InvalidStateException(String m, Object... args) {
super(String.format(m, args));
}

public InvalidStateException(Throwable e) {
super(e);
}

public InvalidStateException(String m, Throwable e) {
super(m, e);
}

public InvalidStateException(Throwable e, String m, Object... args) {
super(String.format(m, args), e);
}
}
Loading

0 comments on commit 0d24d6d

Please sign in to comment.