2 * //******************************************************************
4 * // Copyright 2016 Samsung Electronics All Rights Reserved.
6 * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
8 * // Licensed under the Apache License, Version 2.0 (the "License");
9 * // you may not use this file except in compliance with the License.
10 * // You may obtain a copy of the License at
12 * // http://www.apache.org/licenses/LICENSE-2.0
14 * // Unless required by applicable law or agreed to in writing, software
15 * // distributed under the License is distributed on an "AS IS" BASIS,
16 * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * // See the License for the specific language governing permissions and
18 * // limitations under the License.
20 * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
22 package org.iotivity.cloud.ciserver;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
28 import org.iotivity.cloud.base.OICConstants;
29 import org.iotivity.cloud.base.ServerSystem;
30 import org.iotivity.cloud.base.connector.ConnectorPool;
31 import org.iotivity.cloud.base.device.CoapDevice;
32 import org.iotivity.cloud.base.device.Device;
33 import org.iotivity.cloud.base.device.IRequestChannel;
34 import org.iotivity.cloud.base.exception.ClientException;
35 import org.iotivity.cloud.base.exception.ServerException;
36 import org.iotivity.cloud.base.exception.ServerException.BadOptionException;
37 import org.iotivity.cloud.base.exception.ServerException.BadRequestException;
38 import org.iotivity.cloud.base.exception.ServerException.InternalServerErrorException;
39 import org.iotivity.cloud.base.exception.ServerException.UnAuthorizedException;
40 import org.iotivity.cloud.base.protocols.MessageBuilder;
41 import org.iotivity.cloud.base.protocols.coap.CoapRequest;
42 import org.iotivity.cloud.base.protocols.coap.CoapResponse;
43 import org.iotivity.cloud.base.protocols.coap.CoapSignaling;
44 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
45 import org.iotivity.cloud.base.protocols.enums.RequestMethod;
46 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
47 import org.iotivity.cloud.base.protocols.enums.SignalingMethod;
48 import org.iotivity.cloud.base.server.CoapServer;
49 import org.iotivity.cloud.base.server.HttpServer;
50 import org.iotivity.cloud.base.server.Server;
51 import org.iotivity.cloud.base.server.WebSocketServer;
52 import org.iotivity.cloud.util.Bytes;
53 import org.iotivity.cloud.util.Cbor;
54 import org.iotivity.cloud.util.Log;
56 import io.netty.channel.ChannelDuplexHandler;
57 import io.netty.channel.ChannelHandler.Sharable;
58 import io.netty.channel.ChannelHandlerContext;
59 import io.netty.channel.ChannelInboundHandlerAdapter;
60 import io.netty.channel.ChannelPromise;
64 * This class provides a set of APIs to manage all of request
68 public class DeviceServerSystem extends ServerSystem {
70 private Cbor<HashMap<String, Object>> mCbor = new Cbor<HashMap<String, Object>>();
71 private HashMap<ChannelHandlerContext, CoapSignaling> mCsmMap = new HashMap<>();
73 IRequestChannel mRDServer = null;
75 public DeviceServerSystem() {
76 mRDServer = ConnectorPool.getConnection("rd");
81 * This class provides a set of APIs to manage device pool.
84 public static class CoapDevicePool {
85 HashMap<String, Device> mMapDevice = new HashMap<>();
88 * API for adding device information into pool.
93 public void addDevice(Device device) {
94 String deviceId = ((CoapDevice) device).getDeviceId();
95 synchronized (mMapDevice) {
96 mMapDevice.put(deviceId, device);
101 * API for removing device information into pool.
104 * device to be removed
106 public void removeDevice(Device device) throws ClientException {
107 String deviceId = ((CoapDevice) device).getDeviceId();
108 synchronized (mMapDevice) {
109 if (mMapDevice.get(deviceId) == device) {
110 mMapDevice.remove(deviceId);
113 removeObserveDevice(device);
116 private void removeObserveDevice(Device device) throws ClientException {
117 synchronized (mMapDevice) {
118 Iterator<String> iterator = mMapDevice.keySet().iterator();
119 while (iterator.hasNext()) {
120 String deviceId = iterator.next();
121 CoapDevice getDevice = (CoapDevice) queryDevice(deviceId);
122 getDevice.removeObserveChannel(
123 ((CoapDevice) device).getRequestChannel());
129 * API for getting device information.
132 * device id to get device
134 public Device queryDevice(String deviceId) {
135 Device device = null;
136 synchronized (mMapDevice) {
137 device = mMapDevice.get(deviceId);
143 CoapDevicePool mDevicePool = new CoapDevicePool();
147 * This class provides a set of APIs to manage life cycle of coap message.
151 class CoapLifecycleHandler extends ChannelDuplexHandler {
153 public void channelRead(ChannelHandlerContext ctx, Object msg) {
155 if (msg instanceof CoapRequest) {
157 CoapDevice coapDevice = (CoapDevice) ctx.channel()
158 .attr(keyDevice).get();
160 if (coapDevice.isExpiredTime()) {
161 throw new UnAuthorizedException("token is expired");
164 CoapRequest coapRequest = (CoapRequest) msg;
165 IRequestChannel targetChannel = null;
166 String urlPath = coapRequest.getUriPath();
168 if (urlPath == null) {
169 throw new InternalServerErrorException(
170 "request uriPath is null");
173 if (urlPath.contains(Constants.ROUTE_FULL_URI)) {
175 int RouteResourcePathSize = Constants.ROUTE_FULL_URI
177 List<String> uriPath = coapRequest.getUriPathSegments();
178 if (uriPath != null && !uriPath.isEmpty()) {
179 CoapDevice targetDevice = (CoapDevice) mDevicePool
181 .get(RouteResourcePathSize - 1));
182 targetChannel = targetDevice.getRequestChannel();
185 switch (coapRequest.getObserve()) {
187 coapDevice.addObserveRequest(
188 Bytes.bytesToLong(coapRequest.getToken()),
190 coapDevice.addObserveChannel(targetChannel);
193 coapDevice.removeObserveChannel(targetChannel);
194 coapDevice.removeObserveRequest(
195 Bytes.bytesToLong(coapRequest.getToken()));
201 } catch (Throwable t) {
202 Log.f(ctx.channel(), t);
203 ResponseStatus responseStatus = t instanceof ServerException
204 ? ((ServerException) t).getErrorResponse()
205 : ResponseStatus.INTERNAL_SERVER_ERROR;
206 ctx.writeAndFlush(MessageBuilder
207 .createResponse((CoapRequest) msg, responseStatus));
211 ctx.fireChannelRead(msg);
215 public void write(ChannelHandlerContext ctx, Object msg,
216 ChannelPromise promise) throws Exception {
218 boolean bCloseConnection = false;
220 if (msg instanceof CoapResponse) {
221 // This is CoapResponse
222 // Once the response is valid, add this to deviceList
223 CoapResponse response = (CoapResponse) msg;
225 String urlPath = response.getUriPath();
227 if (urlPath == null) {
228 throw new InternalServerErrorException(
229 "request uriPath is null");
233 case OICConstants.ACCOUNT_SESSION_FULL_URI:
234 if (response.getStatus() != ResponseStatus.CHANGED) {
235 bCloseConnection = true;
238 case OICConstants.ACCOUNT_FULL_URI:
239 if (response.getStatus() == ResponseStatus.DELETED) {
240 bCloseConnection = true;
246 ctx.writeAndFlush(msg);
248 if (bCloseConnection == true) {
254 public void channelActive(ChannelHandlerContext ctx) {
255 Device device = ctx.channel().attr(keyDevice).get();
256 // Authenticated device connected
258 sendDevicePresence(device.getDeviceId(), "on");
259 mDevicePool.addDevice(device);
261 device.onConnected();
265 public void channelInactive(ChannelHandlerContext ctx)
266 throws ClientException {
267 Device device = ctx.channel().attr(keyDevice).get();
269 // Some cases, this event occurs after new device connected using
271 // So compare actual value, and remove if same.
272 if (device != null) {
273 sendDevicePresence(device.getDeviceId(), "off");
275 device.onDisconnected();
277 mDevicePool.removeDevice(device);
278 ctx.channel().attr(keyDevice).remove();
284 * API for sending state to resource directory
287 * device id to be sent to resource directory
289 * device state to be sent to resource directory
291 public void sendDevicePresence(String deviceId, String state) {
293 Cbor<HashMap<String, Object>> cbor = new Cbor<>();
294 HashMap<String, Object> payload = new HashMap<String, Object>();
295 payload.put(Constants.REQ_DEVICE_ID, deviceId);
296 payload.put(Constants.PRESENCE_STATE, state);
297 StringBuffer uriPath = new StringBuffer();
298 uriPath.append("/" + Constants.PREFIX_OIC);
299 uriPath.append("/" + Constants.DEVICE_PRESENCE_URI);
300 mRDServer.sendRequest(MessageBuilder.createRequest(
301 RequestMethod.POST, uriPath.toString(), null,
302 ContentFormat.APPLICATION_CBOR,
303 cbor.encodingPayloadToCbor(payload)), null);
307 CoapLifecycleHandler mLifeCycleHandler = new CoapLifecycleHandler();
310 class CoapAuthHandler extends ChannelDuplexHandler {
313 public void channelActive(ChannelHandlerContext ctx) {
314 // Actual channel active should decided after authentication.
318 public void write(ChannelHandlerContext ctx, Object msg,
319 ChannelPromise promise) {
322 if (!(msg instanceof CoapResponse)) {
323 // throw new BadRequestException(
324 // "this msg type is not CoapResponse");
326 // TODO check websocket handshake response
327 ctx.writeAndFlush(msg);
330 // This is CoapResponse
331 // Once the response is valid, add this to deviceList
333 CoapResponse response = (CoapResponse) msg;
335 String urlPath = response.getUriPath();
337 if (urlPath == null) {
338 throw new InternalServerErrorException(
339 "request uriPath is null");
344 case OICConstants.ACCOUNT_SESSION_FULL_URI:
345 HashMap<String, Object> payloadData = mCbor
346 .parsePayloadFromCbor(response.getPayload(),
349 if (response.getStatus() != ResponseStatus.CHANGED) {
350 throw new UnAuthorizedException();
353 if (payloadData == null) {
354 throw new BadRequestException("payload is empty");
356 int remainTime = (int) payloadData
357 .get(Constants.EXPIRES_IN);
359 Device device = ctx.channel().attr(keyDevice).get();
360 ((CoapDevice) device).setExpiredPolicy(remainTime);
362 // Remove current auth handler and replace to
364 ctx.channel().pipeline().replace(this,
365 "LifeCycleHandler", mLifeCycleHandler);
367 // Raise event that we have Authenticated device
368 ctx.fireChannelActive();
373 ctx.writeAndFlush(msg);
375 } catch (Throwable t) {
376 Log.f(ctx.channel(), t);
377 ctx.writeAndFlush(msg);
383 public void channelRead(ChannelHandlerContext ctx, Object msg) {
385 if (!(msg instanceof CoapRequest)) {
386 throw new BadRequestException(
387 "this msg type is not CoapRequest");
390 // And check first response is VALID then add or cut
391 CoapRequest request = (CoapRequest) msg;
393 String urlPath = request.getUriPath();
395 if (urlPath == null) {
396 throw new InternalServerErrorException(
397 "request uriPath is null");
401 // Check whether request is about account
402 case OICConstants.ACCOUNT_FULL_URI:
403 case OICConstants.ACCOUNT_TOKENREFRESH_FULL_URI:
405 if (ctx.channel().attr(keyDevice).get() == null) {
406 // Create device first and pass to upperlayer
407 Device device = new CoapDevice(ctx);
408 ctx.channel().attr(keyDevice).set(device);
413 case OICConstants.ACCOUNT_SESSION_FULL_URI:
415 HashMap<String, Object> authPayload = mCbor
416 .parsePayloadFromCbor(request.getPayload(),
419 Device device = ctx.channel().attr(keyDevice).get();
421 if (device == null) {
422 device = new CoapDevice(ctx);
423 ctx.channel().attr(keyDevice).set(device);
426 if (authPayload == null) {
427 throw new BadRequestException("payload is empty");
430 ((CoapDevice) device).updateDevice(
431 (String) authPayload.get(Constants.DEVICE_ID),
432 (String) authPayload.get(Constants.USER_ID),
434 .get(Constants.ACCESS_TOKEN));
438 case OICConstants.KEEP_ALIVE_FULL_URI:
439 // TODO: Pass ping request to upper layer
443 throw new UnAuthorizedException(
444 "authentication required first");
447 ctx.fireChannelRead(msg);
449 } catch (Throwable t) {
450 ResponseStatus responseStatus = t instanceof ServerException
451 ? ((ServerException) t).getErrorResponse()
452 : ResponseStatus.UNAUTHORIZED;
453 ctx.writeAndFlush(MessageBuilder
454 .createResponse((CoapRequest) msg, responseStatus));
455 Log.f(ctx.channel(), t);
461 static class HttpAuthHandler extends ChannelDuplexHandler {
463 public void channelActive(ChannelHandlerContext ctx) throws Exception {
464 // After current channel authenticated, raise to upper layer
469 class CoapSignalingHandler extends ChannelInboundHandlerAdapter {
472 public void channelInactive(ChannelHandlerContext ctx)
474 // delete csm information from the map
476 ctx.fireChannelInactive();
480 public void channelRead(ChannelHandlerContext ctx, Object msg) {
482 if (msg instanceof CoapSignaling) {
483 if (mCsmMap.get(ctx) == null) {
484 // In the server, the CSM message is sent to the device
486 CoapSignaling inicialCsm = (CoapSignaling) MessageBuilder
487 .createSignaling(SignalingMethod.CSM);
488 inicialCsm.setCsmMaxMessageSize(4294967295L);
489 ctx.writeAndFlush(inicialCsm);
491 CoapSignaling signaling = (CoapSignaling) msg;
492 switch (signaling.getSignalingMethod()) {
494 // get existing CSM from the map
495 CoapSignaling existingCsm = mCsmMap.get(ctx);
496 if (existingCsm == null) {
497 existingCsm = signaling;
499 // replace and cumulate CSM options
500 existingCsm.setCsmBlockWiseTransfer(
501 signaling.getCsmBlockWiseTransfer());
502 existingCsm.setCsmMaxMessageSize(
503 signaling.getCsmMaxMessageSize());
504 existingCsm.setCsmServerName(
505 signaling.getCsmServerName());
507 mCsmMap.put(ctx, existingCsm);
510 // TODO process PING signaling option
513 // TODO process PONG signaling option
521 throw new BadOptionException(
522 "unsupported CoAP Signaling option");
525 ctx.fireChannelRead(msg);
527 ctx.fireChannelRead(msg);
528 // TODO annotated codes must be removed to follow
529 // the CSM specification of draft-ietf-core-coap-tcp-tls-05
531 // if (mCsmMap.get(ctx) != null) {
532 // ctx.fireChannelRead(msg);
534 // // send ABORT signaling and close the connection
535 // ctx.writeAndFlush(MessageBuilder.createSignaling(
536 // SignalingMethod.ABORT,
538 // "Capability and Settings message (CSM) is not received
544 } catch (Throwable t) {
545 ResponseStatus responseStatus = t instanceof ServerException
546 ? ((ServerException) t).getErrorResponse()
547 : ResponseStatus.BAD_OPTION;
548 if (msg instanceof CoapRequest) {
549 ctx.writeAndFlush(MessageBuilder
550 .createResponse((CoapRequest) msg, responseStatus));
551 } else if (msg instanceof CoapSignaling) {
552 ctx.writeAndFlush(MessageBuilder.createSignalingResponse(
553 (CoapSignaling) msg, responseStatus));
555 Log.f(ctx.channel(), t);
562 public void addServer(Server server) {
563 if (server instanceof CoapServer) {
564 server.addHandler(new CoapSignalingHandler());
565 server.addHandler(new CoapAuthHandler());
568 if (server instanceof WebSocketServer) {
569 server.addHandler(new CoapAuthHandler());
572 if (server instanceof HttpServer) {
573 server.addHandler(new HttpAuthHandler());
576 super.addServer(server);
579 public CoapDevicePool getDevicePool() {