/******************************************************************************* * Copyright (c) 2013-2015 Sierra Wireless and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 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.server.californium.impl; import java.net.InetSocketAddress; import java.util.Set; import java.util.SortedMap; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import org.eclipse.californium.core.coap.MessageObserver; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.Endpoint; import org.eclipse.leshan.core.californium.AsyncRequestObserver; import org.eclipse.leshan.core.californium.SyncRequestObserver; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder; import org.eclipse.leshan.core.request.DownlinkRequest; import org.eclipse.leshan.core.response.ErrorCallback; import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.core.response.ResponseCallback; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.request.LwM2mRequestSender; import org.eclipse.leshan.util.Validate; public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender { private final Set<Endpoint> endpoints; private final ObservationServiceImpl observationService; private final LwM2mModelProvider modelProvider; private final LwM2mNodeDecoder decoder; private final LwM2mNodeEncoder encoder; // A map which contains all pending CoAP requests // This is mainly used to cancel request and avoid retransmission on de-registration private final ConcurrentNavigableMap<String/* registrationId#requestId */, Request /* pending coap Request */> pendingRequests = new ConcurrentSkipListMap<>(); /** * @param endpoints the CoAP endpoints to use for sending requests * @param observationService the service for keeping track of observed resources * @param modelProvider provides the supported objects definitions */ public CaliforniumLwM2mRequestSender(Set<Endpoint> endpoints, ObservationServiceImpl observationService, LwM2mModelProvider modelProvider, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder) { Validate.notNull(endpoints); Validate.notNull(observationService); Validate.notNull(modelProvider); this.observationService = observationService; this.endpoints = endpoints; this.modelProvider = modelProvider; this.encoder = encoder; this.decoder = decoder; } @Override public <T extends LwM2mResponse> T send(final Registration destination, final DownlinkRequest<T> request, Long timeout) throws InterruptedException { // Retrieve the objects definition final LwM2mModel model = modelProvider.getObjectModel(destination); // Create the CoAP request from LwM2m request CoapRequestBuilder coapRequestBuilder = new CoapRequestBuilder( new InetSocketAddress(destination.getAddress(), destination.getPort()), destination.getRootPath(), destination.getId(), destination.getEndpoint(), model, encoder); request.accept(coapRequestBuilder); final Request coapRequest = coapRequestBuilder.getRequest(); // Send CoAP request synchronously SyncRequestObserver<T> syncMessageObserver = new SyncRequestObserver<T>(coapRequest, timeout) { @Override public T buildResponse(Response coapResponse) { // Build LwM2m response LwM2mResponseBuilder<T> lwm2mResponseBuilder = new LwM2mResponseBuilder<>(coapRequest, coapResponse, destination, model, observationService, decoder); request.accept(lwm2mResponseBuilder); return lwm2mResponseBuilder.getResponse(); } }; coapRequest.addMessageObserver(syncMessageObserver); // Store pending request to cancel it on de-registration addPendingRequest(destination.getId(), coapRequest); // Send CoAP request asynchronously Endpoint endpoint = getEndpointForClient(destination); endpoint.sendRequest(coapRequest); // Wait for response, then return it return syncMessageObserver.waitForResponse(); } @Override public <T extends LwM2mResponse> void send(final Registration destination, final DownlinkRequest<T> request, ResponseCallback<T> responseCallback, ErrorCallback errorCallback) { // Retrieve the objects definition final LwM2mModel model = modelProvider.getObjectModel(destination); // Create the CoAP request from LwM2m request CoapRequestBuilder coapRequestBuilder = new CoapRequestBuilder( new InetSocketAddress(destination.getAddress(), destination.getPort()), destination.getRootPath(), destination.getId(), destination.getEndpoint(), model, encoder); request.accept(coapRequestBuilder); final Request coapRequest = coapRequestBuilder.getRequest(); // Add CoAP request callback coapRequest.addMessageObserver(new AsyncRequestObserver<T>(coapRequest, responseCallback, errorCallback) { @Override public T buildResponse(Response coapResponse) { // Build LwM2m response LwM2mResponseBuilder<T> lwm2mResponseBuilder = new LwM2mResponseBuilder<>(coapRequest, coapResponse, destination, model, observationService, decoder); request.accept(lwm2mResponseBuilder); return lwm2mResponseBuilder.getResponse(); } }); // Store pending request to cancel it on de-registration addPendingRequest(destination.getId(), coapRequest); // Send CoAP request asynchronously Endpoint endpoint = getEndpointForClient(destination); endpoint.sendRequest(coapRequest); } @Override public void cancelPendingRequests(Registration registration) { Validate.notNull(registration); String registrationId = registration.getId(); SortedMap<String, Request> requests = pendingRequests.subMap(getFloorKey(registrationId), getCeilingKey(registrationId)); for (Request coapRequest : requests.values()) { coapRequest.cancel(); } requests.clear(); } private String getFloorKey(String registrationId) { // The key format is regid#int, So we need a key which is always before this pattern (in natural order). return registrationId + '#'; } private String getCeilingKey(String registrationId) { // The key format is regid#int, So we need a key which is always after this pattern (in natural order). return registrationId + "#A"; } private String getKey(String registrationId, int requestId) { return registrationId + '#' + requestId; } private void addPendingRequest(String registrationId, Request coapRequest) { Validate.notNull(registrationId); CleanerMessageObserver observer = new CleanerMessageObserver(registrationId, coapRequest); coapRequest.addMessageObserver(observer); pendingRequests.put(observer.getRequestKey(), coapRequest); } private void removePendingRequest(String key, Request coapRequest) { Validate.notNull(key); pendingRequests.remove(key, coapRequest); } private class CleanerMessageObserver implements MessageObserver { private final String requestKey; private final Request coapRequest; public CleanerMessageObserver(String registrationId, Request coapRequest) { super(); requestKey = getKey(registrationId, hashCode()); this.coapRequest = coapRequest; } public String getRequestKey() { return requestKey; } @Override public void onRetransmission() { } @Override public void onResponse(Response response) { removePendingRequest(requestKey, coapRequest); } @Override public void onAcknowledgement() { // we can remove the request on acknowledgement as we only want to avoid CoAP retransmission. removePendingRequest(requestKey, coapRequest); } @Override public void onReject() { removePendingRequest(requestKey, coapRequest); } @Override public void onTimeout() { removePendingRequest(requestKey, coapRequest); } @Override public void onCancel() { removePendingRequest(requestKey, coapRequest); } } /** * Gets the CoAP endpoint that should be used to communicate with a given client. * * @param registration the client * @return the CoAP endpoint bound to the same network address and port that the client connected to during * registration. If no such CoAP endpoint is available, the first CoAP endpoint from the list of registered * endpoints is returned */ private Endpoint getEndpointForClient(Registration registration) { for (Endpoint ep : endpoints) { InetSocketAddress endpointAddress = ep.getAddress(); if (endpointAddress.equals(registration.getRegistrationEndpointAddress())) { return ep; } } throw new IllegalStateException( "can't find the client endpoint for address : " + registration.getRegistrationEndpointAddress()); } }