serial: switch from SSL to CryptUtils
[nikiroo-utils.git] / src / be / nikiroo / utils / serial / server / Server.java
CommitLineData
79ce1a49 1package be.nikiroo.utils.serial.server;
ce0974c4
NR
2
3import java.io.IOException;
4import java.net.ServerSocket;
5import java.net.Socket;
f4053377 6import java.net.UnknownHostException;
ce0974c4 7
08a58812 8import be.nikiroo.utils.TraceHandler;
ce0974c4 9
08a58812
NR
10/**
11 * This class implements a simple server that can listen for connections and
12 * send/receive objects.
13 * <p>
14 * Note: this {@link Server} has to be discarded after use (cannot be started
15 * twice).
16 *
17 * @author niki
18 */
79ce1a49 19abstract class Server implements Runnable {
8468bb79 20 protected final String key;
ce0974c4 21
08a58812 22 private final String name;
08a58812
NR
23 private final Object lock = new Object();
24 private final Object counterLock = new Object();
25
ce0974c4 26 private ServerSocket ss;
08a58812
NR
27 private int port;
28
ce0974c4
NR
29 private boolean started;
30 private boolean exiting = false;
31 private int counter;
08a58812 32
4bb7e88e
NR
33 private long bytesReceived;
34 private long bytesSent;
35
08a58812 36 private TraceHandler tracer = new TraceHandler();
ce0974c4 37
79ce1a49
NR
38 /**
39 * Create a new {@link ConnectActionServer} to handle a request.
40 *
41 * @param s
42 * the socket to service
43 *
44 * @return the action
45 */
46 abstract ConnectActionServer createConnectActionServer(Socket s);
cd0c27d2 47
08a58812
NR
48 /**
49 * Create a new server that will start listening on the network when
50 * {@link Server#start()} is called.
51 *
52 * @param port
53 * the port to listen on, or 0 to assign any unallocated port
54 * found (which can later on be queried via
55 * {@link Server#getPort()}
8468bb79
NR
56 * @param key
57 * an optional key to encrypt all the communications (if NULL,
58 * everything will be sent in clear text)
08a58812
NR
59 *
60 * @throws IOException
61 * in case of I/O error
f4053377
NR
62 * @throws UnknownHostException
63 * if the IP address of the host could not be determined
64 * @throws IllegalArgumentException
65 * if the port parameter is outside the specified range of valid
66 * port values, which is between 0 and 65535, inclusive
08a58812 67 */
8468bb79
NR
68 public Server(int port, String key) throws IOException {
69 this((String) null, port, key);
08a58812
NR
70 }
71
72 /**
73 * Create a new server that will start listening on the network when
74 * {@link Server#start()} is called.
8468bb79
NR
75 * <p>
76 * All the communications will happen in plain text.
08a58812
NR
77 *
78 * @param name
79 * the server name (only used for debug info and traces)
80 * @param port
81 * the port to listen on
08a58812
NR
82 *
83 * @throws IOException
84 * in case of I/O error
f4053377
NR
85 * @throws UnknownHostException
86 * if the IP address of the host could not be determined
87 * @throws IllegalArgumentException
88 * if the port parameter is outside the specified range of valid
89 * port values, which is between 0 and 65535, inclusive
08a58812 90 */
8468bb79
NR
91 public Server(String name, int port) throws IOException {
92 this(name, port, null);
93 }
94
95 /**
96 * Create a new server that will start listening on the network when
97 * {@link Server#start()} is called.
98 *
99 * @param name
100 * the server name (only used for debug info and traces)
101 * @param port
102 * the port to listen on
103 * @param key
104 * an optional key to encrypt all the communications (if NULL,
105 * everything will be sent in clear text)
106 *
107 * @throws IOException
108 * in case of I/O error
109 * @throws UnknownHostException
110 * if the IP address of the host could not be determined
111 * @throws IllegalArgumentException
112 * if the port parameter is outside the specified range of valid
113 * port values, which is between 0 and 65535, inclusive
114 */
115 public Server(String name, int port, String key) throws IOException {
08a58812 116 this.name = name;
ce0974c4 117 this.port = port;
8468bb79
NR
118 this.key = key;
119 this.ss = new ServerSocket(port);
08a58812
NR
120
121 if (this.port == 0) {
122 this.port = this.ss.getLocalPort();
123 }
124 }
125
126 /**
127 * The traces handler for this {@link Server}.
128 *
129 * @return the traces handler
130 */
131 public TraceHandler getTraceHandler() {
132 return tracer;
ce0974c4
NR
133 }
134
08a58812
NR
135 /**
136 * The traces handler for this {@link Server}.
137 *
138 * @param tracer
139 * the new traces handler
140 */
141 public void setTraceHandler(TraceHandler tracer) {
142 if (tracer == null) {
143 tracer = new TraceHandler(false, false, false);
144 }
145
146 this.tracer = tracer;
147 }
148
79ce1a49
NR
149 /**
150 * The name of this {@link Server} if any.
151 * <p>
152 * Used for traces and debug purposes only.
153 *
154 * @return the name or NULL
155 */
156 public String getName() {
157 return name;
158 }
159
08a58812
NR
160 /**
161 * Return the assigned port.
f157aed8
NR
162 *
163 * @return the assigned port
08a58812
NR
164 */
165 public int getPort() {
166 return port;
167 }
168
4bb7e88e
NR
169 /**
170 * The total amount of bytes received.
171 *
172 * @return the amount of bytes received
173 */
174 public long getBytesReceived() {
175 return bytesReceived;
176 }
177
178 /**
179 * The total amount of bytes sent.
180 *
181 * @return the amount of bytes sent
182 */
183 public long getBytesSent() {
184 return bytesSent;
185 }
186
08a58812
NR
187 /**
188 * Start the server (listen on the network for new connections).
189 * <p>
190 * Can only be called once.
b08aa70e 191 * <p>
d463d266
NR
192 * This call is asynchronous, and will just start a new {@link Thread} on
193 * itself (see {@link Server#run()}).
08a58812 194 */
ce0974c4 195 public void start() {
d463d266 196 new Thread(this).start();
b08aa70e
NR
197 }
198
199 /**
200 * Start the server (listen on the network for new connections).
201 * <p>
202 * Can only be called once.
d463d266
NR
203 * <p>
204 * You may call it via {@link Server#start()} for an asynchronous call, too.
b08aa70e 205 */
d463d266
NR
206 @Override
207 public void run() {
208 ServerSocket ss = null;
209 boolean alreadyStarted = false;
ce0974c4 210 synchronized (lock) {
d463d266 211 ss = this.ss;
08a58812 212 if (!started && ss != null) {
b08aa70e 213 started = true;
d463d266
NR
214 } else {
215 alreadyStarted = started;
ce0974c4
NR
216 }
217 }
08a58812 218
d463d266 219 if (alreadyStarted) {
08a58812
NR
220 tracer.error(name + ": cannot start server on port " + port
221 + ", it is already started");
d463d266
NR
222 return;
223 }
224
225 if (ss == null) {
226 tracer.error(name + ": cannot start server on port " + port
227 + ", it has already been used");
228 return;
229 }
230
231 try {
f4053377 232 tracer.trace(name + ": server starting on port " + port + " ("
8468bb79 233 + (key != null ? "encrypted" : "plain text") + ")");
d463d266
NR
234
235 while (started && !exiting) {
236 count(1);
8537d55a
NR
237 final Socket s = ss.accept();
238 new Thread(new Runnable() {
239 @Override
240 public void run() {
4bb7e88e 241 ConnectActionServer action = null;
8537d55a 242 try {
4bb7e88e
NR
243 action = createConnectActionServer(s);
244 action.connect();
8537d55a
NR
245 } finally {
246 count(-1);
4bb7e88e
NR
247 if (action != null) {
248 bytesReceived += action.getBytesReceived();
249 bytesSent += action.getBytesSent();
250 }
8537d55a
NR
251 }
252 }
253 }).start();
d463d266
NR
254 }
255
256 // Will be covered by @link{Server#stop(long)} for timeouts
257 while (counter > 0) {
258 Thread.sleep(10);
259 }
260 } catch (Exception e) {
261 if (counter > 0) {
262 onError(e);
263 }
264 } finally {
265 try {
266 ss.close();
267 } catch (Exception e) {
268 onError(e);
269 }
270
271 this.ss = null;
272
273 started = false;
274 exiting = false;
275 counter = 0;
276
277 tracer.trace(name + ": client terminated on port " + port);
08a58812 278 }
ce0974c4
NR
279 }
280
08a58812
NR
281 /**
282 * Will stop the server, synchronously and without a timeout.
283 */
ce0974c4 284 public void stop() {
08a58812 285 tracer.trace(name + ": stopping server");
ce0974c4
NR
286 stop(0, true);
287 }
288
08a58812
NR
289 /**
290 * Stop the server.
291 *
292 * @param timeout
293 * the maximum timeout to wait for existing actions to complete,
294 * or 0 for "no timeout"
295 * @param wait
296 * wait for the server to be stopped before returning
297 * (synchronous) or not (asynchronous)
298 */
ce0974c4
NR
299 public void stop(final long timeout, final boolean wait) {
300 if (wait) {
301 stop(timeout);
302 } else {
303 new Thread(new Runnable() {
cd0c27d2 304 @Override
ce0974c4
NR
305 public void run() {
306 stop(timeout);
307 }
308 }).start();
309 }
310 }
311
08a58812
NR
312 /**
313 * Stop the server (synchronous).
314 *
315 * @param timeout
316 * the maximum timeout to wait for existing actions to complete,
317 * or 0 for "no timeout"
318 */
ce0974c4 319 private void stop(long timeout) {
d463d266 320 tracer.trace(name + ": server stopping on port " + port);
ce0974c4
NR
321 synchronized (lock) {
322 if (started && !exiting) {
323 exiting = true;
324
325 try {
8468bb79
NR
326 new ConnectActionClientObject(new Socket((String) null,
327 port), key).connect();
ce0974c4
NR
328 long time = 0;
329 while (ss != null && timeout > 0 && timeout > time) {
330 Thread.sleep(10);
331 time += 10;
332 }
333 } catch (Exception e) {
334 if (ss != null) {
335 counter = 0; // will stop the main thread
336 onError(e);
337 }
338 }
339 }
0988831f 340 }
ce0974c4 341
0988831f
NR
342 // return only when stopped
343 while (started || exiting) {
344 try {
345 Thread.sleep(10);
346 } catch (InterruptedException e) {
ce0974c4
NR
347 }
348 }
349 }
350
08a58812
NR
351 /**
352 * Change the number of currently serviced actions.
353 *
354 * @param change
355 * the number to increase or decrease
356 *
357 * @return the current number after this operation
358 */
8537d55a 359 private int count(int change) {
ce0974c4
NR
360 synchronized (counterLock) {
361 counter += change;
362 return counter;
363 }
364 }
365
8537d55a
NR
366 /**
367 * This method will be called on errors.
368 * <p>
369 * By default, it will only call the trace handler (so you may want to call
370 * super {@link Server#onError} if you override it).
371 *
372 * @param e
373 * the error
374 */
375 protected void onError(Exception e) {
376 tracer.error(e);
377 }
378
08a58812
NR
379 /**
380 * Create a {@link Socket}.
381 *
382 * @param host
383 * the host to connect to
384 * @param port
385 * the port to connect to
08a58812
NR
386 *
387 * @return the {@link Socket}
388 *
389 * @throws IOException
390 * in case of I/O error
f4053377
NR
391 * @throws UnknownHostException
392 * if the host is not known
393 * @throws IllegalArgumentException
394 * if the port parameter is outside the specified range of valid
395 * port values, which is between 0 and 65535, inclusive
08a58812 396 */
8468bb79
NR
397 @Deprecated
398 static Socket createSocket(String host, int port) throws IOException {
399 return new Socket(host, port);
ce0974c4
NR
400 }
401
08a58812
NR
402 /**
403 * Create a {@link ServerSocket}.
404 *
405 * @param port
406 * the port to accept connections on
08a58812
NR
407 *
408 * @return the {@link ServerSocket}
409 *
410 * @throws IOException
411 * in case of I/O error
f4053377
NR
412 * @throws UnknownHostException
413 * if the IP address of the host could not be determined
414 * @throws IllegalArgumentException
415 * if the port parameter is outside the specified range of valid
416 * port values, which is between 0 and 65535, inclusive
08a58812 417 */
8468bb79
NR
418 @Deprecated
419 static ServerSocket createSocketServer(int port) throws IOException {
420 return new ServerSocket(port);
ce0974c4
NR
421 }
422}