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