1 package be
.nikiroo
.utils
.serial
.server
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.io
.OutputStream
;
6 import java
.net
.Socket
;
8 import javax
.net
.ssl
.SSLException
;
10 import be
.nikiroo
.utils
.CryptUtils
;
11 import be
.nikiroo
.utils
.IOUtils
;
12 import be
.nikiroo
.utils
.StringUtils
;
13 import be
.nikiroo
.utils
.Version
;
14 import be
.nikiroo
.utils
.serial
.Exporter
;
15 import be
.nikiroo
.utils
.serial
.Importer
;
16 import be
.nikiroo
.utils
.streams
.BufferedOutputStream
;
17 import be
.nikiroo
.utils
.streams
.NextableInputStream
;
18 import be
.nikiroo
.utils
.streams
.NextableInputStreamStep
;
19 import be
.nikiroo
.utils
.streams
.ReplaceInputStream
;
20 import be
.nikiroo
.utils
.streams
.ReplaceOutputStream
;
23 * Base class used for the client/server basic handling.
25 * It represents a single action: a client is expected to only execute one
26 * action, while a server is expected to execute one action for each client
31 abstract class ConnectAction
{
32 // We separate each "packet" we send with this character and make sure it
33 // does not occurs in the message itself.
34 static private char STREAM_SEP
= '\b';
35 static private String
[] STREAM_RAW
= new String
[] { "\\", "\b" };
36 static private String
[] STREAM_CODED
= new String
[] { "\\\\", "\\b" };
39 private boolean server
;
41 private Version clientVersion
;
42 private Version serverVersion
;
44 private CryptUtils crypt
;
46 private Object lock
= new Object();
47 private NextableInputStream in
;
48 private BufferedOutputStream out
;
49 private boolean contentToSend
;
52 * Method that will be called when an action is performed on either the
53 * client or server this {@link ConnectAction} represent.
56 * the version on the other side of the communication (client or
60 * in case of I/O error
62 abstract protected void action(Version version
) throws Exception
;
65 * Method called when we negotiate the version with the client.
67 * Thus, it is only called on the server.
69 * Will return the actual server version by default.
71 * @param clientVersion
74 * @return the version to send to the client
76 abstract protected Version
negotiateVersion(Version clientVersion
);
79 * Handler called when an unexpected error occurs in the code.
82 * the exception that occurred, SSLException usually denotes a
85 abstract protected void onError(Exception e
);
88 * Create a new {@link ConnectAction}.
91 * the socket to bind to
93 * TRUE for a server action, FALSE for a client action (will
96 * an optional key to encrypt all the communications (if NULL,
97 * everything will be sent in clear text)
99 * the client-or-server version (depending upon the boolean
100 * parameter <tt>server</tt>)
102 protected ConnectAction(Socket s
, boolean server
, String key
,
105 this.server
= server
;
107 crypt
= new CryptUtils(key
);
110 if (version
== null) {
111 version
= new Version();
115 serverVersion
= version
;
117 clientVersion
= version
;
122 * The version of this client-or-server.
124 * @return the version
126 public Version
getVersion() {
128 return serverVersion
;
131 return clientVersion
;
135 * The total amount of bytes received.
137 * @return the amount of bytes received
139 public long getBytesReceived() {
140 return in
.getBytesRead();
144 * The total amount of bytes sent.
146 * @return the amount of bytes sent
148 public long getBytesWritten() {
149 return out
.getBytesWritten();
153 * Actually start the process (this is synchronous).
155 public void connect() {
157 in
= new NextableInputStream(s
.getInputStream(),
158 new NextableInputStreamStep(STREAM_SEP
));
160 out
= new BufferedOutputStream(s
.getOutputStream());
165 String HELLO
= recString();
166 if (HELLO
== null || !HELLO
.startsWith("VERSION ")) {
167 throw new SSLException(
168 "Client used bad encryption key");
170 version
= negotiateVersion(new Version(
171 HELLO
.substring("VERSION ".length())));
172 sendString("VERSION " + version
);
174 String HELLO
= sendString("VERSION " + clientVersion
);
175 if (HELLO
== null || !HELLO
.startsWith("VERSION ")) {
176 throw new SSLException(
177 "Server did not accept the encryption key");
179 version
= new Version(HELLO
.substring("VERSION "
191 } catch (Exception e
) {
196 } catch (Exception e
) {
203 * Serialise and send the given object to the counter part (and, only for
204 * client, return the deserialised answer -- the server will always receive
210 * @return the answer (which can be NULL if no answer, or NULL for an answer
211 * which is NULL) if this action is a client, always NULL if it is a
214 * @throws IOException
215 * in case of I/O error
216 * @throws NoSuchFieldException
217 * if the serialised data contains information about a field
218 * which does actually not exist in the class we know of
219 * @throws NoSuchMethodException
220 * if a class described in the serialised data cannot be created
221 * because it is not compatible with this code
222 * @throws ClassNotFoundException
223 * if a class described in the serialised data cannot be found
225 protected Object
sendObject(Object data
) throws IOException
,
226 NoSuchFieldException
, NoSuchMethodException
, ClassNotFoundException
{
227 return send(out
, data
, false);
231 * Reserved for the server: flush the data to the client and retrieve its
234 * Also used internally for the client (only do something if there is
237 * Will only flush the data if there is contentToSend.
239 * @return the deserialised answer (which can actually be NULL)
241 * @throws IOException
242 * in case of I/O error
243 * @throws NoSuchFieldException
244 * if the serialised data contains information about a field
245 * which does actually not exist in the class we know of
246 * @throws NoSuchMethodException
247 * if a class described in the serialised data cannot be created
248 * because it is not compatible with this code
249 * @throws ClassNotFoundException
250 * if a class described in the serialised data cannot be found
251 * @throws java.lang.NullPointerException
252 * if the counter part has no data to send
254 protected Object
recObject() throws IOException
, NoSuchFieldException
,
255 NoSuchMethodException
, ClassNotFoundException
,
256 java
.lang
.NullPointerException
{
261 * Send the given string to the counter part (and, only for client, return
262 * the answer -- the server will always receive NULL).
265 * the data to send (we will add a line feed)
267 * @return the answer if this action is a client (without the added line
268 * feed), NULL if it is a server
270 * @throws IOException
271 * in case of I/O error
272 * @throws SSLException
273 * in case of crypt error
275 protected String
sendString(String line
) throws IOException
{
277 return (String
) send(out
, line
, true);
278 } catch (NoSuchFieldException e
) {
281 } catch (NoSuchMethodException e
) {
284 } catch (ClassNotFoundException e
) {
293 * Reserved for the server (externally): flush the data to the client and
294 * retrieve its answer.
296 * Also used internally for the client (only do something if there is
299 * Will only flush the data if there is contentToSend.
301 * @return the answer (which can be NULL if no more content)
303 * @throws IOException
304 * in case of I/O error
305 * @throws SSLException
306 * in case of crypt error
308 protected String
recString() throws IOException
{
310 return (String
) rec(true);
311 } catch (NoSuchFieldException e
) {
314 } catch (NoSuchMethodException e
) {
317 } catch (ClassNotFoundException e
) {
320 } catch (NullPointerException e
) {
329 * Serialise and send the given object to the counter part (and, only for
330 * client, return the deserialised answer -- the server will always receive
334 * the stream to write to
338 * TRUE to write it as a String, FALSE to write it as an Object
340 * @return the answer (which can be NULL if no answer, or NULL for an answer
341 * which is NULL) if this action is a client, always NULL if it is a
344 * @throws IOException
345 * in case of I/O error
346 * @throws SSLException
347 * in case of crypt error
348 * @throws IOException
349 * in case of I/O error
350 * @throws NoSuchFieldException
351 * if the serialised data contains information about a field
352 * which does actually not exist in the class we know of
353 * @throws NoSuchMethodException
354 * if a class described in the serialised data cannot be created
355 * because it is not compatible with this code
356 * @throws ClassNotFoundException
357 * if a class described in the serialised data cannot be found
359 private Object
send(BufferedOutputStream out
, Object data
, boolean asString
)
360 throws IOException
, NoSuchFieldException
, NoSuchMethodException
,
361 ClassNotFoundException
, java
.lang
.NullPointerException
{
363 synchronized (lock
) {
366 sub
= crypt
.encrypt64(out
.open());
371 sub
= new ReplaceOutputStream(sub
, STREAM_RAW
, STREAM_CODED
);
374 sub
.write(StringUtils
.getBytes(data
.toString()));
376 new Exporter(sub
).append(data
);
382 out
.write(STREAM_SEP
);
389 contentToSend
= true;
391 return rec(asString
);
392 } catch (NullPointerException e
) {
393 // We accept no data here for Objects
401 * Reserved for the server: flush the data to the client and retrieve its
404 * Also used internally for the client (only do something if there is
407 * Will only flush the data if there is contentToSend.
409 * Note that the behaviour is slightly different for String and Object
410 * reading regarding exceptions:
412 * <li>NULL means that the counter part has no more data to send</li>
413 * <li>All the exceptions except {@link IOException} are there for Object
418 * TRUE for String reading, FALSE for Object reading (which can
421 * @return the deserialised answer (which can actually be NULL)
423 * @throws IOException
424 * in case of I/O error
425 * @throws NoSuchFieldException
426 * if the serialised data contains information about a field
427 * which does actually not exist in the class we know of
428 * @throws NoSuchMethodException
429 * if a class described in the serialised data cannot be created
430 * because it is not compatible with this code
431 * @throws ClassNotFoundException
432 * if a class described in the serialised data cannot be found
433 * @throws java.lang.NullPointerException
434 * for Objects only: if the counter part has no data to send
436 @SuppressWarnings("resource")
437 private Object
rec(boolean asString
) throws IOException
,
438 NoSuchFieldException
, NoSuchMethodException
,
439 ClassNotFoundException
, java
.lang
.NullPointerException
{
441 synchronized (lock
) {
442 if (server
|| contentToSend
) {
445 contentToSend
= false;
448 if (in
.next() && !in
.eof()) {
449 InputStream read
= new ReplaceInputStream(in
.open(),
450 STREAM_CODED
, STREAM_RAW
);
453 read
= crypt
.decrypt64(read
);
457 return IOUtils
.readSmallStream(read
);
460 return new Importer().read(read
).getValue();
467 throw new NullPointerException();