IOT-3143
[iotivity.git] / cloud / stack / src / main / java / org / iotivity / cloud / base / connector / CoapConnector.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.base.connector;
23
24 import io.netty.bootstrap.Bootstrap;
25 import io.netty.channel.*;
26 import io.netty.channel.ChannelHandler.Sharable;
27 import io.netty.channel.nio.NioEventLoopGroup;
28 import io.netty.channel.socket.SocketChannel;
29 import io.netty.channel.socket.nio.NioSocketChannel;
30 import io.netty.handler.ssl.SslContext;
31 import io.netty.handler.ssl.SslContextBuilder;
32 import io.netty.handler.ssl.SslProvider;
33 import io.netty.handler.timeout.IdleState;
34 import io.netty.handler.timeout.IdleStateEvent;
35 import io.netty.handler.timeout.IdleStateHandler;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38 import org.iotivity.cloud.base.OICConstants;
39 import org.iotivity.cloud.base.protocols.coap.*;
40 import org.iotivity.cloud.base.protocols.coap.PingMessage;
41
42 import javax.net.ssl.SSLException;
43 import java.io.File;
44 import java.net.InetSocketAddress;
45 import java.util.*;
46 import java.util.concurrent.ConcurrentHashMap;
47 import java.util.concurrent.ConcurrentMap;
48
49 public class CoapConnector {
50     private final static Logger Log             = LoggerFactory.getLogger(CoapConnector.class);
51     public CoapConnector() {
52
53         mBootstrap.group(mConnectorGroup);
54         mBootstrap.channel(NioSocketChannel.class);
55         mBootstrap.option(ChannelOption.TCP_NODELAY, true);
56         mBootstrap.option(ChannelOption.SO_KEEPALIVE, true);
57         mBootstrap.option(ChannelOption.SO_REUSEADDR, true);
58     }
59
60     @Sharable
61     private class CoapPacketHandler
62             extends SimpleChannelInboundHandler<CoapResponse> {
63
64         @Override
65         protected void channelRead0(ChannelHandlerContext ctx, CoapResponse msg)
66                 throws Exception {
67             mChannelMap.get(ctx.channel()).onResponseReceived(msg);
68         }
69     }
70
71     public static class KeepAliveHandler extends ChannelDuplexHandler {
72         @Override
73         public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
74                 throws Exception {
75             if (evt instanceof IdleStateEvent) {
76                 IdleStateEvent event = (IdleStateEvent) evt;
77                 if (event.state() == IdleState.WRITER_IDLE) {
78                     mChannelMap.get(ctx.channel()).sendRequest(PingMessage.build(),null);
79                 }
80                 if (event.state() == IdleState.READER_IDLE) {
81                     Log.debug("Connection with" +  ctx.channel().remoteAddress().toString() + "is idle. Closing connection.");
82                     ctx.close();
83                 }
84             }
85         }
86     }
87
88     public static class CoapConnectorInitializer
89             extends ChannelInitializer<SocketChannel> {
90
91         private List<ChannelHandler> additionalHandlers = new ArrayList<>();
92
93         private Boolean              mTlsMode           = false;
94         private Boolean              mKeepAlive         = false;
95         InetSocketAddress            mInetSocketAddress = null;
96         String                       mRootCertFiePath   = null;
97
98         public void setTlsMode(Boolean tlsMode) {
99             this.mTlsMode = tlsMode;
100         }
101
102         public void setKeepAlive(Boolean keepAlive) {
103             this.mKeepAlive = keepAlive;
104         }
105
106         public void setInetSocketAddress(InetSocketAddress address) {
107             this.mInetSocketAddress = address;
108         }
109
110         public void setRootCertFilePath(String path) {
111             this.mRootCertFiePath = path;
112         }
113
114         public void addHandler(ChannelHandler handler) {
115             additionalHandlers.add(handler);
116         }
117
118         @Override
119         public void initChannel(SocketChannel ch) {
120             ChannelPipeline p = ch.pipeline();
121
122             SslContext sslContext = null;
123
124             if (mTlsMode.equals(true)) {
125
126                 File rootCert = new File(mRootCertFiePath);
127
128                 try {
129                     sslContext = SslContextBuilder.forClient()
130                             .sslProvider(SslProvider.JDK).trustManager(rootCert)
131                             .build();
132                 } catch (SSLException e) {
133                     e.printStackTrace();
134                 }
135
136                 final SslContext sslCtx = sslContext;
137                 p.addLast(sslCtx.newHandler(ch.alloc(),
138                         mInetSocketAddress.getHostString(),
139                         mInetSocketAddress.getPort()));
140             }
141
142             p.addLast(new CoapDecoder());
143             p.addLast(new CoapEncoder());
144             p.addLast(new CoapLogHandler());
145
146             if (mKeepAlive.equals(true)) {
147                 p.addLast(new IdleStateHandler(100, 45, 0));
148                 p.addLast(new KeepAliveHandler());
149             }
150
151             for (ChannelHandler handler : additionalHandlers) {
152                 p.addLast(handler);
153             }
154         }
155     }
156
157     private static ConcurrentMap<Channel, CoapClient> mChannelMap     = new ConcurrentHashMap<>();
158     Bootstrap                    mBootstrap      = new Bootstrap();
159     EventLoopGroup               mConnectorGroup = new NioEventLoopGroup();
160     Timer                        mTimer          = new Timer();
161
162     public void connect(final String connectionName, final InetSocketAddress inetSocketAddress,
163             boolean tlsMode, boolean keepAlive) {
164
165         CoapConnectorInitializer initializer = new CoapConnectorInitializer();
166
167         if (tlsMode == true) {
168             initializer.setTlsMode(true);
169             initializer.setInetSocketAddress(inetSocketAddress);
170             initializer.setRootCertFilePath(OICConstants.ROOT_CERT_FILE);
171         }
172
173         initializer.setKeepAlive(keepAlive);
174         initializer.addHandler(new CoapPacketHandler());
175         mBootstrap.handler(initializer);
176         doConnect(connectionName, inetSocketAddress, tlsMode);
177     }
178
179     private void doConnect(final String connectionName, final InetSocketAddress inetSocketAddress, final boolean tlsMode) {
180         mBootstrap.connect(inetSocketAddress).addListener(new ChannelFutureListener() {
181                 @Override public void operationComplete(ChannelFuture future) throws Exception {
182                     if(!future.isSuccess()) {
183                         Log.debug("Connection to " + inetSocketAddress.getHostString() + " was not successful. Retrying...");
184                         future.channel().close();
185                         scheduleConnect(connectionName, inetSocketAddress, tlsMode, 5000);
186                     } else {
187                         connectionEstablished(connectionName, future.channel());
188                         addCloseDetectListener(future.channel());
189                     }
190                 }
191
192             private void addCloseDetectListener(Channel channel) {
193                 channel.closeFuture().addListener((ChannelFutureListener) future -> {
194                     ConnectorPool.removeConnection(connectionName);
195                     Log.debug("Connection to " + inetSocketAddress.getHostString() + " was lost. Retrying...");
196                     scheduleConnect(connectionName, inetSocketAddress, tlsMode, 5);
197                 });
198             }
199         });
200     }
201
202     private void scheduleConnect(String connectionName, InetSocketAddress inetSocketAddress, boolean tlsMode, long millis) {
203         mTimer.schedule( new TimerTask() {
204             @Override
205             public void run() {
206                 doConnect(connectionName, inetSocketAddress, tlsMode);
207             }
208         }, millis );
209     }
210
211     public void connectionEstablished(String connectionName, Channel channel) {
212         CoapClient coapClient = new CoapClient(channel);
213         mChannelMap.put(channel, coapClient);
214         ConnectorPool.addConnection(connectionName, coapClient);
215     }
216
217     public void disconenct() throws Exception {
218         mConnectorGroup.shutdownGracefully().await();
219     }
220 }