Add CoAP over Websocket interface in cloud
[iotivity.git] / cloud / stack / src / main / java / org / iotivity / cloud / base / protocols / coap / websocket / WebSocketFrameHandler.java
1 /*
2  * //******************************************************************
3  * //
4  * // Copyright 2017 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.base.protocols.coap.websocket;
23
24 import io.netty.buffer.ByteBuf;
25 import io.netty.buffer.Unpooled;
26 import io.netty.channel.ChannelDuplexHandler;
27 import io.netty.channel.ChannelFuture;
28 import io.netty.channel.ChannelFutureListener;
29 import io.netty.channel.ChannelHandlerContext;
30 import io.netty.channel.ChannelPromise;
31 import io.netty.handler.codec.http.DefaultFullHttpResponse;
32 import io.netty.handler.codec.http.HttpObjectAggregator;
33 import io.netty.handler.codec.http.HttpServerCodec;
34 import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
35 import io.netty.handler.codec.http.websocketx.WebSocketFrame;
36
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.List;
40
41 import org.iotivity.cloud.base.exception.ServerException.BadRequestException;
42 import org.iotivity.cloud.base.exception.ServerException.InternalServerErrorException;
43 import org.iotivity.cloud.base.protocols.coap.CoapDecoder;
44 import org.iotivity.cloud.base.protocols.coap.CoapEncoder;
45 import org.iotivity.cloud.base.protocols.coap.CoapMessage;
46 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
47 import org.iotivity.cloud.util.Cbor;
48 import org.iotivity.cloud.util.JSONUtil;
49 import org.iotivity.cloud.util.Log;
50
51 public class WebSocketFrameHandler extends ChannelDuplexHandler {
52
53     @Override
54     public void channelActive(ChannelHandlerContext ctx) throws Exception {
55         Log.v(ctx.channel().id().asLongText().substring(26)
56                 + " WebSocket Connected, Address: "
57                 + ctx.channel().remoteAddress().toString());
58
59         ctx.fireChannelActive();
60     }
61
62     @Override
63     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
64         Log.v(ctx.channel().id().asLongText().substring(26)
65                 + " WebSocket Disconnected, Address: "
66                 + ctx.channel().remoteAddress().toString());
67
68         ctx.fireChannelInactive();
69     }
70
71     @Override
72     public void channelRead(ChannelHandlerContext ctx, Object msg)
73             throws Exception {
74         // TODO check ping pong
75
76         if (msg instanceof BinaryWebSocketFrame) {
77
78             List<Object> messages = new ArrayList<>();
79             new CoapDecoder().decode(((BinaryWebSocketFrame) msg).content(),
80                     messages);
81
82             for (Object message : messages) {
83                 if (message instanceof CoapMessage) {
84                     CoapMessage coapMessage = (CoapMessage) message;
85
86                     // convert content format to cbor if content format is json.
87                     if (coapMessage.getPayloadSize() != 0
88                             && coapMessage.getContentFormat().equals(
89                                     ContentFormat.APPLICATION_JSON)) {
90                         byte[] payload = coapMessage.getPayload();
91                         coapMessage.setPayload(convertJsonToCbor(payload));
92                         coapMessage
93                                 .setContentFormat(ContentFormat.APPLICATION_CBOR);
94                     }
95                     ctx.fireChannelRead(coapMessage);
96                 }
97             }
98         } else {
99             throw new BadRequestException("invalid request message type");
100         }
101     }
102
103     @Override
104     public void write(ChannelHandlerContext ctx, Object msg,
105             ChannelPromise promise) throws Exception {
106         Object newMsg = msg;
107
108         if (msg instanceof DefaultFullHttpResponse) {
109
110             ChannelFuture ch = ctx.writeAndFlush(newMsg);
111             ch.addListener(new ChannelFutureListener() {
112
113                 @Override
114                 public void operationComplete(ChannelFuture future)
115                         throws Exception {
116                     Log.v(future.channel().id().asLongText().substring(26)
117                             + " WebSocket Handshake done, Address: "
118                             + future.channel().remoteAddress().toString());
119
120                     // remove http encoder/decoder after handshake done.
121                     future.channel().pipeline().remove(HttpServerCodec.class);
122                     future.channel().pipeline()
123                             .remove(HttpObjectAggregator.class);
124                 }
125             });
126
127             return;
128         }
129         if (msg instanceof CoapMessage) {
130
131             CoapMessage coapMessage = (CoapMessage) msg;
132
133             // covert content format to json.
134             if (coapMessage.getPayloadSize() != 0) {
135                 byte[] payload = coapMessage.getPayload();
136                 coapMessage.setPayload(convertCborToJson(payload));
137                 coapMessage.setContentFormat(ContentFormat.APPLICATION_JSON);
138             }
139
140             ByteBuf encodedBytes = Unpooled.buffer();
141             new CoapEncoder().encode((CoapMessage) msg, encodedBytes);
142             WebSocketFrame frame = new BinaryWebSocketFrame(encodedBytes);
143             newMsg = frame;
144         } else {
145             throw new InternalServerErrorException(
146                     "invalid response message type");
147         }
148
149         ctx.writeAndFlush(newMsg);
150     }
151
152     @Override
153     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
154             throws Exception {
155
156         cause.printStackTrace();
157     }
158
159     private byte[] convertJsonToCbor(byte[] jsonData) {
160
161         JSONUtil<HashMap<String, Object>> json = new JSONUtil<>();
162         HashMap<String, Object> parsedData = json.parseJSON(jsonData,
163                 HashMap.class);
164
165         Cbor<HashMap<String, Object>> cbor = new Cbor<>();
166         return cbor.encodingPayloadToCbor(parsedData);
167     }
168
169     private byte[] convertCborToJson(byte[] cborData) {
170
171         Cbor<Object> cbor = new Cbor<>();
172         Object parsedData = cbor.parsePayloadFromCbor(cborData, Object.class);
173
174         JSONUtil<String> json = new JSONUtil<>();
175         return json.writeJSON(parsedData).getBytes();
176     }
177 }