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) mDevicePool
113 .queryDevice(deviceId);
114 getDevice.removeObserveChannel(
115 ((CoapDevice) device).getRequestChannel());
120 * API for getting device information.
123 * device id to get device
125 public Device queryDevice(String deviceId) {
126 Device device = null;
127 synchronized (mMapDevice) {
128 device = mMapDevice.get(deviceId);
134 CoapDevicePool mDevicePool = new CoapDevicePool();
138 * This class provides a set of APIs to manage life cycle of coap message.
142 class CoapLifecycleHandler extends ChannelDuplexHandler {
144 public void channelRead(ChannelHandlerContext ctx, Object msg) {
146 if (msg instanceof CoapRequest) {
148 CoapDevice coapDevice = (CoapDevice) ctx.channel()
149 .attr(keyDevice).get();
151 if (coapDevice.isExpiredTime()) {
152 throw new UnAuthorizedException("token is expired");
155 CoapRequest coapRequest = (CoapRequest) msg;
156 IRequestChannel targetChannel = null;
157 if (coapRequest.getUriPathSegments()
158 .contains(Constants.REQ_DEVICE_ID)) {
159 CoapDevice targetDevice = (CoapDevice) mDevicePool
160 .queryDevice(coapRequest.getUriPathSegments()
162 targetChannel = targetDevice.getRequestChannel();
164 switch (coapRequest.getObserve()) {
166 coapDevice.addObserveRequest(
167 Bytes.bytesToLong(coapRequest.getToken()),
169 coapDevice.addObserveChannel(targetChannel);
172 coapDevice.removeObserveChannel(targetChannel);
173 coapDevice.removeObserveRequest(
174 Bytes.bytesToLong(coapRequest.getToken()));
180 } catch (Throwable t) {
181 Log.f(ctx.channel(), t);
182 ResponseStatus responseStatus = t instanceof ServerException
183 ? ((ServerException) t).getErrorResponse()
184 : ResponseStatus.INTERNAL_SERVER_ERROR;
185 ctx.writeAndFlush(MessageBuilder
186 .createResponse((CoapRequest) msg, responseStatus));
190 ctx.fireChannelRead(msg);
194 public void write(ChannelHandlerContext ctx, Object msg,
195 ChannelPromise promise) throws Exception {
197 if (!(msg instanceof CoapResponse)) {
198 throw new BadRequestException(
199 "this msg type is not CoapResponse");
201 // This is CoapResponse
202 // Once the response is valid, add this to deviceList
203 CoapResponse response = (CoapResponse) msg;
205 switch (response.getUriPath()) {
206 case OICConstants.ACCOUNT_SESSION_FULL_URI:
207 if (response.getStatus() != ResponseStatus.CHANGED) {
208 throw new UnAuthorizedException();
211 if (response.getPayload() != null) {
217 case OICConstants.ACCOUNT_FULL_URI:
218 if (response.getStatus() != ResponseStatus.DELETED) {
224 ctx.writeAndFlush(msg);
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() {