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;
27 import org.iotivity.cloud.base.OICConstants;
28 import org.iotivity.cloud.base.ServerSystem;
29 import org.iotivity.cloud.base.connector.ConnectorPool;
30 import org.iotivity.cloud.base.device.CoapDevice;
31 import org.iotivity.cloud.base.device.Device;
32 import org.iotivity.cloud.base.device.IRequestChannel;
33 import org.iotivity.cloud.base.exception.ClientException;
34 import org.iotivity.cloud.base.exception.ServerException;
35 import org.iotivity.cloud.base.exception.ServerException.BadOptionException;
36 import org.iotivity.cloud.base.exception.ServerException.BadRequestException;
37 import org.iotivity.cloud.base.exception.ServerException.UnAuthorizedException;
38 import org.iotivity.cloud.base.protocols.MessageBuilder;
39 import org.iotivity.cloud.base.protocols.coap.CoapRequest;
40 import org.iotivity.cloud.base.protocols.coap.CoapResponse;
41 import org.iotivity.cloud.base.protocols.coap.CoapSignaling;
42 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
43 import org.iotivity.cloud.base.protocols.enums.RequestMethod;
44 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
45 import org.iotivity.cloud.base.protocols.enums.SignalingMethod;
46 import org.iotivity.cloud.base.server.CoapServer;
47 import org.iotivity.cloud.base.server.HttpServer;
48 import org.iotivity.cloud.base.server.Server;
49 import org.iotivity.cloud.base.server.WebSocketServer;
50 import org.iotivity.cloud.util.Bytes;
51 import org.iotivity.cloud.util.Cbor;
52 import org.iotivity.cloud.util.Log;
54 import io.netty.channel.ChannelDuplexHandler;
55 import io.netty.channel.ChannelHandler.Sharable;
56 import io.netty.channel.ChannelHandlerContext;
57 import io.netty.channel.ChannelInboundHandlerAdapter;
58 import io.netty.channel.ChannelPromise;
62 * This class provides a set of APIs to manage all of request
66 public class DeviceServerSystem extends ServerSystem {
68 private Cbor<HashMap<String, Object>> mCbor = new Cbor<HashMap<String, Object>>();
69 private HashMap<ChannelHandlerContext, CoapSignaling> mCsmMap = new HashMap<>();
71 IRequestChannel mRDServer = null;
73 public DeviceServerSystem() {
74 mRDServer = ConnectorPool.getConnection("rd");
79 * This class provides a set of APIs to manage device pool.
82 public class CoapDevicePool {
83 HashMap<String, Device> mMapDevice = new HashMap<>();
86 * API for adding device information into pool.
91 public void addDevice(Device device) {
92 String deviceId = ((CoapDevice) device).getDeviceId();
93 synchronized (mMapDevice) {
94 mMapDevice.put(deviceId, device);
99 * API for removing device information into pool.
102 * device to be removed
104 public void removeDevice(Device device) throws ClientException {
105 String deviceId = ((CoapDevice) device).getDeviceId();
106 synchronized (mMapDevice) {
107 if (mMapDevice.get(deviceId) == device) {
108 mMapDevice.remove(deviceId);
111 removeObserveDevice(device);
114 private void removeObserveDevice(Device device) throws ClientException {
115 Iterator<String> iterator = mMapDevice.keySet().iterator();
116 while (iterator.hasNext()) {
117 String deviceId = iterator.next();
118 CoapDevice getDevice = (CoapDevice) queryDevice(deviceId);
119 getDevice.removeObserveChannel(
120 ((CoapDevice) device).getRequestChannel());
125 * API for getting device information.
128 * device id to get device
130 public Device queryDevice(String deviceId) {
131 Device device = null;
132 synchronized (mMapDevice) {
133 device = mMapDevice.get(deviceId);
139 CoapDevicePool mDevicePool = new CoapDevicePool();
143 * This class provides a set of APIs to manage life cycle of coap message.
147 class CoapLifecycleHandler extends ChannelDuplexHandler {
149 public void channelRead(ChannelHandlerContext ctx, Object msg) {
151 if (msg instanceof CoapRequest) {
153 CoapDevice coapDevice = (CoapDevice) ctx.channel()
154 .attr(keyDevice).get();
156 if (coapDevice.isExpiredTime()) {
157 throw new UnAuthorizedException("token is expired");
160 CoapRequest coapRequest = (CoapRequest) msg;
161 IRequestChannel targetChannel = null;
162 if (coapRequest.getUriPath()
163 .contains(Constants.ROUTE_FULL_URI)) {
165 int RouteResourcePathSize = Constants.ROUTE_FULL_URI
167 CoapDevice targetDevice = (CoapDevice) mDevicePool
168 .queryDevice(coapRequest.getUriPathSegments()
169 .get(RouteResourcePathSize - 1));
170 targetChannel = targetDevice.getRequestChannel();
172 switch (coapRequest.getObserve()) {
174 coapDevice.addObserveRequest(
175 Bytes.bytesToLong(coapRequest.getToken()),
177 coapDevice.addObserveChannel(targetChannel);
180 coapDevice.removeObserveChannel(targetChannel);
181 coapDevice.removeObserveRequest(
182 Bytes.bytesToLong(coapRequest.getToken()));
188 } catch (Throwable t) {
189 Log.f(ctx.channel(), t);
190 ResponseStatus responseStatus = t instanceof ServerException
191 ? ((ServerException) t).getErrorResponse()
192 : ResponseStatus.INTERNAL_SERVER_ERROR;
193 ctx.writeAndFlush(MessageBuilder
194 .createResponse((CoapRequest) msg, responseStatus));
198 ctx.fireChannelRead(msg);
202 public void write(ChannelHandlerContext ctx, Object msg,
203 ChannelPromise promise) throws Exception {
205 boolean bCloseConnection = false;
207 if (msg instanceof CoapResponse) {
208 // This is CoapResponse
209 // Once the response is valid, add this to deviceList
210 CoapResponse response = (CoapResponse) msg;
212 switch (response.getUriPath()) {
213 case OICConstants.ACCOUNT_SESSION_FULL_URI:
214 if (response.getStatus() != ResponseStatus.CHANGED) {
215 bCloseConnection = true;
218 case OICConstants.ACCOUNT_FULL_URI:
219 if (response.getStatus() == ResponseStatus.DELETED) {
220 bCloseConnection = true;
226 ctx.writeAndFlush(msg);
228 if (bCloseConnection == true) {
234 public void channelActive(ChannelHandlerContext ctx) {
235 Device device = ctx.channel().attr(keyDevice).get();
236 // Authenticated device connected
238 sendDevicePresence(device.getDeviceId(), "on");
239 mDevicePool.addDevice(device);
241 device.onConnected();
245 public void channelInactive(ChannelHandlerContext ctx)
246 throws ClientException {
247 Device device = ctx.channel().attr(keyDevice).get();
249 // Some cases, this event occurs after new device connected using
251 // So compare actual value, and remove if same.
252 if (device != null) {
253 sendDevicePresence(device.getDeviceId(), "off");
255 device.onDisconnected();
257 mDevicePool.removeDevice(device);
258 ctx.channel().attr(keyDevice).remove();
264 * API for sending state to resource directory
267 * device id to be sent to resource directory
269 * device state to be sent to resource directory
271 public void sendDevicePresence(String deviceId, String state) {
273 Cbor<HashMap<String, Object>> cbor = new Cbor<>();
274 HashMap<String, Object> payload = new HashMap<String, Object>();
275 payload.put(Constants.REQ_DEVICE_ID, deviceId);
276 payload.put(Constants.PRESENCE_STATE, state);
277 StringBuffer uriPath = new StringBuffer();
278 uriPath.append("/" + Constants.PREFIX_OIC);
279 uriPath.append("/" + Constants.DEVICE_PRESENCE_URI);
280 mRDServer.sendRequest(MessageBuilder.createRequest(
281 RequestMethod.POST, uriPath.toString(), null,
282 ContentFormat.APPLICATION_CBOR,
283 cbor.encodingPayloadToCbor(payload)), null);
287 CoapLifecycleHandler mLifeCycleHandler = new CoapLifecycleHandler();
290 class CoapAuthHandler extends ChannelDuplexHandler {
293 public void channelActive(ChannelHandlerContext ctx) {
294 // Actual channel active should decided after authentication.
298 public void write(ChannelHandlerContext ctx, Object msg,
299 ChannelPromise promise) {
302 if (!(msg instanceof CoapResponse)) {
303 // throw new BadRequestException(
304 // "this msg type is not CoapResponse");
306 // TODO check websocket handshake response
307 ctx.writeAndFlush(msg);
310 // This is CoapResponse
311 // Once the response is valid, add this to deviceList
313 CoapResponse response = (CoapResponse) msg;
315 switch (response.getUriPath()) {
317 case OICConstants.ACCOUNT_SESSION_FULL_URI:
318 HashMap<String, Object> payloadData = mCbor
319 .parsePayloadFromCbor(response.getPayload(),
322 if (response.getStatus() != ResponseStatus.CHANGED) {
323 throw new UnAuthorizedException();
326 if (payloadData == null) {
327 throw new BadRequestException("payload is empty");
329 int remainTime = (int) payloadData
330 .get(Constants.EXPIRES_IN);
332 Device device = ctx.channel().attr(keyDevice).get();
333 ((CoapDevice) device).setExpiredPolicy(remainTime);
335 // Remove current auth handler and replace to
337 ctx.channel().pipeline().replace(this,
338 "LifeCycleHandler", mLifeCycleHandler);
340 // Raise event that we have Authenticated device
341 ctx.fireChannelActive();
346 ctx.writeAndFlush(msg);
348 } catch (Throwable t) {
349 Log.f(ctx.channel(), t);
350 ctx.writeAndFlush(msg);
356 public void channelRead(ChannelHandlerContext ctx, Object msg) {
358 if (!(msg instanceof CoapRequest)) {
359 throw new BadRequestException(
360 "this msg type is not CoapRequest");
363 // And check first response is VALID then add or cut
364 CoapRequest request = (CoapRequest) msg;
366 switch (request.getUriPath()) {
367 // Check whether request is about account
368 case OICConstants.ACCOUNT_FULL_URI:
369 case OICConstants.ACCOUNT_TOKENREFRESH_FULL_URI:
371 if (ctx.channel().attr(keyDevice).get() == null) {
372 // Create device first and pass to upperlayer
373 Device device = new CoapDevice(ctx);
374 ctx.channel().attr(keyDevice).set(device);
379 case OICConstants.ACCOUNT_SESSION_FULL_URI:
381 HashMap<String, Object> authPayload = mCbor
382 .parsePayloadFromCbor(request.getPayload(),
385 Device device = ctx.channel().attr(keyDevice).get();
387 if (device == null) {
388 device = new CoapDevice(ctx);
389 ctx.channel().attr(keyDevice).set(device);
392 if (authPayload == null) {
393 throw new BadRequestException("payload is empty");
396 ((CoapDevice) device).updateDevice(
397 (String) authPayload.get(Constants.DEVICE_ID),
398 (String) authPayload.get(Constants.USER_ID),
400 .get(Constants.ACCESS_TOKEN));
404 case OICConstants.KEEP_ALIVE_FULL_URI:
405 // TODO: Pass ping request to upper layer
409 throw new UnAuthorizedException(
410 "authentication required first");
413 ctx.fireChannelRead(msg);
415 } catch (Throwable t) {
416 ResponseStatus responseStatus = t instanceof ServerException
417 ? ((ServerException) t).getErrorResponse()
418 : ResponseStatus.UNAUTHORIZED;
419 ctx.writeAndFlush(MessageBuilder
420 .createResponse((CoapRequest) msg, responseStatus));
421 Log.f(ctx.channel(), t);
427 class HttpAuthHandler extends ChannelDuplexHandler {
429 public void channelActive(ChannelHandlerContext ctx) throws Exception {
430 // After current channel authenticated, raise to upper layer
435 class CoapSignalingHandler extends ChannelInboundHandlerAdapter {
438 public void channelInactive(ChannelHandlerContext ctx)
440 // delete csm information from the map
442 ctx.fireChannelInactive();
446 public void channelRead(ChannelHandlerContext ctx, Object msg) {
448 if (msg instanceof CoapSignaling) {
449 if (mCsmMap.get(ctx) == null) {
450 // In the server, the CSM message is sent to the device
452 CoapSignaling inicialCsm = (CoapSignaling) MessageBuilder
453 .createSignaling(SignalingMethod.CSM);
454 inicialCsm.setCsmMaxMessageSize(4294967295L);
455 ctx.writeAndFlush(inicialCsm);
457 CoapSignaling signaling = (CoapSignaling) msg;
458 switch (signaling.getSignalingMethod()) {
460 // get existing CSM from the map
461 CoapSignaling existingCsm = mCsmMap.get(ctx);
462 if (existingCsm == null) {
463 existingCsm = signaling;
465 // replace and cumulate CSM options
466 existingCsm.setCsmBlockWiseTransfer(
467 signaling.getCsmBlockWiseTransfer());
468 existingCsm.setCsmMaxMessageSize(
469 signaling.getCsmMaxMessageSize());
470 existingCsm.setCsmServerName(
471 signaling.getCsmServerName());
473 mCsmMap.put(ctx, existingCsm);
476 // TODO process PING signaling option
479 // TODO process PONG signaling option
487 throw new BadOptionException(
488 "unsupported CoAP Signaling option");
491 ctx.fireChannelRead(msg);
493 ctx.fireChannelRead(msg);
494 // TODO annotated codes must be removed to follow
495 // the CSM specification of draft-ietf-core-coap-tcp-tls-05
497 // if (mCsmMap.get(ctx) != null) {
498 // ctx.fireChannelRead(msg);
500 // // send ABORT signaling and close the connection
501 // ctx.writeAndFlush(MessageBuilder.createSignaling(
502 // SignalingMethod.ABORT,
504 // "Capability and Settings message (CSM) is not received
510 } catch (Throwable t) {
511 ResponseStatus responseStatus = t instanceof ServerException
512 ? ((ServerException) t).getErrorResponse()
513 : ResponseStatus.BAD_OPTION;
514 if (msg instanceof CoapRequest) {
515 ctx.writeAndFlush(MessageBuilder
516 .createResponse((CoapRequest) msg, responseStatus));
517 } else if (msg instanceof CoapSignaling) {
518 ctx.writeAndFlush(MessageBuilder.createSignalingResponse(
519 (CoapSignaling) msg, responseStatus));
521 Log.f(ctx.channel(), t);
528 public void addServer(Server server) {
529 if (server instanceof CoapServer) {
530 server.addHandler(new CoapSignalingHandler());
531 server.addHandler(new CoapAuthHandler());
534 if (server instanceof WebSocketServer) {
535 server.addHandler(new CoapAuthHandler());
538 if (server instanceof HttpServer) {
539 server.addHandler(new HttpAuthHandler());
542 super.addServer(server);
545 public CoapDevicePool getDevicePool() {