]> git.basschouten.com Git - openhab-addons.git/blob
96a1e9a0e8496828d3b3b628f9a6820aa9264c0f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.lutron.internal.net;
14
15 import java.io.BufferedReader;
16 import java.io.Closeable;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.io.PrintStream;
20 import java.nio.CharBuffer;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.regex.MatchResult;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import org.apache.commons.net.telnet.InvalidTelnetOptionException;
30 import org.apache.commons.net.telnet.SuppressGAOptionHandler;
31 import org.apache.commons.net.telnet.TelnetClient;
32 import org.apache.commons.net.telnet.TelnetInputListener;
33 import org.apache.commons.net.telnet.TelnetOptionHandler;
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.eclipse.jdt.annotation.Nullable;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * A single telnet session.
41  *
42  * @author Allan Tong - Initial contribution
43  * @author Bob Adair - Fix to readInput and added debug logging
44  */
45 @NonNullByDefault
46 public class TelnetSession implements Closeable {
47
48     private static final int BUFSIZE = 8192;
49
50     private final Logger logger = LoggerFactory.getLogger(TelnetSession.class);
51
52     private TelnetClient telnetClient;
53     private @Nullable BufferedReader reader;
54     private @Nullable PrintStream outstream;
55
56     private CharBuffer charBuffer;
57     private List<TelnetSessionListener> listeners = new ArrayList<>();
58
59     private @Nullable TelnetOptionHandler suppressGAOptionHandler;
60
61     public TelnetSession() {
62         logger.trace("Creating new TelnetSession");
63         this.telnetClient = new TelnetClient();
64         this.charBuffer = CharBuffer.allocate(BUFSIZE);
65
66         this.telnetClient.setReaderThread(true);
67         this.telnetClient.registerInputListener(new TelnetInputListener() {
68             @Override
69             public void telnetInputAvailable() {
70                 try {
71                     readInput();
72                 } catch (IOException e) {
73                     notifyInputError(e);
74                 }
75             }
76         });
77     }
78
79     public void addListener(TelnetSessionListener listener) {
80         this.listeners.add(listener);
81     }
82
83     public void clearListeners() {
84         this.listeners.clear();
85     }
86
87     private void notifyInputAvailable() {
88         for (TelnetSessionListener listener : this.listeners) {
89             listener.inputAvailable();
90         }
91     }
92
93     private void notifyInputError(IOException exception) {
94         logger.debug("TelnetSession notifyInputError: {}", exception.getMessage());
95         for (TelnetSessionListener listener : this.listeners) {
96             listener.error(exception);
97         }
98     }
99
100     public void open(String host) throws IOException {
101         open(host, 23);
102     }
103
104     public void open(String host, int port) throws IOException {
105         // Synchronized block prevents listener thread from attempting to read input before we're ready.
106         synchronized (this.charBuffer) {
107             logger.trace("TelnetSession open called");
108             try {
109                 telnetClient.connect(host, port);
110                 telnetClient.setKeepAlive(true);
111             } catch (IOException e) {
112                 logger.debug("TelnetSession open: error connecting: {}", e.getMessage());
113                 throw (e);
114             }
115
116             if (this.suppressGAOptionHandler == null) {
117                 // Only do this once.
118                 this.suppressGAOptionHandler = new SuppressGAOptionHandler(true, true, true, true);
119
120                 try {
121                     this.telnetClient.addOptionHandler(this.suppressGAOptionHandler);
122                 } catch (InvalidTelnetOptionException e) {
123                     // Should never happen. Wrap it inside IOException so as not to declare another throwable.
124                     logger.debug("TelnetSession open: error adding telnet option handler: {}", e.getMessage());
125                     throw new IOException(e);
126                 }
127             }
128
129             this.reader = new BufferedReader(new InputStreamReader(this.telnetClient.getInputStream()));
130             this.outstream = new PrintStream(this.telnetClient.getOutputStream());
131         }
132     }
133
134     @Override
135     public void close() throws IOException {
136         synchronized (charBuffer) {
137             logger.trace("TelnetSession close called");
138             try {
139                 if (telnetClient.isConnected()) {
140                     telnetClient.disconnect();
141                 }
142             } catch (IOException e) {
143                 logger.debug("TelnetSession close: error disconnecting: {}", e.getMessage());
144                 throw (e);
145             } finally {
146                 reader = null;
147                 outstream = null;
148             }
149         }
150     }
151
152     public boolean isConnected() {
153         synchronized (charBuffer) {
154             return reader != null;
155         }
156     }
157
158     private void readInput() throws IOException {
159         synchronized (charBuffer) {
160             if (reader != null) {
161                 try {
162                     reader.read(charBuffer);
163                 } catch (IOException e) {
164                     logger.debug("TelnetSession readInput: error reading: {}", e.getMessage());
165                     throw (e);
166                 }
167                 charBuffer.notifyAll();
168
169                 if (charBuffer.position() > 0) {
170                     notifyInputAvailable();
171                 }
172             } else {
173                 logger.debug("TelnetSession readInput: reader is null - session is closed");
174                 throw new IOException("Session is closed");
175             }
176         }
177     }
178
179     public MatchResult waitFor(String prompt) throws InterruptedException {
180         return waitFor(prompt, 0);
181     }
182
183     public MatchResult waitFor(String prompt, long timeout) throws InterruptedException {
184         Pattern regex = Pattern.compile(prompt);
185         long startTime = timeout > 0 ? System.currentTimeMillis() : 0;
186
187         logger.trace("TelnetSession waitFor called with {} {}", prompt, timeout);
188         synchronized (this.charBuffer) {
189             this.charBuffer.flip();
190
191             String bufdata = this.charBuffer.toString();
192             int n = bufdata.lastIndexOf('\n');
193             String lastLine;
194
195             if (n != -1) {
196                 lastLine = bufdata.substring(n + 1);
197             } else {
198                 lastLine = bufdata;
199             }
200
201             Matcher matcher = regex.matcher(lastLine);
202
203             while (!matcher.find()) {
204                 long elapsed = timeout > 0 ? (System.currentTimeMillis() - startTime) : 0;
205
206                 if (timeout > 0 && elapsed >= timeout) {
207                     break;
208                 }
209
210                 this.charBuffer.clear();
211                 this.charBuffer.put(lastLine);
212
213                 this.charBuffer.wait(timeout - elapsed);
214                 this.charBuffer.flip();
215
216                 bufdata = this.charBuffer.toString();
217                 n = bufdata.lastIndexOf('\n');
218
219                 if (n != -1) {
220                     lastLine = bufdata.substring(n + 1);
221                 } else {
222                     lastLine = bufdata;
223                 }
224
225                 matcher = regex.matcher(lastLine);
226             }
227
228             this.charBuffer.clear();
229
230             return matcher.toMatchResult();
231         }
232     }
233
234     public Iterable<String> readLines() {
235         synchronized (this.charBuffer) {
236             this.charBuffer.flip();
237
238             String bufdata = this.charBuffer.toString();
239             int n = bufdata.lastIndexOf('\n');
240             String leftover;
241             String[] lines = null;
242
243             if (n != -1) {
244                 leftover = bufdata.substring(n + 1);
245                 bufdata = bufdata.substring(0, n).trim();
246
247                 lines = bufdata.split("\r\n");
248             } else {
249                 leftover = bufdata;
250             }
251
252             this.charBuffer.clear();
253             this.charBuffer.put(leftover);
254
255             return lines == null ? Collections.<String> emptyList() : Arrays.asList(lines);
256         }
257     }
258
259     public void writeLine(String line) throws IOException {
260         synchronized (charBuffer) {
261             logger.trace("TelnetSession writeLine called with {}", line);
262             PrintStream out = outstream;
263             if (out == null) {
264                 logger.debug("TelnetSession writeLine: outstream is null - session is closed");
265                 throw new IOException("Session is closed");
266             }
267             out.print(line + "\r\n");
268
269             if (out.checkError()) {
270                 logger.debug("TelnetSession writeLine: error writing to outstream");
271                 throw new IOException("Could not write to stream");
272             }
273         }
274     }
275 }