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.BadRequestException;
36 import org.iotivity.cloud.base.exception.ServerException.UnAuthorizedException;
37 import org.iotivity.cloud.base.protocols.MessageBuilder;
38 import org.iotivity.cloud.base.protocols.coap.CoapRequest;
39 import org.iotivity.cloud.base.protocols.coap.CoapResponse;
40 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
41 import org.iotivity.cloud.base.protocols.enums.RequestMethod;
42 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
43 import org.iotivity.cloud.base.server.CoapServer;
44 import org.iotivity.cloud.base.server.HttpServer;
45 import org.iotivity.cloud.base.server.Server;
46 import org.iotivity.cloud.util.Bytes;
47 import org.iotivity.cloud.util.Cbor;
48 import org.iotivity.cloud.util.Log;
50 import io.netty.channel.ChannelDuplexHandler;
51 import io.netty.channel.ChannelHandler.Sharable;
52 import io.netty.channel.ChannelHandlerContext;
53 import io.netty.channel.ChannelPromise;
57 * This class provides a set of APIs to manage all of request
61 public class DeviceServerSystem extends ServerSystem {
63 private Cbor<HashMap<String, Object>> mCbor = new Cbor<HashMap<String, Object>>();
65 IRequestChannel mRDServer = null;
67 public DeviceServerSystem() {
68 mRDServer = ConnectorPool.getConnection("rd");
73 * This class provides a set of APIs to manage device pool.
76 public class CoapDevicePool {
77 HashMap<String, Device> mMapDevice = new HashMap<>();
80 * API for adding device information into pool.
85 public void addDevice(Device device) {
86 String deviceId = ((CoapDevice) device).getDeviceId();
87 synchronized (mMapDevice) {
88 mMapDevice.put(deviceId, device);
93 * API for removing device information into pool.
96 * device to be removed
98 public void removeDevice(Device device) throws ClientException {
99 String deviceId = ((CoapDevice) device).getDeviceId();
100 synchronized (mMapDevice) {
101 if (mMapDevice.get(deviceId) == device) {
102 mMapDevice.remove(deviceId);
105 removeObserveDevice(device);
108 private void removeObserveDevice(Device device) throws ClientException {
109 Iterator<String> iterator = mMapDevice.keySet().iterator();
110 while (iterator.hasNext()) {
111 String deviceId = iterator.next();
112 CoapDevice getDevice = (CoapDevice) queryDevice(deviceId);
113 getDevice.removeObserveChannel(
114 ((CoapDevice) device).getRequestChannel());
119 * API for getting device information.
122 * device id to get device
124 public Device queryDevice(String deviceId) {
125 Device device = null;
126 synchronized (mMapDevice) {
127 device = mMapDevice.get(deviceId);
133 CoapDevicePool mDevicePool = new CoapDevicePool();
137 * This class provides a set of APIs to manage life cycle of coap message.
141 class CoapLifecycleHandler extends ChannelDuplexHandler {
143 public void channelRead(ChannelHandlerContext ctx, Object msg) {
145 if (msg instanceof CoapRequest) {
147 CoapDevice coapDevice = (CoapDevice) ctx.channel()
148 .attr(keyDevice).get();
150 if (coapDevice.isExpiredTime()) {
151 throw new UnAuthorizedException("token is expired");
154 CoapRequest coapRequest = (CoapRequest) msg;
155 IRequestChannel targetChannel = null;
156 if (coapRequest.getUriPath()
157 .contains(Constants.ROUTE_FULL_URI)) {
159 int RouteResourcePathSize = Constants.ROUTE_FULL_URI
161 CoapDevice targetDevice = (CoapDevice) mDevicePool
162 .queryDevice(coapRequest.getUriPathSegments()
163 .get(RouteResourcePathSize - 1));
164 targetChannel = targetDevice.getRequestChannel();
166 switch (coapRequest.getObserve()) {
168 coapDevice.addObserveRequest(
169 Bytes.bytesToLong(coapRequest.getToken()),
171 coapDevice.addObserveChannel(targetChannel);
174 coapDevice.removeObserveChannel(targetChannel);
175 coapDevice.removeObserveRequest(
176 Bytes.bytesToLong(coapRequest.getToken()));
182 } catch (Throwable t) {
183 Log.f(ctx.channel(), t);
184 ResponseStatus responseStatus = t instanceof ServerException
185 ? ((ServerException) t).getErrorResponse()
186 : ResponseStatus.INTERNAL_SERVER_ERROR;
187 ctx.writeAndFlush(MessageBuilder
188 .createResponse((CoapRequest) msg, responseStatus));
192 ctx.fireChannelRead(msg);
196 public void write(ChannelHandlerContext ctx, Object msg,
197 ChannelPromise promise) throws Exception {
199 boolean bCloseConnection = false;
201 if (msg instanceof CoapResponse) {
202 // This is CoapResponse
203 // Once the response is valid, add this to deviceList
204 CoapResponse response = (CoapResponse) msg;
206 switch (response.getUriPath()) {
207 case OICConstants.ACCOUNT_SESSION_FULL_URI:
208 if (response.getStatus() != ResponseStatus.CHANGED) {
209 bCloseConnection = true;
212 case OICConstants.ACCOUNT_FULL_URI:
213 if (response.getStatus() == ResponseStatus.DELETED) {
214 bCloseConnection = true;
220 ctx.writeAndFlush(msg);
222 if (bCloseConnection == true) {
228 public void channelActive(ChannelHandlerContext ctx) {
229 Device device = ctx.channel().attr(keyDevice).get();
230 // Authenticated device connected
232 sendDevicePresence(device.getDeviceId(), "on");
233 mDevicePool.addDevice(device);
235 device.onConnected();
239 public void channelInactive(ChannelHandlerContext ctx)
240 throws ClientException {
241 Device device = ctx.channel().attr(keyDevice).get();
242 // Some cases, this event occurs after new device connected using
244 // So compare actual value, and remove if same.
245 if (device != null) {
246 sendDevicePresence(device.getDeviceId(), "off");
248 device.onDisconnected();
250 mDevicePool.removeDevice(device);
251 ctx.channel().attr(keyDevice).remove();
257 * API for sending state to resource directory
260 * device id to be sent to resource directory
262 * device state to be sent to resource directory
264 public void sendDevicePresence(String deviceId, String state) {
266 Cbor<HashMap<String, Object>> cbor = new Cbor<>();
267 HashMap<String, Object> payload = new HashMap<String, Object>();
268 payload.put(Constants.REQ_DEVICE_ID, deviceId);
269 payload.put(Constants.PRESENCE_STATE, state);
270 StringBuffer uriPath = new StringBuffer();
271 uriPath.append("/" + Constants.PREFIX_OIC);
272 uriPath.append("/" + Constants.DEVICE_PRESENCE_URI);
273 mRDServer.sendRequest(MessageBuilder.createRequest(
274 RequestMethod.POST, uriPath.toString(), null,
275 ContentFormat.APPLICATION_CBOR,
276 cbor.encodingPayloadToCbor(payload)), null);
280 CoapLifecycleHandler mLifeCycleHandler = new CoapLifecycleHandler();
283 class CoapAuthHandler extends ChannelDuplexHandler {
286 public void channelActive(ChannelHandlerContext ctx) {
287 // Actual channel active should decided after authentication.
291 public void write(ChannelHandlerContext ctx, Object msg,
292 ChannelPromise promise) {
296 if (!(msg instanceof CoapResponse)) {
297 throw new BadRequestException(
298 "this msg type is not CoapResponse");
300 // This is CoapResponse
301 // Once the response is valid, add this to deviceList
302 CoapResponse response = (CoapResponse) msg;
304 switch (response.getUriPath()) {
306 case OICConstants.ACCOUNT_SESSION_FULL_URI:
307 HashMap<String, Object> payloadData = mCbor
308 .parsePayloadFromCbor(response.getPayload(),
311 if (response.getStatus() != ResponseStatus.CHANGED) {
312 throw new UnAuthorizedException();
315 if (payloadData == null) {
316 throw new BadRequestException("payload is empty");
318 int remainTime = (int) payloadData
319 .get(Constants.EXPIRES_IN);
321 Device device = ctx.channel().attr(keyDevice).get();
322 ((CoapDevice) device).setExpiredPolicy(remainTime);
324 // Remove current auth handler and replace to
326 ctx.channel().pipeline().replace(this,
327 "LifeCycleHandler", mLifeCycleHandler);
329 // Raise event that we have Authenticated device
330 ctx.fireChannelActive();
335 ctx.writeAndFlush(msg);
337 } catch (Throwable t) {
338 Log.f(ctx.channel(), t);
339 ctx.writeAndFlush(msg);
345 public void channelRead(ChannelHandlerContext ctx, Object msg) {
348 if (!(msg instanceof CoapRequest)) {
349 throw new BadRequestException(
350 "this msg type is not CoapRequest");
353 // And check first response is VALID then add or cut
354 CoapRequest request = (CoapRequest) msg;
356 switch (request.getUriPath()) {
357 // Check whether request is about account
358 case OICConstants.ACCOUNT_FULL_URI:
359 case OICConstants.ACCOUNT_TOKENREFRESH_FULL_URI:
361 if (ctx.channel().attr(keyDevice).get() == null) {
362 // Create device first and pass to upperlayer
363 Device device = new CoapDevice(ctx);
364 ctx.channel().attr(keyDevice).set(device);
369 case OICConstants.ACCOUNT_SESSION_FULL_URI:
371 HashMap<String, Object> authPayload = mCbor
372 .parsePayloadFromCbor(request.getPayload(),
375 Device device = ctx.channel().attr(keyDevice).get();
377 if (device == null) {
378 device = new CoapDevice(ctx);
379 ctx.channel().attr(keyDevice).set(device);
382 if (authPayload == null) {
383 throw new BadRequestException("payload is empty");
386 ((CoapDevice) device).updateDevice(
387 (String) authPayload.get(Constants.DEVICE_ID),
388 (String) authPayload.get(Constants.USER_ID),
390 .get(Constants.ACCESS_TOKEN));
394 case OICConstants.KEEP_ALIVE_FULL_URI:
395 // TODO: Pass ping request to upper layer
399 throw new UnAuthorizedException(
400 "authentication required first");
403 ctx.fireChannelRead(msg);
405 } catch (Throwable t) {
406 ResponseStatus responseStatus = t instanceof ServerException
407 ? ((ServerException) t).getErrorResponse()
408 : ResponseStatus.UNAUTHORIZED;
409 ctx.writeAndFlush(MessageBuilder
410 .createResponse((CoapRequest) msg, responseStatus));
411 Log.f(ctx.channel(), t);
417 class HttpAuthHandler extends ChannelDuplexHandler {
419 public void channelActive(ChannelHandlerContext ctx) throws Exception {
420 // After current channel authenticated, raise to upper layer
425 public void addServer(Server server) {
426 if (server instanceof CoapServer) {
427 server.addHandler(new CoapAuthHandler());
430 if (server instanceof HttpServer) {
431 server.addHandler(new HttpAuthHandler());
434 super.addServer(server);
437 public CoapDevicePool getDevicePool() {