]> git.basschouten.com Git - openhab-addons.git/blob
6f99fe3959d29806a97b29b10d9e7b7552a90394
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.velux.internal.bridge.slip.io;
14
15 import java.io.Closeable;
16 import java.io.DataOutputStream;
17 import java.io.IOException;
18 import java.net.ConnectException;
19 import java.net.InetSocketAddress;
20 import java.net.UnknownHostException;
21 import java.security.KeyManagementException;
22 import java.security.NoSuchAlgorithmException;
23 import java.security.cert.CertificateException;
24 import java.security.cert.X509Certificate;
25
26 import javax.net.ssl.SSLContext;
27 import javax.net.ssl.SSLSocket;
28 import javax.net.ssl.TrustManager;
29 import javax.net.ssl.X509TrustManager;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.openhab.binding.velux.internal.VeluxBindingConstants;
34 import org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration;
35 import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * Transport layer supported by the Velux bridge.
41  * <P>
42  * SLIP-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
43  * <P>
44  * It provides methods for pre- and post-communication
45  * as well as a common method for the real communication.
46  * <UL>
47  * <LI>{@link SSLconnection#SSLconnection} for establishing the connection,</LI>
48  * <LI>{@link SSLconnection#send} for sending a message to the bridge,</LI>
49  * <LI>{@link SSLconnection#available} for observation whether there are bytes available,</LI>
50  * <LI>{@link SSLconnection#receive} for receiving a message from the bridge,</LI>
51  * <LI>{@link SSLconnection#close} for tearing down the connection.</LI>
52  * <LI>{@link SSLconnection#setTimeout} for adapting communication parameters.</LI>
53  * </UL>
54  *
55  * @author Guenther Schreiner - Initial contribution.
56  */
57 @NonNullByDefault
58 class SSLconnection implements Closeable {
59     private final Logger logger = LoggerFactory.getLogger(SSLconnection.class);
60
61     // Public definition
62     public static final SSLconnection UNKNOWN = new SSLconnection();
63
64     /*
65      * ***************************
66      * ***** Private Objects *****
67      */
68
69     private @Nullable SSLSocket socket;
70     private @Nullable DataOutputStream dOut;
71     private @Nullable DataInputStreamWithTimeout dIn;
72
73     private int readTimeoutMSecs = 2000;
74     private int connTimeoutMSecs = 6000;
75
76     /**
77      * Fake trust manager to suppress any certificate errors,
78      * used within {@link #SSLconnection} for seamless operation
79      * even on self-signed certificates like provided by <B>Velux</B>.
80      */
81     private final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
82         @Override
83         public X509Certificate @Nullable [] getAcceptedIssuers() {
84             return null;
85         }
86
87         @Override
88         public void checkClientTrusted(X509Certificate @Nullable [] arg0, @Nullable String arg1)
89                 throws CertificateException {
90         }
91
92         @Override
93         public void checkServerTrusted(X509Certificate @Nullable [] arg0, @Nullable String arg1)
94                 throws CertificateException {
95         }
96     } };
97
98     /*
99      * ************************
100      * ***** Constructors *****
101      */
102
103     /**
104      * Constructor for initialization of an unfinished connectivity.
105      */
106     SSLconnection() {
107         logger.debug("SSLconnection() called.");
108     }
109
110     /**
111      * Constructor to setup and establish a connection.
112      *
113      * @param bridgeInstance the actual Bridge Thing instance
114      * @throws java.net.ConnectException in case of unrecoverable communication failures.
115      * @throws java.io.IOException in case of continuous communication I/O failures.
116      * @throws java.net.UnknownHostException in case of continuous communication I/O failures.
117      */
118     SSLconnection(VeluxBridgeHandler bridgeInstance) throws ConnectException, IOException, UnknownHostException {
119         logger.debug("SSLconnection() called");
120         logger.info("Starting {} bridge connection.", VeluxBindingConstants.BINDING_ID);
121         SSLContext ctx = null;
122         try {
123             ctx = SSLContext.getInstance("SSL");
124             ctx.init(null, trustAllCerts, null);
125         } catch (NoSuchAlgorithmException | KeyManagementException e) {
126             throw new IOException(String.format("create of an empty trust store failed: %s.", e.getMessage()));
127         }
128         logger.trace("SSLconnection(): creating socket...");
129         SSLSocket socket = this.socket = (SSLSocket) ctx.getSocketFactory().createSocket();
130         if (socket != null) {
131             VeluxBridgeConfiguration cfg = bridgeInstance.veluxBridgeConfiguration();
132             readTimeoutMSecs = cfg.timeoutMsecs;
133             connTimeoutMSecs = Math.max(connTimeoutMSecs, readTimeoutMSecs);
134             // use longer timeout when establishing the connection
135             socket.setSoTimeout(connTimeoutMSecs);
136             socket.setKeepAlive(true);
137             socket.connect(new InetSocketAddress(cfg.ipAddress, cfg.tcpPort), connTimeoutMSecs);
138             logger.trace("SSLconnection(): starting SSL handshake...");
139             socket.startHandshake();
140             // use shorter timeout for normal communications
141             socket.setSoTimeout(readTimeoutMSecs);
142             dOut = new DataOutputStream(socket.getOutputStream());
143             dIn = new DataInputStreamWithTimeout(socket.getInputStream(), bridgeInstance);
144             if (logger.isTraceEnabled()) {
145                 logger.trace(
146                         "SSLconnection(): connected... (ip={}, port={}, sslTimeout={}, soTimeout={}, soKeepAlive={})",
147                         cfg.ipAddress, cfg.tcpPort, connTimeoutMSecs, socket.getSoTimeout(),
148                         socket.getKeepAlive() ? "true" : "false");
149             }
150         }
151         logger.trace("SSLconnection() finished.");
152     }
153
154     /*
155      * **************************
156      * ***** Public Methods *****
157      */
158
159     /**
160      * Method to query the readiness of the connection.
161      *
162      * @return <b>ready</b> as boolean for an established connection.
163      */
164     synchronized boolean isReady() {
165         return socket != null && dIn != null && dOut != null;
166     }
167
168     /**
169      * Method to pass a message towards the bridge. This method gets called when we are initiating a new SLIP
170      * transaction.
171      *
172      * @param <b>packet</b> as Array of bytes to be transmitted towards the bridge via the established connection.
173      * @throws java.io.IOException in case of a communication I/O failure
174      */
175     synchronized void send(byte[] packet) throws IOException {
176         logger.trace("send() called, writing {} bytes.", packet.length);
177         DataOutputStream dOutX = dOut;
178         if (dOutX == null) {
179             throw new IOException("DataOutputStream not initialised");
180         }
181         try {
182             // copy packet data to the write buffer
183             dOutX.write(packet, 0, packet.length);
184             // force the write buffer data to be written to the socket
185             dOutX.flush();
186             if (logger.isTraceEnabled()) {
187                 StringBuilder sb = new StringBuilder();
188                 for (byte b : packet) {
189                     sb.append(String.format("%02X ", b));
190                 }
191                 logger.trace("send() finished after having send {} bytes: {}", packet.length, sb.toString());
192             }
193         } catch (IOException e) {
194             close();
195             throw e;
196         }
197     }
198
199     /**
200      * Method to verify that there is message from the bridge.
201      *
202      * @return <b>true</b> if there are any messages ready to be queried using {@link SSLconnection#receive}.
203      */
204     synchronized boolean available() {
205         logger.trace("available() called.");
206         DataInputStreamWithTimeout dInX = dIn;
207         if (dInX != null) {
208             int availableMessages = dInX.available();
209             logger.trace("available(): found {} messages ready to be read (> 0 means true).", availableMessages);
210             return availableMessages > 0;
211         }
212         return false;
213     }
214
215     /**
216      * Method to get a message from the bridge.
217      *
218      * @return <b>packet</b> as Array of bytes as received from the bridge via the established connection.
219      * @throws java.io.IOException in case of a communication I/O failure.
220      */
221     synchronized byte[] receive() throws IOException {
222         logger.trace("receive() called.");
223         DataInputStreamWithTimeout dInX = dIn;
224         if (dInX == null) {
225             throw new IOException("DataInputStreamWithTimeout not initialised");
226         }
227         try {
228             byte[] packet = dInX.readSlipMessage(readTimeoutMSecs);
229             if (logger.isTraceEnabled()) {
230                 StringBuilder sb = new StringBuilder();
231                 for (byte b : packet) {
232                     sb.append(String.format("%02X ", b));
233                 }
234                 logger.trace("receive() finished after having read {} bytes: {}", packet.length, sb.toString());
235             }
236             return packet;
237         } catch (IOException e) {
238             close();
239             throw e;
240         }
241     }
242
243     /**
244      * Destructor to tear down a connection.
245      *
246      * @throws java.io.IOException in case of a communication I/O failure.
247      *             But actually eats all exceptions to ensure sure that all shutdown code is executed
248      */
249     @Override
250     public synchronized void close() throws IOException {
251         logger.debug("close() called.");
252         DataInputStreamWithTimeout dInX = dIn;
253         if (dInX != null) {
254             try {
255                 dInX.close();
256             } catch (IOException e) {
257                 // eat the exception so the following will always be executed
258             }
259         }
260         DataOutputStream dOutX = dOut;
261         if (dOutX != null) {
262             try {
263                 dOutX.close();
264             } catch (IOException e) {
265                 // eat the exception so the following will always be executed
266             }
267         }
268         SSLSocket socketX = socket;
269         if (socketX != null) {
270             logger.debug("Shutting down Velux bridge connection.");
271             try {
272                 socketX.close();
273             } catch (IOException e) {
274                 // eat the exception so the following will always be executed
275             }
276         }
277         dIn = null;
278         dOut = null;
279         socket = null;
280         logger.trace("close() finished.");
281     }
282 }