14e247918ced48248f5fc2f4f2982b75411663e3
[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
26 import org.iotivity.cloud.base.OICConstants;
27 import org.iotivity.cloud.base.ServerSystem;
28 import org.iotivity.cloud.base.connector.ConnectorPool;
29 import org.iotivity.cloud.base.device.CoapDevice;
30 import org.iotivity.cloud.base.device.Device;
31 import org.iotivity.cloud.base.device.IRequestChannel;
32 import org.iotivity.cloud.base.exception.ServerException;
33 import org.iotivity.cloud.base.exception.ServerException.BadRequestException;
34 import org.iotivity.cloud.base.exception.ServerException.UnAuthorizedException;
35 import org.iotivity.cloud.base.protocols.MessageBuilder;
36 import org.iotivity.cloud.base.protocols.coap.CoapRequest;
37 import org.iotivity.cloud.base.protocols.coap.CoapResponse;
38 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
39 import org.iotivity.cloud.base.protocols.enums.RequestMethod;
40 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
41 import org.iotivity.cloud.base.server.CoapServer;
42 import org.iotivity.cloud.base.server.HttpServer;
43 import org.iotivity.cloud.base.server.Server;
44 import org.iotivity.cloud.util.Cbor;
45 import org.iotivity.cloud.util.Log;
46
47 import io.netty.channel.ChannelDuplexHandler;
48 import io.netty.channel.ChannelHandler.Sharable;
49 import io.netty.channel.ChannelHandlerContext;
50 import io.netty.channel.ChannelPromise;
51
52 public class DeviceServerSystem extends ServerSystem {
53
54     IRequestChannel mRDServer = null;
55
56     public DeviceServerSystem() {
57         mRDServer = ConnectorPool.getConnection("rd");
58     }
59
60     public class CoapDevicePool {
61         HashMap<String, Device> mMapDevice = new HashMap<>();
62
63         public void addDevice(Device device) {
64             String deviceId = ((CoapDevice) device).getDeviceId();
65             synchronized (mMapDevice) {
66                 mMapDevice.put(deviceId, device);
67             }
68         }
69
70         public void removeDevice(Device device) {
71             String deviceId = ((CoapDevice) device).getDeviceId();
72             synchronized (mMapDevice) {
73                 if (mMapDevice.get(deviceId) == device) {
74                     mMapDevice.remove(deviceId);
75                 }
76             }
77         }
78
79         public Device queryDevice(String deviceId) {
80             Device device = null;
81             synchronized (mMapDevice) {
82                 device = mMapDevice.get(deviceId);
83             }
84             return device;
85         }
86     }
87
88     CoapDevicePool mDevicePool = new CoapDevicePool();
89
90     @Sharable
91     class CoapLifecycleHandler extends ChannelDuplexHandler {
92         @Override
93         public void channelRead(ChannelHandlerContext ctx, Object msg) {
94
95             if (msg instanceof CoapRequest) {
96                 try {
97                     CoapDevice coapDevice = (CoapDevice) ctx.channel()
98                             .attr(keyDevice).get();
99
100                     if (coapDevice.isExpiredTime()) {
101                         throw new UnAuthorizedException("token is expired");
102                     }
103
104                 } catch (Throwable t) {
105                     Log.f(ctx.channel(), t);
106                     ResponseStatus responseStatus = t instanceof ServerException
107                             ? ((ServerException) t).getErrorResponse()
108                             : ResponseStatus.INTERNAL_SERVER_ERROR;
109                     ctx.writeAndFlush(MessageBuilder
110                             .createResponse((CoapRequest) msg, responseStatus));
111                     ctx.close();
112                 }
113             }
114
115             ctx.fireChannelRead(msg);
116         }
117
118         @Override
119         public void channelActive(ChannelHandlerContext ctx) {
120
121             // Authenticated device connected
122             Device device = ctx.channel().attr(keyDevice).get();
123             mDevicePool.addDevice(device);
124             device.onConnected();
125
126             sendDevicePresence(device.getDeviceId(), "on");
127         }
128
129         @Override
130         public void channelInactive(ChannelHandlerContext ctx) {
131             Device device = ctx.channel().attr(keyDevice).get();
132             // Some cases, this event occurs after new device connected using
133             // same di.
134             // So compare actual value, and remove if same.
135             if (device != null) {
136                 mDevicePool.removeDevice(device);
137                 device.onDisconnected();
138                 ctx.channel().attr(keyDevice).remove();
139
140                 sendDevicePresence(device.getDeviceId(), "off");
141             }
142         }
143
144         public void sendDevicePresence(String deviceId, String state) {
145
146             Cbor<HashMap<String, Object>> cbor = new Cbor<>();
147             HashMap<String, Object> payload = new HashMap<String, Object>();
148             payload.put(Constants.REQ_DEVICE_ID, deviceId);
149             payload.put(Constants.PRESENCE_STATE, state);
150             StringBuffer uriPath = new StringBuffer();
151             uriPath.append("/" + Constants.PREFIX_OIC);
152             uriPath.append("/" + Constants.DEVICE_PRESENCE_URI);
153             mRDServer.sendRequest(MessageBuilder.createRequest(
154                     RequestMethod.POST, uriPath.toString(), null,
155                     ContentFormat.APPLICATION_CBOR,
156                     cbor.encodingPayloadToCbor(payload)), null);
157         }
158     }
159
160     CoapLifecycleHandler mLifeCycleHandler = new CoapLifecycleHandler();
161
162     @Sharable
163     class CoapAuthHandler extends ChannelDuplexHandler {
164         private Cbor<HashMap<String, Object>> mCbor = new Cbor<HashMap<String, Object>>();
165
166         @Override
167         public void channelActive(ChannelHandlerContext ctx) {
168             // Actual channel active should decided after authentication.
169         }
170
171         @Override
172         public void write(ChannelHandlerContext ctx, Object msg,
173                 ChannelPromise promise) {
174
175             try {
176
177                 if (!(msg instanceof CoapResponse)) {
178                     throw new BadRequestException(
179                             "this msg type is not CoapResponse");
180                 }
181                 // This is CoapResponse
182                 // Once the response is valid, add this to deviceList
183                 CoapResponse response = (CoapResponse) msg;
184
185                 switch (response.getUriPath()) {
186
187                     case OICConstants.ACCOUNT_SESSION_FULL_URI:
188                         HashMap<String, Object> payloadData = mCbor
189                                 .parsePayloadFromCbor(response.getPayload(),
190                                         HashMap.class);
191
192                         if (response.getStatus() != ResponseStatus.CHANGED) {
193                             throw new UnAuthorizedException();
194                         }
195
196                         if (payloadData == null) {
197                             throw new BadRequestException("payload is empty");
198                         }
199                         int remainTime = (int) payloadData
200                                 .get(Constants.EXPIRES_IN);
201
202                         Device device = ctx.channel().attr(keyDevice).get();
203                         ((CoapDevice) device).setExpiredPolicy(remainTime);
204
205                         // Remove current auth handler and replace to
206                         // LifeCycleHandler
207                         ctx.channel().pipeline().replace(this,
208                                 "LifeCycleHandler", mLifeCycleHandler);
209
210                         // Raise event that we have Authenticated device
211                         ctx.fireChannelActive();
212
213                         break;
214                 }
215
216                 ctx.writeAndFlush(msg);
217
218             } catch (Throwable t) {
219                 Log.f(ctx.channel(), t);
220                 ctx.writeAndFlush(msg);
221                 ctx.close();
222             }
223         }
224
225         @Override
226         public void channelRead(ChannelHandlerContext ctx, Object msg) {
227
228             try {
229                 if (!(msg instanceof CoapRequest)) {
230                     throw new BadRequestException(
231                             "this msg type is not CoapRequest");
232                 }
233
234                 // And check first response is VALID then add or cut
235                 CoapRequest request = (CoapRequest) msg;
236
237                 switch (request.getUriPath()) {
238                     // Check whether request is about account
239                     case OICConstants.ACCOUNT_FULL_URI:
240                     case OICConstants.ACCOUNT_TOKENREFRESH_FULL_URI:
241
242                         if (ctx.channel().attr(keyDevice).get() == null) {
243                             // Create device first and pass to upperlayer
244                             Device device = new CoapDevice(ctx);
245                             ctx.channel().attr(keyDevice).set(device);
246                         }
247
248                         break;
249
250                     case OICConstants.ACCOUNT_SESSION_FULL_URI:
251
252                         HashMap<String, Object> authPayload = mCbor
253                                 .parsePayloadFromCbor(request.getPayload(),
254                                         HashMap.class);
255
256                         Device device = ctx.channel().attr(keyDevice).get();
257
258                         if (device == null) {
259                             device = new CoapDevice(ctx);
260                             ctx.channel().attr(keyDevice).set(device);
261                         }
262
263                         if (authPayload == null) {
264                             throw new BadRequestException("payload is empty");
265                         }
266
267                         ((CoapDevice) device).updateDevice(
268                                 (String) authPayload.get(Constants.DEVICE_ID),
269                                 (String) authPayload.get(Constants.USER_ID),
270                                 (String) authPayload
271                                         .get(Constants.ACCESS_TOKEN));
272
273                         break;
274
275                     case OICConstants.KEEP_ALIVE_FULL_URI:
276                         // TODO: Pass ping request to upper layer
277                         break;
278
279                     default:
280                         throw new UnAuthorizedException(
281                                 "authentication required first");
282                 }
283
284                 ctx.fireChannelRead(msg);
285
286             } catch (Throwable t) {
287                 ResponseStatus responseStatus = t instanceof ServerException
288                         ? ((ServerException) t).getErrorResponse()
289                         : ResponseStatus.UNAUTHORIZED;
290                 ctx.writeAndFlush(MessageBuilder
291                         .createResponse((CoapRequest) msg, responseStatus));
292                 Log.f(ctx.channel(), t);
293             }
294         }
295     }
296
297     @Sharable
298     class HttpAuthHandler extends ChannelDuplexHandler {
299         @Override
300         public void channelActive(ChannelHandlerContext ctx) throws Exception {
301             // After current channel authenticated, raise to upper layer
302         }
303     }
304
305     @Override
306     public void addServer(Server server) {
307         if (server instanceof CoapServer) {
308             server.addHandler(new CoapAuthHandler());
309         }
310
311         if (server instanceof HttpServer) {
312             server.addHandler(new HttpAuthHandler());
313         }
314
315         super.addServer(server);
316     }
317
318     public CoapDevicePool getDevicePool() {
319         return mDevicePool;
320     }
321 }