en cours, 3
[nikiroo-utils.git] / src / be / nikiroo / utils / serial / server / ConnectAction.java
CommitLineData
79ce1a49 1package be.nikiroo.utils.serial.server;
ce0974c4
NR
2
3import java.io.BufferedReader;
4import java.io.IOException;
9af739b5 5import java.io.InputStream;
ce0974c4 6import java.io.InputStreamReader;
9af739b5 7import java.io.OutputStream;
ce0974c4
NR
8import java.net.Socket;
9
9fb03c36
NR
10import javax.net.ssl.SSLException;
11
8468bb79 12import be.nikiroo.utils.CryptUtils;
ce0974c4 13import be.nikiroo.utils.Version;
79ce1a49
NR
14import be.nikiroo.utils.serial.Exporter;
15import be.nikiroo.utils.serial.Importer;
ce0974c4 16
f157aed8
NR
17/**
18 * Base class used for the client/server basic handling.
19 * <p>
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
22 * action.
23 *
24 * @author niki
25 */
ce0974c4
NR
26abstract class ConnectAction {
27 private Socket s;
28 private boolean server;
29 private Version version;
f157aed8 30 private Version clientVersion;
ce0974c4 31
8468bb79
NR
32 private CryptUtils crypt;
33
ce0974c4 34 private Object lock = new Object();
9af739b5
NR
35 private InputStream in;
36 private OutputStream out;
ce0974c4
NR
37 private boolean contentToSend;
38
4bb7e88e
NR
39 private long bytesReceived;
40 private long bytesSent;
41
f157aed8
NR
42 /**
43 * Method that will be called when an action is performed on either the
44 * client or server this {@link ConnectAction} represent.
45 *
46 * @param version
47 * the counter part version
48 *
49 * @throws Exception
50 * in case of I/O error
51 */
52 abstract protected void action(Version version) throws Exception;
53
54 /**
55 * Method called when we negotiate the version with the client.
56 * <p>
57 * Thus, it is only called on the server.
58 * <p>
59 * Will return the actual server version by default.
60 *
61 * @param clientVersion
62 * the client version
63 *
64 * @return the version to send to the client
65 */
66 abstract protected Version negotiateVersion(Version clientVersion);
67
68 /**
69 * Handler called when an unexpected error occurs in the code.
70 *
71 * @param e
9fb03c36
NR
72 * the exception that occurred, SSLException usually denotes a
73 * crypt error
f157aed8
NR
74 */
75 abstract protected void onError(Exception e);
ce0974c4 76
f157aed8
NR
77 /**
78 * Create a new {@link ConnectAction}.
79 *
80 * @param s
81 * the socket to bind to
82 * @param server
83 * TRUE for a server action, FALSE for a client action (will
84 * impact the process)
8468bb79
NR
85 * @param key
86 * an optional key to encrypt all the communications (if NULL,
87 * everything will be sent in clear text)
f157aed8
NR
88 * @param version
89 * the version of this client-or-server
90 */
8468bb79
NR
91 protected ConnectAction(Socket s, boolean server, String key,
92 Version version) {
ce0974c4
NR
93 this.s = s;
94 this.server = server;
8468bb79
NR
95 if (key != null) {
96 crypt = new CryptUtils(key);
97 }
ce0974c4
NR
98
99 if (version == null) {
100 this.version = new Version();
101 } else {
102 this.version = version;
103 }
f157aed8
NR
104
105 clientVersion = new Version();
ce0974c4
NR
106 }
107
f157aed8
NR
108 /**
109 * The version of this client-or-server.
110 *
111 * @return the version
112 */
113 public Version getVersion() {
114 return version;
ce0974c4
NR
115 }
116
4bb7e88e
NR
117 /**
118 * The total amount of bytes received.
119 *
120 * @return the amount of bytes received
121 */
122 public long getBytesReceived() {
123 return bytesReceived;
124 }
125
126 /**
127 * The total amount of bytes sent.
128 *
129 * @return the amount of bytes sent
130 */
131 public long getBytesSent() {
132 return bytesSent;
133 }
134
f157aed8
NR
135 /**
136 * Actually start the process (this is synchronous).
137 */
ce0974c4
NR
138 public void connect() {
139 try {
9af739b5 140 in = s.getInputStream();
ce0974c4 141 try {
9af739b5 142 out = s.getOutputStream();
ce0974c4
NR
143 try {
144 if (server) {
9fb03c36
NR
145 String line;
146 try {
147 line = readLine(in);
148 } catch (SSLException e) {
9af739b5 149 out.write("Unauthorized\n".getBytes());
9fb03c36
NR
150 throw e;
151 }
152
217a3310
NR
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);
159 if (v == null) {
160 v = new Version();
161 }
162
163 sendString("VERSION " + v.toString());
164 }
165
f157aed8 166 action(clientVersion);
ce0974c4
NR
167 } else {
168 String v = sendString("VERSION " + version.toString());
169 if (v != null && v.startsWith("VERSION ")) {
170 v = v.substring("VERSION ".length());
171 }
172
173 action(new Version(v));
174 }
175 } finally {
176 out.close();
0988831f 177 out = null;
ce0974c4
NR
178 }
179 } finally {
180 in.close();
0988831f 181 in = null;
ce0974c4
NR
182 }
183 } catch (Exception e) {
184 onError(e);
185 } finally {
186 try {
187 s.close();
188 } catch (Exception e) {
189 onError(e);
190 }
191 }
192 }
193
f157aed8
NR
194 /**
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
197 * NULL).
198 *
199 * @param data
200 * the data to send
201 *
dc41a952
NR
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
204 * server
f157aed8
NR
205 *
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
216 */
4bb7e88e
NR
217 protected Object sendObject(Object data) throws IOException,
218 NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
ce0974c4 219 synchronized (lock) {
dc41a952
NR
220 new Exporter(out).append(data);
221
222 if (server) {
223 out.flush();
224 return null;
225 }
226
227 contentToSend = true;
228 try {
229 return recObject();
230 } catch (NullPointerException e) {
231 // We accept no data here
08a58812
NR
232 }
233
234 return null;
ce0974c4
NR
235 }
236 }
237
f157aed8
NR
238 /**
239 * Reserved for the server: flush the data to the client and retrieve its
240 * answer.
241 * <p>
242 * Also used internally for the client (only do something if there is
243 * contentToSend).
244 * <p>
245 * Will only flush the data if there is contentToSend.
246 *
247 * @return the deserialised answer (which can actually be NULL)
248 *
249 * @throws IOException
250 * in case of I/O error
251 * @throws NoSuchFieldException
252 * if the serialised data contains information about a field
253 * which does actually not exist in the class we know of
254 * @throws NoSuchMethodException
255 * if a class described in the serialised data cannot be created
256 * because it is not compatible with this code
257 * @throws ClassNotFoundException
258 * if a class described in the serialised data cannot be found
259 * @throws java.lang.NullPointerException
260 * if the counter part has no data to send
261 */
4bb7e88e
NR
262 protected Object recObject() throws IOException, NoSuchFieldException,
263 NoSuchMethodException, ClassNotFoundException,
264 java.lang.NullPointerException {
dc41a952
NR
265 synchronized (lock) {
266 if (server || contentToSend) {
267 if (contentToSend) {
268 out.flush();
269 contentToSend = false;
270 }
ce0974c4 271
dc41a952
NR
272 return new Importer().read(in).getValue();
273 }
274
275 return null;
276 }
ce0974c4
NR
277 }
278
cd0c27d2 279 /**
f157aed8
NR
280 * Send the given string to the counter part (and, only for client, return
281 * the answer -- the server will always receive NULL).
cd0c27d2 282 *
f157aed8
NR
283 * @param line
284 * the data to send (we will add a line feed)
cd0c27d2 285 *
f157aed8
NR
286 * @return the answer if this action is a client (without the added line
287 * feed), NULL if it is a server
288 *
289 * @throws IOException
290 * in case of I/O error
9fb03c36
NR
291 * @throws SSLException
292 * in case of crypt error
cd0c27d2 293 */
f157aed8 294 protected String sendString(String line) throws IOException {
ce0974c4 295 synchronized (lock) {
8468bb79 296 writeLine(out, line);
ce0974c4
NR
297
298 if (server) {
299 out.flush();
300 return null;
ce0974c4 301 }
cd0c27d2
NR
302
303 contentToSend = true;
79ce1a49 304 return recString();
ce0974c4
NR
305 }
306 }
307
f157aed8
NR
308 /**
309 * Reserved for the server (externally): flush the data to the client and
310 * retrieve its answer.
311 * <p>
312 * Also used internally for the client (only do something if there is
313 * contentToSend).
314 * <p>
315 * Will only flush the data if there is contentToSend.
316 *
317 * @return the answer (which can be NULL)
318 *
319 * @throws IOException
320 * in case of I/O error
9fb03c36
NR
321 * @throws SSLException
322 * in case of crypt error
f157aed8 323 */
79ce1a49 324 protected String recString() throws IOException {
ce0974c4
NR
325 synchronized (lock) {
326 if (server || contentToSend) {
327 if (contentToSend) {
328 out.flush();
329 contentToSend = false;
330 }
331
8468bb79 332 return readLine(in);
ce0974c4 333 }
cd0c27d2
NR
334
335 return null;
ce0974c4
NR
336 }
337 }
8468bb79 338
9fb03c36
NR
339 /**
340 * Read a possibly encrypted line.
341 *
342 * @param in
343 * the stream to read from
344 * @return the unencrypted line
345 *
346 *
347 * @throws IOException
348 * in case of I/O error
349 * @throws SSLException
350 * in case of crypt error
351 */
9af739b5
NR
352 private String readLine(InputStream in) throws IOException {
353 if (inReader == null) {
354 inReader = new BufferedReader(new InputStreamReader(in));
355 }
356 String line = inReader.readLine();
8468bb79
NR
357 if (line != null) {
358 bytesReceived += line.length();
359 if (crypt != null) {
360 line = crypt.decrypt64s(line, false);
361 }
362 }
363
364 return line;
365 }
366
9af739b5
NR
367 private BufferedReader inReader;
368
9fb03c36
NR
369 /**
370 * Write a line, possible encrypted.
371 *
372 * @param out
373 * the stream to write to
374 * @param line
375 * the line to write
376 * @throws IOException
377 * in case of I/O error
378 * @throws SSLException
379 * in case of crypt error
380 */
9af739b5 381 private void writeLine(OutputStream out, String line) throws IOException {
8468bb79 382 if (crypt == null) {
68532958 383 out.write(line.getBytes("UTF-8"));
8468bb79
NR
384 bytesSent += line.length();
385 } else {
386 // TODO: how NOT to create so many big Strings?
387 String b64 = crypt.encrypt64(line, false);
68532958 388 out.write(b64.getBytes("UTF-8"));
8468bb79
NR
389 bytesSent += b64.length();
390 }
68532958 391 out.write("\n".getBytes("UTF-8"));
8468bb79
NR
392 bytesSent++;
393 }
ce0974c4 394}