telnet daemon working
[nikiroo-utils.git] / src / jexer / net / TelnetOutputStream.java
CommitLineData
ea91242c
KL
1/**
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31package jexer.net;
32
33import java.io.OutputStream;
34import java.io.IOException;
35
36/**
37 * TelnetOutputStream works with TelnetSocket to perform the telnet protocol.
38 */
39public final class TelnetOutputStream extends OutputStream {
40
41 /**
42 * The root TelnetSocket that has my telnet protocol state.
43 */
44 private TelnetSocket master;
45
46 /**
47 * The raw socket's OutputStream.
48 */
49 private OutputStream output;
50
51 /**
52 * Package private constructor.
53 *
54 * @param master the master TelnetSocket
55 * @param output the underlying socket's OutputStream
56 */
9b1afdde 57 TelnetOutputStream(final TelnetSocket master, final OutputStream output) {
ea91242c
KL
58 this.master = master;
59 this.output = output;
60 }
61
62 // OutputStream interface -------------------------------------------------
63
64 /**
65 * Closes this output stream and releases any system resources associated
66 * with this stream.
9b1afdde
KL
67 *
68 * @throws IOException if an I/O error occurs
ea91242c
KL
69 */
70 @Override
71 public void close() throws IOException {
005ec497
KL
72 if (output != null) {
73 output.close();
74 output = null;
75 }
ea91242c
KL
76 }
77
78 /**
79 * Flushes this output stream and forces any buffered output bytes to be
80 * written out.
9b1afdde
KL
81 *
82 * @throws IOException if an I/O error occurs
ea91242c
KL
83 */
84 @Override
85 public void flush() throws IOException {
9b1afdde 86 if ((master.binaryMode == false) && (writeCR == true)) {
005ec497
KL
87 // The last byte sent to this.write() was a CR, which was never
88 // actually sent. So send the CR in ascii mode, then flush.
89 // CR <anything> -> CR NULL
90 output.write(master.C_CR);
91 output.write(master.C_NUL);
9b1afdde 92 writeCR = false;
005ec497
KL
93 }
94 output.flush();
ea91242c
KL
95 }
96
97 /**
98 * Writes b.length bytes from the specified byte array to this output
99 * stream.
9b1afdde
KL
100 *
101 * @param b the data.
102 * @throws IOException if an I/O error occurs
ea91242c
KL
103 */
104 @Override
9b1afdde 105 public void write(final byte[] b) throws IOException {
005ec497 106 writeImpl(b, 0, b.length);
ea91242c
KL
107 }
108
109 /**
110 * Writes len bytes from the specified byte array starting at offset off
111 * to this output stream.
9b1afdde
KL
112 *
113 * @param b the data.
114 * @param off the start offset in the data.
115 * @param len the number of bytes to write.
116 * @throws IOException if an I/O error occurs
ea91242c
KL
117 */
118 @Override
9b1afdde
KL
119 public void write(final byte[] b, final int off,
120 final int len) throws IOException {
121
005ec497 122 writeImpl(b, off, len);
ea91242c
KL
123 }
124
125 /**
126 * Writes the specified byte to this output stream.
9b1afdde
KL
127 *
128 * @param b the byte to write.
129 * @throws IOException if an I/O error occurs
ea91242c
KL
130 */
131 @Override
9b1afdde 132 public void write(final int b) throws IOException {
005ec497
KL
133 byte [] bytes = new byte[1];
134 bytes[0] = (byte)b;
135 writeImpl(bytes, 0, 1);
ea91242c
KL
136 }
137
005ec497
KL
138 /**
139 * Writes b.length bytes from the specified byte array to this output
140 * stream. Note package private access.
9b1afdde
KL
141 *
142 * @param b the data.
143 * @throws IOException if an I/O error occurs
005ec497 144 */
9b1afdde 145 void rawWrite(final byte[] b) throws IOException {
005ec497
KL
146 output.write(b, 0, b.length);
147 }
148
9b1afdde
KL
149 // Telnet protocol --------------------------------------------------------
150
151 /**
152 * When true, the last byte the caller passed to write() was a CR.
153 */
154 private boolean writeCR = false;
155
005ec497
KL
156 /**
157 * Writes len bytes from the specified byte array starting at offset off
158 * to this output stream.
9b1afdde
KL
159 *
160 * @param b the data.
161 * @param off the start offset in the data.
162 * @param len the number of bytes to write.
163 * @throws IOException if an I/O error occurs
005ec497
KL
164 */
165 private void writeImpl(final byte[] b, final int off,
166 final int len) throws IOException {
167
168 byte [] writeBuffer = new byte[Math.max(len, 4)];
169 int writeBufferI = 0;
170
171 for (int i = 0; i < len; i++) {
172 if (writeBufferI >= writeBuffer.length - 4) {
173 // Flush what we have generated so far and reset the buffer,
174 // because the next byte could generate up to 4 output bytes
175 // (CR <something> <IAC> <IAC>).
9b1afdde 176 output.write(writeBuffer, 0, writeBufferI);
005ec497
KL
177 writeBufferI = 0;
178 }
179
180 // Pull the next byte
181 byte ch = b[i + off];
182
9b1afdde 183 if (master.binaryMode == true) {
005ec497
KL
184
185 if (ch == master.TELNET_IAC) {
186 // IAC -> IAC IAC
187 writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
188 writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
189 } else {
190 // Anything else -> just send
191 writeBuffer[writeBufferI++] = ch;
192 }
193 continue;
194 }
195
196 // Non-binary mode: more complicated. We use writeCR to handle
197 // the case that the last byte of b was a CR.
198
199 // Bare carriage return -> CR NUL
200 if (ch == master.C_CR) {
9b1afdde 201 if (writeCR == true) {
005ec497
KL
202 // Flush the previous CR to the stream.
203 // CR <anything> -> CR NULL
204 writeBuffer[writeBufferI++] = (byte)master.C_CR;
205 writeBuffer[writeBufferI++] = (byte)master.C_NUL;
206 }
9b1afdde 207 writeCR = true;
005ec497 208 } else if (ch == master.C_LF) {
9b1afdde 209 if (writeCR == true) {
005ec497
KL
210 // CR LF -> CR LF
211 writeBuffer[writeBufferI++] = (byte)master.C_CR;
212 writeBuffer[writeBufferI++] = (byte)master.C_LF;
9b1afdde 213 writeCR = false;
005ec497
KL
214 } else {
215 // Bare LF -> LF
216 writeBuffer[writeBufferI++] = ch;
217 }
218 } else if (ch == master.TELNET_IAC) {
9b1afdde 219 if (writeCR == true) {
005ec497
KL
220 // CR <anything> -> CR NULL
221 writeBuffer[writeBufferI++] = (byte)master.C_CR;
222 writeBuffer[writeBufferI++] = (byte)master.C_NUL;
9b1afdde 223 writeCR = false;
005ec497
KL
224 }
225 // IAC -> IAC IAC
226 writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
227 writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
228 } else {
9b1afdde 229 if (writeCR == true) {
005ec497
KL
230 // CR <anything> -> CR NULL
231 writeBuffer[writeBufferI++] = (byte)master.C_CR;
232 writeBuffer[writeBufferI++] = (byte)master.C_NUL;
9b1afdde 233 writeCR = false;
005ec497
KL
234 } else {
235 // Normal character */
236 writeBuffer[writeBufferI++] = ch;
237 }
238 }
239
240 } // while (i < userbuf.length)
241
242 if (writeBufferI > 0) {
243 // Flush what we have generated so far and reset the buffer.
9b1afdde 244 output.write(writeBuffer, 0, writeBufferI);
005ec497
KL
245 }
246 }
247
ea91242c 248}