c5750bdcf2aa0616c955229a94388cb95195066b
1 package be
.nikiroo
.utils
.serial
.server
;
3 import java
.io
.BufferedReader
;
4 import java
.io
.IOException
;
5 import java
.io
.InputStream
;
6 import java
.io
.InputStreamReader
;
7 import java
.io
.OutputStream
;
8 import java
.net
.Socket
;
10 import javax
.net
.ssl
.SSLException
;
12 import be
.nikiroo
.utils
.CryptUtils
;
13 import be
.nikiroo
.utils
.Version
;
14 import be
.nikiroo
.utils
.serial
.Exporter
;
15 import be
.nikiroo
.utils
.serial
.Importer
;
18 * Base class used for the client/server basic handling.
20 * It represents a single action: a client is expected to only execute one
21 * action, while a server is expected to execute one action for each client
26 abstract class ConnectAction
{
28 private boolean server
;
29 private Version version
;
30 private Version clientVersion
;
32 private CryptUtils crypt
;
34 private Object lock
= new Object();
35 private InputStream in
;
36 private OutputStream out
;
37 private boolean contentToSend
;
39 private long bytesReceived
;
40 private long bytesSent
;
43 * Method that will be called when an action is performed on either the
44 * client or server this {@link ConnectAction} represent.
47 * the counter part version
50 * in case of I/O error
52 abstract protected void action(Version version
) throws Exception
;
55 * Method called when we negotiate the version with the client.
57 * Thus, it is only called on the server.
59 * Will return the actual server version by default.
61 * @param clientVersion
64 * @return the version to send to the client
66 abstract protected Version
negotiateVersion(Version clientVersion
);
69 * Handler called when an unexpected error occurs in the code.
72 * the exception that occurred, SSLException usually denotes a
75 abstract protected void onError(Exception e
);
78 * Create a new {@link ConnectAction}.
81 * the socket to bind to
83 * TRUE for a server action, FALSE for a client action (will
86 * an optional key to encrypt all the communications (if NULL,
87 * everything will be sent in clear text)
89 * the version of this client-or-server
91 protected ConnectAction(Socket s
, boolean server
, String key
,
96 crypt
= new CryptUtils(key
);
99 if (version
== null) {
100 this.version
= new Version();
102 this.version
= version
;
105 clientVersion
= new Version();
109 * The version of this client-or-server.
111 * @return the version
113 public Version
getVersion() {
118 * The total amount of bytes received.
120 * @return the amount of bytes received
122 public long getBytesReceived() {
123 return bytesReceived
;
127 * The total amount of bytes sent.
129 * @return the amount of bytes sent
131 public long getBytesSent() {
136 * Actually start the process (this is synchronous).
138 public void connect() {
140 in
= s
.getInputStream();
142 out
= s
.getOutputStream();
148 } catch (SSLException e
) {
149 out
.write("Unauthorized\n".getBytes());
153 if (line
!= null && line
.startsWith("VERSION ")) {
154 // "VERSION client-version" (VERSION 1.0.0)
155 Version clientVersion
= new Version(
156 line
.substring("VERSION ".length()));
157 this.clientVersion
= clientVersion
;
158 Version v
= negotiateVersion(clientVersion
);
163 sendString("VERSION " + v
.toString());
166 action(clientVersion
);
168 String v
= sendString("VERSION " + version
.toString());
169 if (v
!= null && v
.startsWith("VERSION ")) {
170 v
= v
.substring("VERSION ".length());
173 action(new Version(v
));
183 } catch (Exception e
) {
188 } catch (Exception e
) {
195 * Serialise and send the given object to the counter part (and, only for
196 * client, return the deserialised answer -- the server will always receive
202 * @return the answer (which can be NULL if no answer, or NULL for an answer
203 * which is NULL) if this action is a client, always NULL if it is a
206 * @throws IOException
207 * in case of I/O error
208 * @throws NoSuchFieldException
209 * if the serialised data contains information about a field
210 * which does actually not exist in the class we know of
211 * @throws NoSuchMethodException
212 * if a class described in the serialised data cannot be created
213 * because it is not compatible with this code
214 * @throws ClassNotFoundException
215 * if a class described in the serialised data cannot be found
217 protected Object
sendObject(Object data
) throws IOException
,
218 NoSuchFieldException
, NoSuchMethodException
, ClassNotFoundException
{
219 synchronized (lock
) {
221 new Exporter(out
).append(data
);
228 contentToSend
= true;
231 } catch (NullPointerException e
) {
232 // We accept no data here
240 * Reserved for the server: flush the data to the client and retrieve its
243 * Also used internally for the client (only do something if there is
246 * Will only flush the data if there is contentToSend.
248 * @return the deserialised answer (which can actually be NULL)
250 * @throws IOException
251 * in case of I/O error
252 * @throws NoSuchFieldException
253 * if the serialised data contains information about a field
254 * which does actually not exist in the class we know of
255 * @throws NoSuchMethodException
256 * if a class described in the serialised data cannot be created
257 * because it is not compatible with this code
258 * @throws ClassNotFoundException
259 * if a class described in the serialised data cannot be found
260 * @throws java.lang.NullPointerException
261 * if the counter part has no data to send
263 protected Object
recObject() throws IOException
, NoSuchFieldException
,
264 NoSuchMethodException
, ClassNotFoundException
,
265 java
.lang
.NullPointerException
{
266 synchronized (lock
) {
267 if (server
|| contentToSend
) {
270 contentToSend
= false;
273 return new Importer().read(in
).getValue();
281 * Send the given string to the counter part (and, only for client, return
282 * the answer -- the server will always receive NULL).
285 * the data to send (we will add a line feed)
287 * @return the answer if this action is a client (without the added line
288 * feed), NULL if it is a server
290 * @throws IOException
291 * in case of I/O error
292 * @throws SSLException
293 * in case of crypt error
295 protected String
sendString(String line
) throws IOException
{
296 synchronized (lock
) {
297 writeLine(out
, line
);
304 contentToSend
= true;
310 * Reserved for the server (externally): flush the data to the client and
311 * retrieve its answer.
313 * Also used internally for the client (only do something if there is
316 * Will only flush the data if there is contentToSend.
318 * @return the answer (which can be NULL)
320 * @throws IOException
321 * in case of I/O error
322 * @throws SSLException
323 * in case of crypt error
325 protected String
recString() throws IOException
{
326 synchronized (lock
) {
327 if (server
|| contentToSend
) {
330 contentToSend
= false;
341 * Read a possibly encrypted line.
344 * the stream to read from
345 * @return the unencrypted line
348 * @throws IOException
349 * in case of I/O error
350 * @throws SSLException
351 * in case of crypt error
353 private String
readLine(InputStream in
) throws IOException
{
354 if (inReader
== null) {
355 inReader
= new BufferedReader(new InputStreamReader(in
));
357 String line
= inReader
.readLine();
359 bytesReceived
+= line
.length();
361 line
= crypt
.decrypt64s(line
, false);
368 private BufferedReader inReader
;
371 * Write a line, possible encrypted.
374 * the stream to write to
377 * @throws IOException
378 * in case of I/O error
379 * @throws SSLException
380 * in case of crypt error
382 private void writeLine(OutputStream out
, String line
) throws IOException
{
384 out
.write(line
.getBytes("UTF-8"));
385 bytesSent
+= line
.length();
387 // TODO: how NOT to create so many big Strings?
388 String b64
= crypt
.encrypt64(line
, false);
389 out
.write(b64
.getBytes("UTF-8"));
390 bytesSent
+= b64
.length();
392 out
.write("\n".getBytes("UTF-8"));