2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.velux.internal.bridge.slip.io;
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;
26 import javax.net.ssl.SSLContext;
27 import javax.net.ssl.SSLSocket;
28 import javax.net.ssl.TrustManager;
29 import javax.net.ssl.X509TrustManager;
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;
40 * Transport layer supported by the Velux bridge.
42 * SLIP-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
44 * It provides methods for pre- and post-communication
45 * as well as a common method for the real communication.
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>
55 * @author Guenther Schreiner - Initial contribution.
58 class SSLconnection implements Closeable {
59 private final Logger logger = LoggerFactory.getLogger(SSLconnection.class);
62 public static final SSLconnection UNKNOWN = new SSLconnection();
65 * ***************************
66 * ***** Private Objects *****
69 private @Nullable SSLSocket socket;
70 private @Nullable DataOutputStream dOut;
71 private @Nullable DataInputStreamWithTimeout dIn;
73 private int readTimeoutMSecs = 2000;
74 private int connTimeoutMSecs = 6000;
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>.
81 private final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
83 public X509Certificate @Nullable [] getAcceptedIssuers() {
88 public void checkClientTrusted(X509Certificate @Nullable [] arg0, @Nullable String arg1)
89 throws CertificateException {
93 public void checkServerTrusted(X509Certificate @Nullable [] arg0, @Nullable String arg1)
94 throws CertificateException {
99 * ************************
100 * ***** Constructors *****
104 * Constructor for initialization of an unfinished connectivity.
107 logger.debug("SSLconnection() called.");
111 * Constructor to setup and establish a connection.
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.
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;
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()));
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()) {
146 "SSLconnection(): connected... (ip={}, port={}, sslTimeout={}, soTimeout={}, soKeepAlive={})",
147 cfg.ipAddress, cfg.tcpPort, connTimeoutMSecs, socket.getSoTimeout(),
148 socket.getKeepAlive() ? "true" : "false");
151 logger.trace("SSLconnection() finished.");
155 * **************************
156 * ***** Public Methods *****
160 * Method to query the readiness of the connection.
162 * @return <b>ready</b> as boolean for an established connection.
164 synchronized boolean isReady() {
165 return socket != null && dIn != null && dOut != null;
169 * Method to pass a message towards the bridge. This method gets called when we are initiating a new SLIP
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
175 synchronized void send(byte[] packet) throws IOException {
176 logger.trace("send() called, writing {} bytes.", packet.length);
177 DataOutputStream dOutX = dOut;
179 throw new IOException("DataOutputStream not initialised");
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
186 if (logger.isTraceEnabled()) {
187 StringBuilder sb = new StringBuilder();
188 for (byte b : packet) {
189 sb.append(String.format("%02X ", b));
191 logger.trace("send() finished after having send {} bytes: {}", packet.length, sb.toString());
193 } catch (IOException e) {
200 * Method to verify that there is message from the bridge.
202 * @return <b>true</b> if there are any messages ready to be queried using {@link SSLconnection#receive}.
204 synchronized boolean available() {
205 logger.trace("available() called.");
206 DataInputStreamWithTimeout dInX = dIn;
208 int availableMessages = dInX.available();
209 logger.trace("available(): found {} messages ready to be read (> 0 means true).", availableMessages);
210 return availableMessages > 0;
216 * Method to get a message from the bridge.
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.
221 synchronized byte[] receive() throws IOException {
222 logger.trace("receive() called.");
223 DataInputStreamWithTimeout dInX = dIn;
225 throw new IOException("DataInputStreamWithTimeout not initialised");
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));
234 logger.trace("receive() finished after having read {} bytes: {}", packet.length, sb.toString());
237 } catch (IOException e) {
244 * Destructor to tear down a connection.
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
250 public synchronized void close() throws IOException {
251 logger.debug("close() called.");
252 DataInputStreamWithTimeout dInX = dIn;
256 } catch (IOException e) {
257 // eat the exception so the following will always be executed
260 DataOutputStream dOutX = dOut;
264 } catch (IOException e) {
265 // eat the exception so the following will always be executed
268 SSLSocket socketX = socket;
269 if (socketX != null) {
270 logger.debug("Shutting down Velux bridge connection.");
273 } catch (IOException e) {
274 // eat the exception so the following will always be executed
280 logger.trace("close() finished.");