org.eclipse.leshan.client.servers.RegistrationEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of leshan-all Show documentation
Show all versions of leshan-all Show documentation
A LWM2M client and server based on Californium (CoAP) all in one.
/*******************************************************************************
* Copyright (c) 2015 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Sierra Wireless - initial API and implementation
*******************************************************************************/
package org.eclipse.leshan.client.servers;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.leshan.LwM2m;
import org.eclipse.leshan.ResponseCode;
import org.eclipse.leshan.client.observer.LwM2mClientObserver;
import org.eclipse.leshan.client.request.LwM2mRequestSender;
import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
import org.eclipse.leshan.client.util.LinkFormatHelper;
import org.eclipse.leshan.core.request.BootstrapRequest;
import org.eclipse.leshan.core.request.DeregisterRequest;
import org.eclipse.leshan.core.request.RegisterRequest;
import org.eclipse.leshan.core.request.UpdateRequest;
import org.eclipse.leshan.core.response.BootstrapResponse;
import org.eclipse.leshan.core.response.DeregisterResponse;
import org.eclipse.leshan.core.response.RegisterResponse;
import org.eclipse.leshan.core.response.UpdateResponse;
import org.eclipse.leshan.util.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manage the registration life-cycle:
*
* - Start bootstrap session if no device Management server is available
* - Register to device management server when available (at startup or after bootstrap)
* - Update registration periodically.
* - If communication failed with device management server, try to bootstrap again each 10 minutes until succeed
*
*
* For now support only one device management server.
*/
public class RegistrationEngine {
private static final Logger LOG = LoggerFactory.getLogger(RegistrationEngine.class);
// TODO bootstrap timeout should be configurable
private static final int BS_TIMEOUT = 93; // in seconds (93s is the COAP MAX_TRANSMIT_WAIT with default config)
private static final long DEREGISTRATION_TIMEOUT = 1000; // in ms, de-registration is only used on stop for now.
// TODO time between bootstrap retry should be configurable and incremental
private static final int BS_RETRY = 10 * 60; // in seconds
private final String endpoint;
private final LwM2mRequestSender sender;
private final Map objectEnablers;
private final BootstrapHandler bootstrapHandler;
private final LwM2mClientObserver observer;
private final AtomicBoolean started = new AtomicBoolean(false);
// registration update
private String registrationID;
private Future> registerFuture;
private ScheduledFuture> updateFuture;
private final ScheduledExecutorService schedExecutor = Executors.newScheduledThreadPool(2,
new NamedThreadFactory("RegistrationEngine#%d"));
public RegistrationEngine(String endpoint, Map objectEnablers,
LwM2mRequestSender requestSender, BootstrapHandler bootstrapState, LwM2mClientObserver observer) {
this.endpoint = endpoint;
this.objectEnablers = objectEnablers;
this.bootstrapHandler = bootstrapState;
this.observer = observer;
sender = requestSender;
}
public void start() {
stop(false); // stop without de-register
started.set(true);
registerFuture = schedExecutor.submit(new RegistrationTask());
}
private boolean bootstrap() throws InterruptedException {
ServersInfo serversInfo = ServersInfoExtractor.getInfo(objectEnablers);
if (serversInfo.bootstrap == null) {
LOG.error("Trying to bootstrap device but there is no bootstrap server config.");
return false;
}
if (bootstrapHandler.tryToInitSession(serversInfo.bootstrap)) {
LOG.info("Trying to start bootstrap session to {} ...", serversInfo.bootstrap.getFullUri());
try {
// Send bootstrap request
ServerInfo bootstrapServer = serversInfo.bootstrap;
BootstrapResponse response = sender.send(bootstrapServer.getAddress(), bootstrapServer.isSecure(),
new BootstrapRequest(endpoint), null);
if (response == null) {
LOG.error("Unable to start bootstrap session: Timeout.");
if (observer != null) {
observer.onBootstrapTimeout(bootstrapServer);
}
return false;
} else if (response.isSuccess()) {
LOG.info("Bootstrap started");
// wait until it is finished (or too late)
boolean timeout = !bootstrapHandler.waitBoostrapFinished(BS_TIMEOUT);
if (timeout) {
LOG.error("Bootstrap sequence aborted: Timeout.");
if (observer != null) {
observer.onBootstrapTimeout(bootstrapServer);
}
return false;
} else {
serversInfo = ServersInfoExtractor.getInfo(objectEnablers);
LOG.info("Bootstrap finished {}.", serversInfo);
if (observer != null) {
observer.onBootstrapSuccess(bootstrapServer);
}
return true;
}
} else {
LOG.error("Bootstrap failed: {} {}.", response.getCode(), response.getErrorMessage());
if (observer != null) {
observer.onBootstrapFailure(bootstrapServer, response.getCode(), response.getErrorMessage());
}
return false;
}
} finally {
bootstrapHandler.closeSession();
}
} else {
LOG.warn("Bootstrap sequence already started.");
return false;
}
}
private boolean register() throws InterruptedException {
ServersInfo serversInfo = ServersInfoExtractor.getInfo(objectEnablers);
DmServerInfo dmInfo = serversInfo.deviceMangements.values().iterator().next();
if (dmInfo == null) {
LOG.error("Trying to register device but there is no LWM2M server config.");
return false;
}
// send register request
LOG.info("Trying to register to {} ...", dmInfo.getFullUri());
RegisterResponse response = sender
.send(dmInfo.getAddress(),
dmInfo.isSecure(), new RegisterRequest(endpoint, dmInfo.lifetime, LwM2m.VERSION, dmInfo.binding,
null, LinkFormatHelper.getClientDescription(objectEnablers.values(), null), null),
null);
if (response == null) {
registrationID = null;
LOG.error("Registration failed: Timeout.");
if (observer != null) {
observer.onRegistrationTimeout(dmInfo);
}
} else if (response.isSuccess()) {
registrationID = response.getRegistrationID();
// update every lifetime period
scheduleUpdate(dmInfo);
LOG.info("Registered with location '{}'.", response.getRegistrationID());
if (observer != null) {
observer.onRegistrationSuccess(dmInfo, response.getRegistrationID());
}
} else {
registrationID = null;
LOG.error("Registration failed: {} {}.", response.getCode(), response.getErrorMessage());
if (observer != null) {
observer.onRegistrationFailure(dmInfo, response.getCode(), response.getErrorMessage());
}
}
return registrationID != null;
}
private boolean deregister() throws InterruptedException {
if (registrationID == null)
return true;
ServersInfo serversInfo = ServersInfoExtractor.getInfo(objectEnablers);
DmServerInfo dmInfo = serversInfo.deviceMangements.values().iterator().next();
if (dmInfo == null) {
LOG.error("Trying to deregister device but there is no LWM2M server config.");
return false;
}
// Send deregister request
LOG.info("Trying to deregister to {} ...", dmInfo.getFullUri());
DeregisterResponse response = sender.send(dmInfo.getAddress(), dmInfo.isSecure(),
new DeregisterRequest(registrationID), DEREGISTRATION_TIMEOUT);
if (response == null) {
registrationID = null;
LOG.error("Deregistration failed: Timeout.");
if (observer != null) {
observer.onDeregistrationTimeout(dmInfo);
}
return false;
} else if (response.isSuccess() || response.getCode() == ResponseCode.NOT_FOUND) {
registrationID = null;
cancelUpdateTask(true);
LOG.info("De-register response {} {}.", response.getCode(), response.getErrorMessage());
if (observer != null) {
if (response.isSuccess()) {
observer.onDeregistrationSuccess(dmInfo, registrationID);
} else {
observer.onDeregistrationFailure(dmInfo, response.getCode(), response.getErrorMessage());
}
}
return true;
} else {
LOG.error("Deregistration failed: {} {}.", response.getCode(), response.getErrorMessage());
if (observer != null) {
observer.onDeregistrationFailure(dmInfo, response.getCode(), response.getErrorMessage());
}
return false;
}
}
private boolean update() throws InterruptedException {
cancelUpdateTask(false);
ServersInfo serversInfo = ServersInfoExtractor.getInfo(objectEnablers);
DmServerInfo dmInfo = serversInfo.deviceMangements.values().iterator().next();
if (dmInfo == null) {
LOG.error("Trying to update registration but there is no LWM2M server config.");
return false;
}
// Send update
LOG.info("Trying to update registration to {} ...", dmInfo.getFullUri());
UpdateResponse response = sender.send(dmInfo.getAddress(), dmInfo.isSecure(),
new UpdateRequest(registrationID, null, null, null, null), null);
if (response == null) {
registrationID = null;
LOG.error("Registration update failed: Timeout.");
if (observer != null) {
observer.onUpdateTimeout(dmInfo);
}
return false;
} else if (response.getCode() == ResponseCode.CHANGED) {
// Update successful, so we reschedule new update
scheduleUpdate(dmInfo);
LOG.info("Registration update succeed.");
if (observer != null) {
observer.onUpdateSuccess(dmInfo, registrationID);
}
return true;
} else {
LOG.error("Registration update failed: {} {}.", response.getCode(), response.getErrorMessage());
if (observer != null) {
observer.onUpdateFailure(dmInfo, response.getCode(), response.getErrorMessage());
}
return false;
}
}
private class RegistrationTask implements Runnable {
@Override
public void run() {
try {
ServersInfo serversInfo = ServersInfoExtractor.getInfo(objectEnablers);
if (!serversInfo.deviceMangements.isEmpty()) {
if (!register()) {
if (!bootstrap()) {
scheduleRegistration();
} else {
if (!register()) {
scheduleRegistration();
}
}
}
} else {
if (!bootstrap()) {
scheduleRegistration();
} else {
if (!register()) {
scheduleRegistration();
}
}
}
} catch (InterruptedException e) {
LOG.info("Registration task interrupted.");
} catch (RuntimeException e) {
LOG.error("Unexpected exception during update registration task", e);
}
}
}
private void scheduleRegistration() {
if (started.get()) {
LOG.info("Unable to connect to any server, next retry in {}s...", BS_RETRY);
registerFuture = schedExecutor.schedule(new RegistrationTask(), BS_RETRY, TimeUnit.SECONDS);
}
}
private void scheduleUpdate(DmServerInfo dmInfo) {
if (started.get()) {
// calculate next update : lifetime - 10%
// dmInfo.lifetime is in seconds
long nextUpdate = dmInfo.lifetime * 900l;
LOG.info("Next registration update in {}s...", nextUpdate / 1000.0);
updateFuture = schedExecutor.schedule(new UpdateRegistrationTask(), nextUpdate, TimeUnit.MILLISECONDS);
}
}
private class UpdateRegistrationTask implements Runnable {
@Override
public void run() {
try {
if (!update()) {
if (!register()) {
if (!bootstrap()) {
scheduleRegistration();
} else {
if (!register()) {
scheduleRegistration();
}
}
}
}
} catch (InterruptedException e) {
LOG.info("Registration update task interrupted.");
} catch (RuntimeException e) {
LOG.error("Unexpected exception during update registration task", e);
}
}
}
private void cancelUpdateTask(boolean mayinterrupt) {
if (updateFuture != null) {
updateFuture.cancel(mayinterrupt);
}
}
private void cancelRegistrationTask() {
if (registerFuture != null) {
registerFuture.cancel(true);
}
}
public void stop(boolean deregister) {
started.set(false);
cancelUpdateTask(true);
// TODO we should manage the case where we stop in the middle of a bootstrap session ...
cancelRegistrationTask();
try {
if (deregister)
deregister();
} catch (InterruptedException e) {
}
}
public void destroy(boolean deregister) {
started.set(false);
// TODO we should manage the case where we stop in the middle of a bootstrap session ...
schedExecutor.shutdownNow();
try {
schedExecutor.awaitTermination(BS_TIMEOUT, TimeUnit.SECONDS);
if (deregister)
deregister();
} catch (InterruptedException e) {
}
}
/**
* @return the current registration Id or null
if the client is not registered.
*/
public String getRegistrationId() {
return registrationID;
}
/**
* @return the LWM2M client endpoint identifier.
*/
public String getEndpoint() {
return endpoint;
}
}