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