/******************************************************************************* * 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 static org.eclipse.leshan.core.californium.ExchangeUtil.extractIdentity; import static org.eclipse.leshan.core.californium.ResponseCodeUtil.fromLwM2mCode; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.coap.CoAP.Type; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.Exchange; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.leshan.Link; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.DeregisterRequest; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.UpdateRequest; import org.eclipse.leshan.core.request.exception.InvalidRequestException; 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.server.impl.SendableResponse; import org.eclipse.leshan.server.registration.RegistrationHandler; import org.eclipse.leshan.server.registration.RegistrationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A CoAP {@link Resource} in charge of handling clients registration requests. * <p> * This resource is the entry point of the Resource Directory ("/rd"). Each new client is added to the * {@link RegistrationService}. * </p> */ public class RegisterResource extends CoapResource { private static final String QUERY_PARAM_ENDPOINT = "ep="; private static final String QUERY_PARAM_BINDING_MODE = "b="; private static final String QUERY_PARAM_LWM2M_VERSION = "lwm2m="; private static final String QUERY_PARAM_SMS = "sms="; private static final String QUERY_PARAM_LIFETIME = "lt="; private static final Logger LOG = LoggerFactory.getLogger(RegisterResource.class); public static final String RESOURCE_NAME = "rd"; private final RegistrationHandler registrationHandler; public RegisterResource(RegistrationHandler registrationHandler) { super(RESOURCE_NAME); this.registrationHandler = registrationHandler; getAttributes().addResourceType("core.rd"); } @Override public void handleRequest(Exchange exchange) { try { super.handleRequest(exchange); } catch (InvalidRequestException e) { LOG.debug("InvalidRequestException while handling request({}) on the /rd resource", exchange.getRequest(), e); Response response = new Response(ResponseCode.BAD_REQUEST); response.setPayload(e.getMessage()); exchange.sendResponse(response); } catch (RuntimeException e) { LOG.error("Exception while handling request({}) on the /rd resource", exchange.getRequest(), e); exchange.sendResponse(new Response(ResponseCode.INTERNAL_SERVER_ERROR)); } } @Override public void handlePOST(CoapExchange exchange) { Request request = exchange.advanced().getRequest(); LOG.debug("POST received : {}", request); // The LWM2M spec (section 8.2) mandates the usage of confirmable messages if (!Type.CON.equals(request.getType())) { exchange.respond(ResponseCode.BAD_REQUEST); return; } List<String> uri = exchange.getRequestOptions().getUriPath(); if (uri == null || uri.size() == 0 || !RESOURCE_NAME.equals(uri.get(0))) { exchange.respond(ResponseCode.BAD_REQUEST); return; } if (uri.size() == 1) { handleRegister(exchange, request); return; } else if (uri.size() == 2) { handleUpdate(exchange, request, uri.get(1)); return; } else { exchange.respond(ResponseCode.BAD_REQUEST); return; } } @Override public void handleDELETE(CoapExchange exchange) { LOG.debug("DELETE received : {}", exchange.advanced().getRequest()); List<String> uri = exchange.getRequestOptions().getUriPath(); if (uri != null && uri.size() == 2 && RESOURCE_NAME.equals(uri.get(0))) { handleDeregister(exchange, uri.get(1)); } else { LOG.debug("Invalid deregistration"); exchange.respond(ResponseCode.NOT_FOUND); } } private void handleRegister(CoapExchange exchange, Request request) { // Get identity // -------------------------------- Identity sender = extractIdentity(exchange); // Create LwM2m request from CoAP request // -------------------------------- // We don't check content media type is APPLICATION LINK FORMAT for now as this is the only format we can expect String endpoint = null; Long lifetime = null; String smsNumber = null; String lwVersion = null; BindingMode binding = null; // Get object Links Link[] objectLinks = Link.parse(request.getPayload()); Map<String, String> additionalParams = new HashMap<>(); // Get parameters 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)) { 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.valueOf(param.substring(2)); } else { String[] tokens = param.split("\\="); if (tokens != null && tokens.length == 2) { additionalParams.put(tokens[0], tokens[1]); } } } // Create request RegisterRequest registerRequest = new RegisterRequest(endpoint, lifetime, lwVersion, binding, smsNumber, objectLinks, additionalParams); // Handle request // ------------------------------- InetSocketAddress serverEndpoint = exchange.advanced().getEndpoint().getAddress(); final SendableResponse<RegisterResponse> sendableResponse = registrationHandler.register(sender, registerRequest, serverEndpoint); RegisterResponse response = sendableResponse.getResponse(); // Create CoAP Response from LwM2m request // ------------------------------- if (response.getCode() == org.eclipse.leshan.ResponseCode.CREATED) { exchange.setLocationPath(RESOURCE_NAME + "/" + response.getRegistrationID()); exchange.respond(ResponseCode.CREATED); } else { exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); } sendableResponse.sent(); } private void handleUpdate(CoapExchange exchange, Request request, String registrationId) { // Get identity Identity sender = extractIdentity(exchange); // Create LwM2m request from CoAP request Long lifetime = null; String smsNumber = null; BindingMode binding = null; Link[] objectLinks = null; 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)) { smsNumber = param.substring(4); } else if (param.startsWith(QUERY_PARAM_BINDING_MODE)) { binding = BindingMode.valueOf(param.substring(2)); } } if (request.getPayload() != null && request.getPayload().length > 0) { objectLinks = Link.parse(request.getPayload()); } UpdateRequest updateRequest = new UpdateRequest(registrationId, lifetime, smsNumber, binding, objectLinks); // Handle request final SendableResponse<UpdateResponse> sendableResponse = registrationHandler.update(sender, updateRequest); UpdateResponse updateResponse = sendableResponse.getResponse(); // Create CoAP Response from LwM2m request exchange.respond(fromLwM2mCode(updateResponse.getCode()), updateResponse.getErrorMessage()); sendableResponse.sent(); } private void handleDeregister(CoapExchange exchange, String registrationId) { // Get identity Identity sender = extractIdentity(exchange); // Create request DeregisterRequest deregisterRequest = new DeregisterRequest(registrationId); // Handle request final SendableResponse<DeregisterResponse> sendableResponse = registrationHandler.deregister(sender, deregisterRequest); DeregisterResponse deregisterResponse = sendableResponse.getResponse(); // Create CoAP Response from LwM2m request exchange.respond(fromLwM2mCode(deregisterResponse.getCode()), deregisterResponse.getErrorMessage()); sendableResponse.sent(); } // Since the V1_0-20150615-D version of specification, the registration update should be a CoAP POST. // To keep compatibility with older version, we still accept CoAP PUT. // TODO remove this backward compatibility when the version 1.0.0 of the spec will be released. @Override public void handlePUT(CoapExchange exchange) { Request request = exchange.advanced().getRequest(); LOG.debug("UPDATE received : {}", request); if (!Type.CON.equals(request.getType())) { exchange.respond(ResponseCode.BAD_REQUEST); return; } List<String> uri = exchange.getRequestOptions().getUriPath(); if (uri == null || uri.size() != 2 || !RESOURCE_NAME.equals(uri.get(0))) { exchange.respond(ResponseCode.BAD_REQUEST); return; } LOG.debug( "Warning a client made a registration update using a CoAP PUT, a POST must be used since version V1_0-20150615-D of the specification. Request: {}", request); handleUpdate(exchange, request, uri.get(1)); } /* * Override the default behavior so that requests to sub resources (typically /rd/{client-reg-id}) are handled by * /rd resource. */ @Override public Resource getChild(String name) { return this; } }