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.DataOutputStream;
16 import java.io.IOException;
17 import java.net.ConnectException;
18 import java.net.UnknownHostException;
19 import java.security.KeyManagementException;
20 import java.security.NoSuchAlgorithmException;
21 import java.security.cert.CertificateException;
22 import java.security.cert.X509Certificate;
24 import javax.net.ssl.SSLContext;
25 import javax.net.ssl.SSLSocket;
26 import javax.net.ssl.TrustManager;
27 import javax.net.ssl.X509TrustManager;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.velux.internal.VeluxBindingConstants;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * Transport layer supported by the Velux bridge.
38 * SLIP-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
40 * It provides methods for pre- and post-communication
41 * as well as a common method for the real communication.
43 * <LI>{@link SSLconnection#SSLconnection} for establishing the connection,</LI>
44 * <LI>{@link SSLconnection#send} for sending a message to the bridge,</LI>
45 * <LI>{@link SSLconnection#available} for observation whether there are bytes available,</LI>
46 * <LI>{@link SSLconnection#receive} for receiving a message from the bridge,</LI>
47 * <LI>{@link SSLconnection#close} for tearing down the connection.</LI>
48 * <LI>{@link SSLconnection#setTimeout} for adapting communication parameters.</LI>
51 * @author Guenther Schreiner - Initial contribution.
55 private final Logger logger = LoggerFactory.getLogger(SSLconnection.class);
58 public static final SSLconnection UNKNOWN = new SSLconnection();
61 * ***************************
62 * ***** Private Objects *****
65 private static final int CONNECTION_BUFFER_SIZE = 4096;
67 private boolean ready = false;
68 private @Nullable SSLSocket socket;
69 private @Nullable DataOutputStream dOut;
70 private @Nullable DataInputStreamWithTimeout dIn;
71 private int ioTimeoutMSecs = 60000;
74 * Fake trust manager to suppress any certificate errors,
75 * used within {@link #SSLconnection} for seamless operation
76 * even on self-signed certificates like provided by <B>Velux</B>.
78 private final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
80 public X509Certificate @Nullable [] getAcceptedIssuers() {
85 public void checkClientTrusted(X509Certificate @Nullable [] arg0, @Nullable String arg1)
86 throws CertificateException {
90 public void checkServerTrusted(X509Certificate @Nullable [] arg0, @Nullable String arg1)
91 throws CertificateException {
96 * ************************
97 * ***** Constructors *****
101 * Constructor for initialization of an unfinished connectivity.
104 logger.debug("SSLconnection() called.");
106 logger.trace("SSLconnection() finished.");
110 * Constructor to setup and establish a connection.
112 * @param host as String describing the Service Access Point location i.e. hostname.
113 * @param port as String describing the Service Access Point location i.e. TCP port.
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(String host, int port) throws ConnectException, IOException, UnknownHostException {
119 logger.debug("SSLconnection({},{}) called.", host, port);
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 // Just for avoidance of Potential null pointer access
130 SSLSocket socketX = (SSLSocket) ctx.getSocketFactory().createSocket(host, port);
131 logger.trace("SSLconnection(): starting SSL handshake...");
132 if (socketX != null) {
133 socketX.startHandshake();
134 dOut = new DataOutputStream(socketX.getOutputStream());
135 dIn = new DataInputStreamWithTimeout(socketX.getInputStream());
139 logger.trace("SSLconnection() finished.");
143 * **************************
144 * ***** Public Methods *****
148 * Method to query the readiness of the connection.
150 * @return <b>ready</b> as boolean for an established connection.
152 synchronized boolean isReady() {
157 * Method to pass a message towards the bridge.
158 * This method gets called when we are initiating a new SLIP transaction.
160 * Note that DataOutputStream and DataInputStream are buffered I/O's. The SLIP protocol requires that prior requests
161 * should have been fully sent over the socket, and their responses should have been fully read from the buffer
162 * before the next request is initiated. i.e. Both read and write buffers should already be empty. Nevertheless,
163 * just in case, we do the following..
165 * 1) Flush from the read buffer any orphan response data that may have been left over from prior transactions, and
166 * 2) Flush the write buffer directly to the socket to ensure that any exceptions are raised immediately, and the
167 * KLF starts work immediately
169 * @param packet as Array of bytes to be transmitted towards the bridge via the established connection.
170 * @throws java.io.IOException in case of a communication I/O failure, and sets 'ready' = false
172 @SuppressWarnings("null")
173 synchronized void send(byte[] packet) throws IOException {
174 logger.trace("send() called, writing {} bytes.", packet.length);
176 if (!ready || (dOut == null) || (dIn == null)) {
177 throw new IOException();
179 // flush the read buffer if (exceptionally) there is orphan response data in it
181 // copy packet data to the write buffer
182 dOut.write(packet, 0, packet.length);
183 // force the write buffer data to be written to the socket
185 if (logger.isTraceEnabled()) {
186 StringBuilder sb = new StringBuilder();
187 for (byte b : packet) {
188 sb.append(String.format("%02X ", b));
190 logger.trace("send() finished after having send {} bytes: {}", packet.length, sb.toString());
192 } catch (IOException e) {
199 * Method to verify that there is message from the bridge.
201 * @return <b>true</b> if there are any bytes ready to be queried using {@link SSLconnection#receive}.
202 * @throws java.io.IOException in case of a communication I/O failure.
204 synchronized boolean available() throws IOException {
205 logger.trace("available() called.");
206 if (!ready || (dIn == null)) {
207 throw new IOException();
209 @SuppressWarnings("null")
210 int availableBytes = dIn.available();
211 logger.trace("available(): found {} bytes ready to be read (> 0 means true).", availableBytes);
212 return availableBytes > 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, and sets 'ready' = false
221 synchronized byte[] receive() throws IOException {
222 logger.trace("receive() called.");
224 if (!ready || (dIn == null)) {
225 throw new IOException();
227 byte[] message = new byte[CONNECTION_BUFFER_SIZE];
228 @SuppressWarnings("null")
229 int messageLength = dIn.read(message, 0, message.length, ioTimeoutMSecs);
230 byte[] packet = new byte[messageLength];
231 System.arraycopy(message, 0, packet, 0, messageLength);
232 if (logger.isTraceEnabled()) {
233 StringBuilder sb = new StringBuilder();
234 for (byte b : packet) {
235 sb.append(String.format("%02X ", b));
237 logger.trace("receive() finished after having read {} bytes: {}", messageLength, sb.toString());
240 } catch (IOException e) {
247 * Destructor to tear down a connection.
249 * @throws java.io.IOException in case of a communication I/O failure.
251 synchronized void close() throws IOException {
252 logger.debug("close() called.");
254 logger.info("Shutting down Velux bridge connection.");
255 // Just for avoidance of Potential null pointer access
256 DataInputStreamWithTimeout dInX = dIn;
261 // Just for avoidance of Potential null pointer access
262 DataOutputStream dOutX = dOut;
267 // Just for avoidance of Potential null pointer access
268 SSLSocket socketX = socket;
269 if (socketX != null) {
273 logger.trace("close() finished.");
277 * Parameter modification.
279 * @param timeoutMSecs the maximum duration in milliseconds for read operations.
281 void setTimeout(int timeoutMSecs) {
282 logger.debug("setTimeout() set timeout to {} milliseconds.", timeoutMSecs);
283 ioTimeoutMSecs = timeoutMSecs;
287 * Method to flush the input buffer.
289 * @throws java.io.IOException in case of a communication I/O failure.
291 private void flushReadBufffer() throws IOException {
292 logger.trace("flushReadBuffer() called.");
293 DataInputStreamWithTimeout dInX = dIn;
294 if (!ready || (dInX == null)) {
295 throw new IOException();
297 int byteCount = dInX.available();
299 byte[] byteArray = new byte[byteCount];
300 dInX.readFully(byteArray);
301 if (logger.isTraceEnabled()) {
302 StringBuilder stringBuilder = new StringBuilder();
303 for (byte currByte : byteArray) {
304 stringBuilder.append(String.format("%02X ", currByte));
306 logger.trace("flushReadBuffer(): discarded {} unexpected bytes in the input buffer: {}", byteCount,
307 stringBuilder.toString());
309 logger.warn("flushReadBuffer(): discarded {} unexpected bytes in the input buffer", byteCount);