1fc959c7b7ca3a5b475c6883ca2652488180bde9
[iotivity.git] / cloud / interface / src / main / java / org / iotivity / cloud / ciserver / DeviceServerSystem.java
1 /*
2  * //******************************************************************
3  * //
4  * // Copyright 2016 Samsung Electronics All Rights Reserved.
5  * //
6  * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
7  * //
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
11  * //
12  * //      http://www.apache.org/licenses/LICENSE-2.0
13  * //
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.
19  * //
20  * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
21  */
22 package org.iotivity.cloud.ciserver;
23
24 import java.util.HashMap;
25 import java.util.Iterator;
26
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;
49
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;
54
55 /**
56  *
57  * This class provides a set of APIs to manage all of request
58  *
59  */
60
61 public class DeviceServerSystem extends ServerSystem {
62
63     IRequestChannel mRDServer = null;
64
65     public DeviceServerSystem() {
66         mRDServer = ConnectorPool.getConnection("rd");
67     }
68
69     /**
70      *
71      * This class provides a set of APIs to manage device pool.
72      *
73      */
74     public class CoapDevicePool {
75         HashMap<String, Device> mMapDevice = new HashMap<>();
76
77         /**
78          * API for adding device information into pool.
79          * 
80          * @param device
81          *            device to be added
82          */
83         public void addDevice(Device device) {
84             String deviceId = ((CoapDevice) device).getDeviceId();
85             synchronized (mMapDevice) {
86                 mMapDevice.put(deviceId, device);
87             }
88         }
89
90         /**
91          * API for removing device information into pool.
92          * 
93          * @param device
94          *            device to be removed
95          */
96         public void removeDevice(Device device) throws ClientException {
97             String deviceId = ((CoapDevice) device).getDeviceId();
98             synchronized (mMapDevice) {
99                 if (mMapDevice.get(deviceId) == device) {
100                     mMapDevice.remove(deviceId);
101                 }
102             }
103             removeObserveDevice(device);
104         }
105
106         private void removeObserveDevice(Device device) throws ClientException {
107             Iterator<String> iterator = mMapDevice.keySet().iterator();
108             while (iterator.hasNext()) {
109                 String deviceId = iterator.next();
110                 CoapDevice getDevice = (CoapDevice) mDevicePool
111                         .queryDevice(deviceId);
112                 getDevice.removeObserveChannel(
113                         ((CoapDevice) device).getRequestChannel());
114             }
115         }
116
117         /**
118          * API for getting device information.
119          * 
120          * @param deviceId
121          *            device id to get device
122          */
123         public Device queryDevice(String deviceId) {
124             Device device = null;
125             synchronized (mMapDevice) {
126                 device = mMapDevice.get(deviceId);
127             }
128             return device;
129         }
130     }
131
132     CoapDevicePool mDevicePool = new CoapDevicePool();
133
134     /**
135      *
136      * This class provides a set of APIs to manage life cycle of coap message.
137      *
138      */
139     @Sharable
140     class CoapLifecycleHandler extends ChannelDuplexHandler {
141         @Override
142         public void channelRead(ChannelHandlerContext ctx, Object msg) {
143
144             if (msg instanceof CoapRequest) {
145                 try {
146                     CoapDevice coapDevice = (CoapDevice) ctx.channel()
147                             .attr(keyDevice).get();
148
149                     if (coapDevice.isExpiredTime()) {
150                         throw new UnAuthorizedException("token is expired");
151                     }
152
153                     CoapRequest coapRequest = (CoapRequest) msg;
154                     IRequestChannel targetChannel = null;
155                     if (coapRequest.getUriPathSegments()
156                             .contains(Constants.REQ_DEVICE_ID)) {
157                         CoapDevice targetDevice = (CoapDevice) mDevicePool
158                                 .queryDevice(coapRequest.getUriPathSegments()
159                                         .get(1));
160                         targetChannel = targetDevice.getRequestChannel();
161                     }
162                     switch (coapRequest.getObserve()) {
163                         case SUBSCRIBE:
164                             coapDevice.addObserveRequest(
165                                     Bytes.bytesToLong(coapRequest.getToken()),
166                                     coapRequest);
167                             coapDevice.addObserveChannel(targetChannel);
168                             break;
169                         case UNSUBSCRIBE:
170                             coapDevice.removeObserveChannel(targetChannel);
171                             coapDevice.removeObserveRequest(
172                                     Bytes.bytesToLong(coapRequest.getToken()));
173                             break;
174                         default:
175                             break;
176                     }
177
178                 } catch (Throwable t) {
179                     Log.f(ctx.channel(), t);
180                     ResponseStatus responseStatus = t instanceof ServerException
181                             ? ((ServerException) t).getErrorResponse()
182                             : ResponseStatus.INTERNAL_SERVER_ERROR;
183                     ctx.writeAndFlush(MessageBuilder
184                             .createResponse((CoapRequest) msg, responseStatus));
185                     ctx.close();
186                 }
187             }
188
189             ctx.fireChannelRead(msg);
190         }
191
192         @Override
193         public void channelActive(ChannelHandlerContext ctx) {
194             Device device = ctx.channel().attr(keyDevice).get();
195             // Authenticated device connected
196
197             sendDevicePresence(device.getDeviceId(), "on");
198             mDevicePool.addDevice(device);
199
200             device.onConnected();
201         }
202
203         @Override
204         public void channelInactive(ChannelHandlerContext ctx)
205                 throws ClientException {
206             Device device = ctx.channel().attr(keyDevice).get();
207             // Some cases, this event occurs after new device connected using
208             // same di.
209             // So compare actual value, and remove if same.
210             if (device != null) {
211                 sendDevicePresence(device.getDeviceId(), "off");
212
213                 device.onDisconnected();
214
215                 mDevicePool.removeDevice(device);
216                 ctx.channel().attr(keyDevice).remove();
217
218             }
219         }
220
221         /**
222          * API for sending state to resource directory
223          * 
224          * @param deviceId
225          *            device id to be sent to resource directory
226          * @param state
227          *            device state to be sent to resource directory
228          */
229         public void sendDevicePresence(String deviceId, String state) {
230
231             Cbor<HashMap<String, Object>> cbor = new Cbor<>();
232             HashMap<String, Object> payload = new HashMap<String, Object>();
233             payload.put(Constants.REQ_DEVICE_ID, deviceId);
234             payload.put(Constants.PRESENCE_STATE, state);
235             StringBuffer uriPath = new StringBuffer();
236             uriPath.append("/" + Constants.PREFIX_OIC);
237             uriPath.append("/" + Constants.DEVICE_PRESENCE_URI);
238             mRDServer.sendRequest(MessageBuilder.createRequest(
239                     RequestMethod.POST, uriPath.toString(), null,
240                     ContentFormat.APPLICATION_CBOR,
241                     cbor.encodingPayloadToCbor(payload)), null);
242         }
243     }
244
245     CoapLifecycleHandler mLifeCycleHandler = new CoapLifecycleHandler();
246
247     @Sharable
248     class CoapAuthHandler extends ChannelDuplexHandler {
249         private Cbor<HashMap<String, Object>> mCbor = new Cbor<HashMap<String, Object>>();
250
251         @Override
252         public void channelActive(ChannelHandlerContext ctx) {
253             // Actual channel active should decided after authentication.
254         }
255
256         @Override
257         public void write(ChannelHandlerContext ctx, Object msg,
258                 ChannelPromise promise) {
259
260             try {
261
262                 if (!(msg instanceof CoapResponse)) {
263                     throw new BadRequestException(
264                             "this msg type is not CoapResponse");
265                 }
266                 // This is CoapResponse
267                 // Once the response is valid, add this to deviceList
268                 CoapResponse response = (CoapResponse) msg;
269
270                 switch (response.getUriPath()) {
271
272                     case OICConstants.ACCOUNT_SESSION_FULL_URI:
273                         HashMap<String, Object> payloadData = mCbor
274                                 .parsePayloadFromCbor(response.getPayload(),
275                                         HashMap.class);
276
277                         if (response.getStatus() != ResponseStatus.CHANGED) {
278                             throw new UnAuthorizedException();
279                         }
280
281                         if (payloadData == null) {
282                             throw new BadRequestException("payload is empty");
283                         }
284                         int remainTime = (int) payloadData
285                                 .get(Constants.EXPIRES_IN);
286
287                         Device device = ctx.channel().attr(keyDevice).get();
288                         ((CoapDevice) device).setExpiredPolicy(remainTime);
289
290                         // Remove current auth handler and replace to
291                         // LifeCycleHandler
292                         ctx.channel().pipeline().replace(this,
293                                 "LifeCycleHandler", mLifeCycleHandler);
294
295                         // Raise event that we have Authenticated device
296                         ctx.fireChannelActive();
297
298                         break;
299                 }
300
301                 ctx.writeAndFlush(msg);
302
303             } catch (Throwable t) {
304                 Log.f(ctx.channel(), t);
305                 ctx.writeAndFlush(msg);
306                 ctx.close();
307             }
308         }
309
310         @Override
311         public void channelRead(ChannelHandlerContext ctx, Object msg) {
312
313             try {
314                 if (!(msg instanceof CoapRequest)) {
315                     throw new BadRequestException(
316                             "this msg type is not CoapRequest");
317                 }
318
319                 // And check first response is VALID then add or cut
320                 CoapRequest request = (CoapRequest) msg;
321
322                 switch (request.getUriPath()) {
323                     // Check whether request is about account
324                     case OICConstants.ACCOUNT_FULL_URI:
325                     case OICConstants.ACCOUNT_TOKENREFRESH_FULL_URI:
326
327                         if (ctx.channel().attr(keyDevice).get() == null) {
328                             // Create device first and pass to upperlayer
329                             Device device = new CoapDevice(ctx);
330                             ctx.channel().attr(keyDevice).set(device);
331                         }
332
333                         break;
334
335                     case OICConstants.ACCOUNT_SESSION_FULL_URI:
336
337                         HashMap<String, Object> authPayload = mCbor
338                                 .parsePayloadFromCbor(request.getPayload(),
339                                         HashMap.class);
340
341                         Device device = ctx.channel().attr(keyDevice).get();
342
343                         if (device == null) {
344                             device = new CoapDevice(ctx);
345                             ctx.channel().attr(keyDevice).set(device);
346                         }
347
348                         if (authPayload == null) {
349                             throw new BadRequestException("payload is empty");
350                         }
351
352                         ((CoapDevice) device).updateDevice(
353                                 (String) authPayload.get(Constants.DEVICE_ID),
354                                 (String) authPayload.get(Constants.USER_ID),
355                                 (String) authPayload
356                                         .get(Constants.ACCESS_TOKEN));
357
358                         break;
359
360                     case OICConstants.KEEP_ALIVE_FULL_URI:
361                         // TODO: Pass ping request to upper layer
362                         break;
363
364                     default:
365                         throw new UnAuthorizedException(
366                                 "authentication required first");
367                 }
368
369                 ctx.fireChannelRead(msg);
370
371             } catch (Throwable t) {
372                 ResponseStatus responseStatus = t instanceof ServerException
373                         ? ((ServerException) t).getErrorResponse()
374                         : ResponseStatus.UNAUTHORIZED;
375                 ctx.writeAndFlush(MessageBuilder
376                         .createResponse((CoapRequest) msg, responseStatus));
377                 Log.f(ctx.channel(), t);
378             }
379         }
380     }
381
382     @Sharable
383     class HttpAuthHandler extends ChannelDuplexHandler {
384         @Override
385         public void channelActive(ChannelHandlerContext ctx) throws Exception {
386             // After current channel authenticated, raise to upper layer
387         }
388     }
389
390     @Override
391     public void addServer(Server server) {
392         if (server instanceof CoapServer) {
393             server.addHandler(new CoapAuthHandler());
394         }
395
396         if (server instanceof HttpServer) {
397             server.addHandler(new HttpAuthHandler());
398         }
399
400         super.addServer(server);
401     }
402
403     public CoapDevicePool getDevicePool() {
404         return mDevicePool;
405     }
406 }