- websocket handler added in cloud stack to support coap-websocket.
Change-Id: I57a8e1beb0e7f76391875b0461732bf30f9535fb
Signed-off-by: Minji Park <minjii.park@samsung.com>
Reviewed-on: https://gerrit.iotivity.org/gerrit/17331
Tested-by: jenkins-iotivity <jenkins@iotivity.org>
Reviewed-by: Jee Hyeok Kim <jihyeok13.kim@samsung.com>
4) Run .jar file
go to "target" folder
- $ java -jar CloudInterface-0.0.1-SNAPSHOT.jar arg1(CI CoAP Server Port) arg2(RD CoAP Server IP) arg3(RD CoAP Server Port) arg4(Account Server IP) arg5(Account Server Port) arg6(MQBroker IP) arg7(MQBroker Port) arg8(TLS mode required)
- e.g. java -jar CloudInterface-0.0.1-SNAPSHOT.jar 5683 127.0.0.1 5684 127.0.0.1 5685 127.0.0.1 5686 0
+ $ java -jar CloudInterface-0.0.1-SNAPSHOT.jar arg1(CI CoAP Server Port) arg2(RD CoAP Server IP) arg3(RD CoAP Server Port) arg4(Account Server IP) arg5(Account Server Port) arg6(MQBroker IP) arg7(MQBroker Port)
+ arg8(CI Http-CoAP proxy server port, '0' if unnecessary) arg9(CI CoAP websocket server port, '0' if unnecessary) arg10(TLS mode <0|1> required) arg11(web log IP, optional) and arg12(web log port, optional)
+ e.g. java -jar CloudInterface-0.0.1-SNAPSHOT.jar 5683 127.0.0.1 5684 127.0.0.1 5685 127.0.0.1 5686 80 8080 0
- Before running a CI server, you should run a RD server, Account server and MQBroker first.
- If you want to operate on TLS mode, "CLOUD_CERT_FILE(iotivitycloud.crt)", "CLOUD_KEY_FILE(iotivitycloud.key)" and ROOT_CERT_FILE(rootca.crt) files should be placed in the "target" folder.
import org.iotivity.cloud.base.connector.ConnectorPool;
import org.iotivity.cloud.base.server.CoapServer;
import org.iotivity.cloud.base.server.HttpServer;
+import org.iotivity.cloud.base.server.WebSocketServer;
import org.iotivity.cloud.ciserver.DeviceServerSystem.CoapDevicePool;
import org.iotivity.cloud.ciserver.resources.KeepAliveResource;
import org.iotivity.cloud.ciserver.resources.RouteResource;
System.out.println("-----CI SERVER-------");
- if (!(args.length == 8 || args.length == 9 || args.length == 10
- || args.length == 11)) {
- Log.e("\nCoAP-server <Port> and RD-server <Address> <Port> Account-server <Address> <Port> MQ-broker <Address> <Port> HC-proxy [HTTP-port] and TLS-mode <0|1> are required.\n"
+ if (args.length < 8 || args.length > 12) {
+ Log.e("\nCoAP-server <Port> and RD-server <Address> <Port> Account-server <Address> <Port> "
+ + "MQ-broker <Address> <Port> HC-proxy <HTTP-port> Websocket-server <Port> and TLS-mode <0|1> are required.\n"
+ "and WebSocketLog-Server <Address> <Port> (optional)\n"
- + "ex) 5683 127.0.0.1 5684 127.0.0.1 5685 127.0.0.1 5686 80 0 127.0.0.1 8080\n");
+ + "ex) 5683 127.0.0.1 5684 127.0.0.1 5685 127.0.0.1 5686 80 8000 0 127.0.0.1 8080\n");
return;
}
- boolean hcProxyMode = false;
- if (args.length == 9) {
- hcProxyMode = true;
- }
+ // CoAP-TCP server port
+ int coapPort = Integer.parseInt(args[0]);
+ // HTTP-CoAP proxy server port
+ int hcProxyPort = Integer.parseInt(args[7]);
+ // CoAP-Websocket server port
+ int websocketPort = Integer.parseInt(args[8]);
- boolean tlsMode = false;
- if (hcProxyMode) {
- tlsMode = Integer.parseInt(args[8]) == 1;
+ boolean hcProxyMode = hcProxyPort > 0;
+ boolean websocketMode = websocketPort > 0;
- } else {
- tlsMode = Integer.parseInt(args[7]) == 1;
- }
+ boolean tlsMode = Integer.parseInt(args[9]) == 1;
- if (args.length == 10 || args.length == 11) {
- if (hcProxyMode) {
- Log.InitWebLog(args[9], args[10],
- CloudInterfaceServer.class.getSimpleName().toString());
- } else {
- Log.InitWebLog(args[8], args[9],
- CloudInterfaceServer.class.getSimpleName().toString());
- }
+ if (args.length >= 11) {
+ Log.InitWebLog(args[10], args[11], CloudInterfaceServer.class
+ .getSimpleName().toString());
}
- ConnectorPool.addConnection("rd",
- new InetSocketAddress(args[1], Integer.parseInt(args[2])),
- tlsMode);
- ConnectorPool.addConnection("account",
- new InetSocketAddress(args[3], Integer.parseInt(args[4])),
- tlsMode);
- ConnectorPool.addConnection("mq",
- new InetSocketAddress(args[5], Integer.parseInt(args[6])),
- tlsMode);
+ ConnectorPool.addConnection("rd", new InetSocketAddress(args[1],
+ Integer.parseInt(args[2])), tlsMode);
+ ConnectorPool.addConnection("account", new InetSocketAddress(args[3],
+ Integer.parseInt(args[4])), tlsMode);
+ ConnectorPool.addConnection("mq", new InetSocketAddress(args[5],
+ Integer.parseInt(args[6])), tlsMode);
DeviceServerSystem deviceServer = new DeviceServerSystem();
deviceServer.addResource(new RouteResource(devicePool));
- deviceServer.addServer(new CoapServer(
- new InetSocketAddress(Integer.parseInt(args[0]))));
+ deviceServer.addServer(new CoapServer(new InetSocketAddress(coapPort)));
// Add HTTP Server for HTTP-to-CoAP Proxy
if (hcProxyMode) {
- deviceServer.addServer(new HttpServer(
- new InetSocketAddress(Integer.valueOf(args[7]))));
+ deviceServer.addServer(new HttpServer(new InetSocketAddress(
+ hcProxyPort)));
+ }
+
+ if (websocketMode) {
+ deviceServer.addServer(new WebSocketServer(new InetSocketAddress(
+ websocketPort)));
}
deviceServer.startSystem(tlsMode);
import org.iotivity.cloud.base.server.CoapServer;
import org.iotivity.cloud.base.server.HttpServer;
import org.iotivity.cloud.base.server.Server;
+import org.iotivity.cloud.base.server.WebSocketServer;
import org.iotivity.cloud.util.Bytes;
import org.iotivity.cloud.util.Cbor;
import org.iotivity.cloud.util.Log;
try {
if (!(msg instanceof CoapResponse)) {
- throw new BadRequestException(
- "this msg type is not CoapResponse");
+ // throw new BadRequestException(
+ // "this msg type is not CoapResponse");
+
+ // TODO check websocket handshake response
+ ctx.writeAndFlush(msg);
+ return;
}
// This is CoapResponse
// Once the response is valid, add this to deviceList
server.addHandler(new CoapAuthHandler());
}
+ if (server instanceof WebSocketServer) {
+ server.addHandler(new CoapAuthHandler());
+ }
+
if (server instanceof HttpServer) {
server.addHandler(new HttpAuthHandler());
}
*/
package org.iotivity.cloud.base;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.util.AttributeKey;
+
import java.util.ArrayList;
import java.util.List;
import org.iotivity.cloud.base.server.CoapServer;
import org.iotivity.cloud.base.server.HttpServer;
import org.iotivity.cloud.base.server.Server;
+import org.iotivity.cloud.base.server.WebSocketServer;
import org.iotivity.cloud.util.Log;
import io.netty.channel.ChannelHandler.Sharable;
}
public void addServer(Server server) {
- if (server instanceof CoapServer) {
+ if (server instanceof CoapServer || server instanceof WebSocketServer) {
server.addHandler(new PersistentPacketReceiver());
} else if (server instanceof HttpServer) {
server.addHandler(new NonPersistentPacketReceiver());
}
}
+ public void decode(ByteBuf in, List<Object> out) throws Exception {
+ decode(null, in, out);
+ }
+
private int parseOptions(CoapMessage coapMessage, ByteBuf byteBuf,
int maxLength) {
}
}
- /// return option length
+ // return option length
return byteBuf.readerIndex() - startPos;
}
}
}
}
+ public void encode(CoapMessage msg, ByteBuf out) throws Exception {
+ encode(null, msg, out);
+ }
+
private void calcShimHeader(CoapMessage coapMessage, ByteBuf byteBuf,
long length) {
if (length < 13) {
log = composeCoapRequest(
ctx.channel().id().asLongText().substring(26),
(CoapRequest) msg);
- } else {
- log = composeCoapResponse(
- ctx.channel().id().asLongText().substring(26),
- (CoapResponse) msg);
+ } else if (msg instanceof CoapResponse) {
+ log = composeCoapResponse(ctx.channel().id().asLongText()
+ .substring(26), (CoapResponse) msg);
}
- Log.v(log);
+ if (log != null) {
+ Log.v(log);
+ }
ctx.writeAndFlush(msg);
}
log = composeCoapRequest(
ctx.channel().id().asLongText().substring(26),
(CoapRequest) msg);
- } else {
- log = composeCoapResponse(
- ctx.channel().id().asLongText().substring(26),
- (CoapResponse) msg);
+ } else if (msg instanceof CoapResponse) {
+ log = composeCoapResponse(ctx.channel().id().asLongText()
+ .substring(26), (CoapResponse) msg);
}
- Log.v(log);
+ if (log != null) {
+ Log.v(log);
+ }
ctx.fireChannelRead(msg);
}
--- /dev/null
+/*
+ * //******************************************************************
+ * //
+ * // Copyright 2017 Samsung Electronics All Rights Reserved.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ * //
+ * // Licensed under the Apache License, Version 2.0 (the "License");
+ * // you may not use this file except in compliance with the License.
+ * // You may obtain a copy of the License at
+ * //
+ * // http://www.apache.org/licenses/LICENSE-2.0
+ * //
+ * // Unless required by applicable law or agreed to in writing, software
+ * // distributed under the License is distributed on an "AS IS" BASIS,
+ * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * // See the License for the specific language governing permissions and
+ * // limitations under the License.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ */
+package org.iotivity.cloud.base.protocols.coap.websocket;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketFrame;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.iotivity.cloud.base.exception.ServerException.BadRequestException;
+import org.iotivity.cloud.base.exception.ServerException.InternalServerErrorException;
+import org.iotivity.cloud.base.protocols.coap.CoapDecoder;
+import org.iotivity.cloud.base.protocols.coap.CoapEncoder;
+import org.iotivity.cloud.base.protocols.coap.CoapMessage;
+import org.iotivity.cloud.base.protocols.enums.ContentFormat;
+import org.iotivity.cloud.util.Cbor;
+import org.iotivity.cloud.util.JSONUtil;
+import org.iotivity.cloud.util.Log;
+
+public class WebSocketFrameHandler extends ChannelDuplexHandler {
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ Log.v(ctx.channel().id().asLongText().substring(26)
+ + " WebSocket Connected, Address: "
+ + ctx.channel().remoteAddress().toString());
+
+ ctx.fireChannelActive();
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ Log.v(ctx.channel().id().asLongText().substring(26)
+ + " WebSocket Disconnected, Address: "
+ + ctx.channel().remoteAddress().toString());
+
+ ctx.fireChannelInactive();
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg)
+ throws Exception {
+ // TODO check ping pong
+
+ if (msg instanceof BinaryWebSocketFrame) {
+
+ List<Object> messages = new ArrayList<>();
+ new CoapDecoder().decode(((BinaryWebSocketFrame) msg).content(),
+ messages);
+
+ for (Object message : messages) {
+ if (message instanceof CoapMessage) {
+ CoapMessage coapMessage = (CoapMessage) message;
+
+ // convert content format to cbor if content format is json.
+ if (coapMessage.getPayloadSize() != 0
+ && coapMessage.getContentFormat().equals(
+ ContentFormat.APPLICATION_JSON)) {
+ byte[] payload = coapMessage.getPayload();
+ coapMessage.setPayload(convertJsonToCbor(payload));
+ coapMessage
+ .setContentFormat(ContentFormat.APPLICATION_CBOR);
+ }
+ ctx.fireChannelRead(coapMessage);
+ }
+ }
+ } else {
+ throw new BadRequestException("invalid request message type");
+ }
+ }
+
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg,
+ ChannelPromise promise) throws Exception {
+ Object newMsg = msg;
+
+ if (msg instanceof DefaultFullHttpResponse) {
+
+ ChannelFuture ch = ctx.writeAndFlush(newMsg);
+ ch.addListener(new ChannelFutureListener() {
+
+ @Override
+ public void operationComplete(ChannelFuture future)
+ throws Exception {
+ Log.v(future.channel().id().asLongText().substring(26)
+ + " WebSocket Handshake done, Address: "
+ + future.channel().remoteAddress().toString());
+
+ // remove http encoder/decoder after handshake done.
+ future.channel().pipeline().remove(HttpServerCodec.class);
+ future.channel().pipeline()
+ .remove(HttpObjectAggregator.class);
+ }
+ });
+
+ return;
+ }
+ if (msg instanceof CoapMessage) {
+
+ CoapMessage coapMessage = (CoapMessage) msg;
+
+ // covert content format to json.
+ if (coapMessage.getPayloadSize() != 0) {
+ byte[] payload = coapMessage.getPayload();
+ coapMessage.setPayload(convertCborToJson(payload));
+ coapMessage.setContentFormat(ContentFormat.APPLICATION_JSON);
+ }
+
+ ByteBuf encodedBytes = Unpooled.buffer();
+ new CoapEncoder().encode((CoapMessage) msg, encodedBytes);
+ WebSocketFrame frame = new BinaryWebSocketFrame(encodedBytes);
+ newMsg = frame;
+ } else {
+ throw new InternalServerErrorException(
+ "invalid response message type");
+ }
+
+ ctx.writeAndFlush(newMsg);
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
+ throws Exception {
+
+ cause.printStackTrace();
+ }
+
+ private byte[] convertJsonToCbor(byte[] jsonData) {
+
+ JSONUtil<HashMap<String, Object>> json = new JSONUtil<>();
+ HashMap<String, Object> parsedData = json.parseJSON(jsonData,
+ HashMap.class);
+
+ Cbor<HashMap<String, Object>> cbor = new Cbor<>();
+ return cbor.encodingPayloadToCbor(parsedData);
+ }
+
+ private byte[] convertCborToJson(byte[] cborData) {
+
+ Cbor<Object> cbor = new Cbor<>();
+ Object parsedData = cbor.parsePayloadFromCbor(cborData, Object.class);
+
+ JSONUtil<String> json = new JSONUtil<>();
+ return json.writeJSON(parsedData).getBytes();
+ }
+}
\ No newline at end of file
}
@Override
- public void initChannel(SocketChannel ch) {
+ public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (mSslContext != null) {
b.childHandler(mServerInitializer);
b.bind(mInetSocketAddress).sync();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
} finally {
}
}
--- /dev/null
+/*
+ * //******************************************************************
+ * //
+ * // Copyright 2017 Samsung Electronics All Rights Reserved.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ * //
+ * // Licensed under the Apache License, Version 2.0 (the "License");
+ * // you may not use this file except in compliance with the License.
+ * // You may obtain a copy of the License at
+ * //
+ * // http://www.apache.org/licenses/LICENSE-2.0
+ * //
+ * // Unless required by applicable law or agreed to in writing, software
+ * // distributed under the License is distributed on an "AS IS" BASIS,
+ * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * // See the License for the specific language governing permissions and
+ * // limitations under the License.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ */
+package org.iotivity.cloud.base.server;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
+
+import java.net.InetSocketAddress;
+
+import org.iotivity.cloud.base.protocols.coap.CoapLogHandler;
+import org.iotivity.cloud.base.protocols.coap.websocket.WebSocketFrameHandler;
+
+public class WebSocketServer extends Server {
+
+ private final int MAX_CONTENT_LENGTH = 65536;
+ private final String PATH = "/.well-known/coap";
+ private final String SUBPROTOCOL = "coap";
+
+ public WebSocketServer(InetSocketAddress inetSocketAddress) {
+ super(inetSocketAddress);
+ }
+
+ @Override
+ protected ChannelHandler[] onQueryDefaultHandler() {
+
+ return new ChannelHandler[] { new HttpServerCodec(),
+ new HttpObjectAggregator(MAX_CONTENT_LENGTH),
+ new WebSocketServerCompressionHandler(),
+ new WebSocketServerProtocolHandler(PATH, SUBPROTOCOL, true),
+ new WebSocketFrameHandler(), new CoapLogHandler() };
+ }
+}
\ No newline at end of file
package org.iotivity.cloud.util;
import java.io.IOException;
-import java.util.HashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
return parsedData;
}
- public T writeJSON(HashMap<Object, Object> data) {
+ public T writeJSON(Object data) {
if (data == null)
return null;