Commit | Line | Data |
---|---|---|
79ce1a49 | 1 | package be.nikiroo.utils.serial.server; |
ce0974c4 | 2 | |
ce0974c4 | 3 | import java.io.IOException; |
9af739b5 | 4 | import java.io.InputStream; |
9af739b5 | 5 | import java.io.OutputStream; |
ce0974c4 NR |
6 | import java.net.Socket; |
7 | ||
9fb03c36 NR |
8 | import javax.net.ssl.SSLException; |
9 | ||
8468bb79 | 10 | import be.nikiroo.utils.CryptUtils; |
08f80ac5 | 11 | import be.nikiroo.utils.IOUtils; |
f8147a0e | 12 | import be.nikiroo.utils.StringUtils; |
79ce1a49 NR |
13 | import be.nikiroo.utils.serial.Exporter; |
14 | import be.nikiroo.utils.serial.Importer; | |
08f80ac5 NR |
15 | import be.nikiroo.utils.streams.BufferedOutputStream; |
16 | import be.nikiroo.utils.streams.NextableInputStream; | |
17 | import be.nikiroo.utils.streams.NextableInputStreamStep; | |
18 | import be.nikiroo.utils.streams.ReplaceInputStream; | |
19 | import be.nikiroo.utils.streams.ReplaceOutputStream; | |
ce0974c4 | 20 | |
f157aed8 NR |
21 | /** |
22 | * Base class used for the client/server basic handling. | |
23 | * <p> | |
24 | * It represents a single action: a client is expected to only execute one | |
25 | * action, while a server is expected to execute one action for each client | |
26 | * action. | |
27 | * | |
28 | * @author niki | |
29 | */ | |
ce0974c4 NR |
30 | abstract class ConnectAction { |
31 | private Socket s; | |
32 | private boolean server; | |
ce0974c4 | 33 | |
8468bb79 NR |
34 | private CryptUtils crypt; |
35 | ||
ce0974c4 | 36 | private Object lock = new Object(); |
08f80ac5 NR |
37 | private NextableInputStream in; |
38 | private BufferedOutputStream out; | |
ce0974c4 NR |
39 | private boolean contentToSend; |
40 | ||
f157aed8 NR |
41 | /** |
42 | * Method that will be called when an action is performed on either the | |
43 | * client or server this {@link ConnectAction} represent. | |
44 | * | |
f157aed8 NR |
45 | * @throws Exception |
46 | * in case of I/O error | |
47 | */ | |
08f80ac5 | 48 | abstract protected void action() throws Exception; |
f157aed8 NR |
49 | |
50 | /** | |
51 | * Handler called when an unexpected error occurs in the code. | |
52 | * | |
53 | * @param e | |
9fb03c36 NR |
54 | * the exception that occurred, SSLException usually denotes a |
55 | * crypt error | |
f157aed8 NR |
56 | */ |
57 | abstract protected void onError(Exception e); | |
ce0974c4 | 58 | |
f157aed8 NR |
59 | /** |
60 | * Create a new {@link ConnectAction}. | |
61 | * | |
62 | * @param s | |
63 | * the socket to bind to | |
64 | * @param server | |
65 | * TRUE for a server action, FALSE for a client action (will | |
66 | * impact the process) | |
8468bb79 NR |
67 | * @param key |
68 | * an optional key to encrypt all the communications (if NULL, | |
69 | * everything will be sent in clear text) | |
f157aed8 | 70 | */ |
08f80ac5 | 71 | protected ConnectAction(Socket s, boolean server, String key) { |
ce0974c4 NR |
72 | this.s = s; |
73 | this.server = server; | |
8468bb79 NR |
74 | if (key != null) { |
75 | crypt = new CryptUtils(key); | |
76 | } | |
ce0974c4 NR |
77 | } |
78 | ||
4bb7e88e NR |
79 | /** |
80 | * The total amount of bytes received. | |
81 | * | |
82 | * @return the amount of bytes received | |
83 | */ | |
84 | public long getBytesReceived() { | |
08f80ac5 | 85 | return in.getBytesRead(); |
4bb7e88e NR |
86 | } |
87 | ||
88 | /** | |
89 | * The total amount of bytes sent. | |
90 | * | |
91 | * @return the amount of bytes sent | |
92 | */ | |
08f80ac5 NR |
93 | public long getBytesWritten() { |
94 | return out.getBytesWritten(); | |
4bb7e88e NR |
95 | } |
96 | ||
f157aed8 NR |
97 | /** |
98 | * Actually start the process (this is synchronous). | |
99 | */ | |
ce0974c4 NR |
100 | public void connect() { |
101 | try { | |
08f80ac5 NR |
102 | // TODO: assure that \b is never used, make sure \n usage is OK |
103 | in = new NextableInputStream(s.getInputStream(), | |
104 | new NextableInputStreamStep('\b')); | |
9fb03c36 | 105 | |
08f80ac5 NR |
106 | try { |
107 | out = new BufferedOutputStream(s.getOutputStream()); | |
ce0974c4 | 108 | |
08f80ac5 NR |
109 | try { |
110 | action(); | |
ce0974c4 NR |
111 | } finally { |
112 | out.close(); | |
113 | } | |
114 | } finally { | |
115 | in.close(); | |
116 | } | |
117 | } catch (Exception e) { | |
118 | onError(e); | |
119 | } finally { | |
120 | try { | |
121 | s.close(); | |
122 | } catch (Exception e) { | |
123 | onError(e); | |
124 | } | |
125 | } | |
126 | } | |
127 | ||
f157aed8 NR |
128 | /** |
129 | * Serialise and send the given object to the counter part (and, only for | |
130 | * client, return the deserialised answer -- the server will always receive | |
131 | * NULL). | |
132 | * | |
133 | * @param data | |
134 | * the data to send | |
135 | * | |
08f80ac5 NR |
136 | * @return the answer (which can be NULL if no answer, or NULL for an answer |
137 | * which is NULL) if this action is a client, always NULL if it is a | |
138 | * server | |
f157aed8 NR |
139 | * |
140 | * @throws IOException | |
141 | * in case of I/O error | |
142 | * @throws NoSuchFieldException | |
143 | * if the serialised data contains information about a field | |
144 | * which does actually not exist in the class we know of | |
145 | * @throws NoSuchMethodException | |
146 | * if a class described in the serialised data cannot be created | |
147 | * because it is not compatible with this code | |
148 | * @throws ClassNotFoundException | |
149 | * if a class described in the serialised data cannot be found | |
150 | */ | |
4bb7e88e NR |
151 | protected Object sendObject(Object data) throws IOException, |
152 | NoSuchFieldException, NoSuchMethodException, ClassNotFoundException { | |
08f80ac5 | 153 | return send(out, data, false); |
ce0974c4 NR |
154 | } |
155 | ||
f157aed8 NR |
156 | /** |
157 | * Reserved for the server: flush the data to the client and retrieve its | |
158 | * answer. | |
159 | * <p> | |
160 | * Also used internally for the client (only do something if there is | |
161 | * contentToSend). | |
162 | * <p> | |
163 | * Will only flush the data if there is contentToSend. | |
164 | * | |
165 | * @return the deserialised answer (which can actually be NULL) | |
166 | * | |
167 | * @throws IOException | |
168 | * in case of I/O error | |
169 | * @throws NoSuchFieldException | |
170 | * if the serialised data contains information about a field | |
171 | * which does actually not exist in the class we know of | |
172 | * @throws NoSuchMethodException | |
173 | * if a class described in the serialised data cannot be created | |
174 | * because it is not compatible with this code | |
175 | * @throws ClassNotFoundException | |
176 | * if a class described in the serialised data cannot be found | |
177 | * @throws java.lang.NullPointerException | |
178 | * if the counter part has no data to send | |
179 | */ | |
4bb7e88e NR |
180 | protected Object recObject() throws IOException, NoSuchFieldException, |
181 | NoSuchMethodException, ClassNotFoundException, | |
182 | java.lang.NullPointerException { | |
08f80ac5 | 183 | return rec(false); |
ce0974c4 NR |
184 | } |
185 | ||
cd0c27d2 | 186 | /** |
f157aed8 NR |
187 | * Send the given string to the counter part (and, only for client, return |
188 | * the answer -- the server will always receive NULL). | |
cd0c27d2 | 189 | * |
f157aed8 NR |
190 | * @param line |
191 | * the data to send (we will add a line feed) | |
cd0c27d2 | 192 | * |
f157aed8 NR |
193 | * @return the answer if this action is a client (without the added line |
194 | * feed), NULL if it is a server | |
195 | * | |
196 | * @throws IOException | |
197 | * in case of I/O error | |
9fb03c36 NR |
198 | * @throws SSLException |
199 | * in case of crypt error | |
cd0c27d2 | 200 | */ |
f157aed8 | 201 | protected String sendString(String line) throws IOException { |
08f80ac5 NR |
202 | try { |
203 | return (String) send(out, line, true); | |
204 | } catch (NoSuchFieldException e) { | |
205 | // Cannot happen | |
206 | e.printStackTrace(); | |
207 | } catch (NoSuchMethodException e) { | |
208 | // Cannot happen | |
209 | e.printStackTrace(); | |
210 | } catch (ClassNotFoundException e) { | |
211 | // Cannot happen | |
212 | e.printStackTrace(); | |
ce0974c4 | 213 | } |
08f80ac5 NR |
214 | |
215 | return null; | |
ce0974c4 NR |
216 | } |
217 | ||
f157aed8 NR |
218 | /** |
219 | * Reserved for the server (externally): flush the data to the client and | |
220 | * retrieve its answer. | |
221 | * <p> | |
222 | * Also used internally for the client (only do something if there is | |
223 | * contentToSend). | |
224 | * <p> | |
225 | * Will only flush the data if there is contentToSend. | |
226 | * | |
08f80ac5 | 227 | * @return the answer (which can be NULL if no more content) |
f157aed8 NR |
228 | * |
229 | * @throws IOException | |
230 | * in case of I/O error | |
9fb03c36 NR |
231 | * @throws SSLException |
232 | * in case of crypt error | |
f157aed8 | 233 | */ |
79ce1a49 | 234 | protected String recString() throws IOException { |
08f80ac5 NR |
235 | try { |
236 | return (String) rec(true); | |
237 | } catch (NoSuchFieldException e) { | |
238 | // Cannot happen | |
239 | e.printStackTrace(); | |
240 | } catch (NoSuchMethodException e) { | |
241 | // Cannot happen | |
242 | e.printStackTrace(); | |
243 | } catch (ClassNotFoundException e) { | |
244 | // Cannot happen | |
245 | e.printStackTrace(); | |
246 | } catch (NullPointerException e) { | |
247 | // Should happen | |
248 | e.printStackTrace(); | |
ce0974c4 | 249 | } |
08f80ac5 NR |
250 | |
251 | return null; | |
ce0974c4 | 252 | } |
8468bb79 | 253 | |
9fb03c36 | 254 | /** |
08f80ac5 NR |
255 | * Serialise and send the given object to the counter part (and, only for |
256 | * client, return the deserialised answer -- the server will always receive | |
257 | * NULL). | |
9fb03c36 | 258 | * |
08f80ac5 NR |
259 | * @param out |
260 | * the stream to write to | |
261 | * @param data | |
262 | * the data to write | |
263 | * @param asString | |
264 | * TRUE to write it as a String, FALSE to write it as an Object | |
9fb03c36 | 265 | * |
08f80ac5 NR |
266 | * @return the answer (which can be NULL if no answer, or NULL for an answer |
267 | * which is NULL) if this action is a client, always NULL if it is a | |
268 | * server | |
9fb03c36 NR |
269 | * |
270 | * @throws IOException | |
271 | * in case of I/O error | |
272 | * @throws SSLException | |
273 | * in case of crypt error | |
08f80ac5 NR |
274 | * @throws IOException |
275 | * in case of I/O error | |
276 | * @throws NoSuchFieldException | |
277 | * if the serialised data contains information about a field | |
278 | * which does actually not exist in the class we know of | |
279 | * @throws NoSuchMethodException | |
280 | * if a class described in the serialised data cannot be created | |
281 | * because it is not compatible with this code | |
282 | * @throws ClassNotFoundException | |
283 | * if a class described in the serialised data cannot be found | |
9fb03c36 | 284 | */ |
08f80ac5 NR |
285 | private Object send(BufferedOutputStream out, Object data, boolean asString) |
286 | throws IOException, NoSuchFieldException, NoSuchMethodException, | |
287 | ClassNotFoundException, java.lang.NullPointerException { | |
288 | ||
289 | synchronized (lock) { | |
290 | OutputStream sub; | |
8468bb79 | 291 | if (crypt != null) { |
a6a73de3 | 292 | sub = crypt.encrypt64(out.open()); |
08f80ac5 NR |
293 | } else { |
294 | sub = out.open(); | |
8468bb79 | 295 | } |
8468bb79 | 296 | |
08f80ac5 NR |
297 | // TODO: could be possible to check for non-crypt and only |
298 | // do it for crypt | |
299 | sub = new ReplaceOutputStream(sub, // | |
300 | new String[] { "\\", "\b" }, // | |
301 | new String[] { "\\\\", "\\b" }); | |
302 | ||
303 | try { | |
304 | if (asString) { | |
f8147a0e | 305 | sub.write(StringUtils.getBytes(data.toString())); |
08f80ac5 NR |
306 | } else { |
307 | new Exporter(sub).append(data); | |
308 | } | |
309 | } finally { | |
310 | sub.close(); | |
311 | } | |
8468bb79 | 312 | |
08f80ac5 NR |
313 | out.write('\b'); |
314 | ||
315 | if (server) { | |
316 | out.flush(); | |
317 | return null; | |
318 | } | |
319 | ||
320 | contentToSend = true; | |
321 | try { | |
322 | return rec(asString); | |
323 | } catch (NullPointerException e) { | |
324 | // We accept no data here for Objects | |
325 | } | |
326 | ||
327 | return null; | |
328 | } | |
329 | } | |
9af739b5 | 330 | |
9fb03c36 | 331 | /** |
08f80ac5 NR |
332 | * Reserved for the server: flush the data to the client and retrieve its |
333 | * answer. | |
334 | * <p> | |
335 | * Also used internally for the client (only do something if there is | |
336 | * contentToSend). | |
337 | * <p> | |
338 | * Will only flush the data if there is contentToSend. | |
339 | * <p> | |
340 | * Note that the behaviour is slightly different for String and Object | |
341 | * reading regarding exceptions: | |
342 | * <ul> | |
343 | * <li>NULL means that the counter part has no more data to send</li> | |
344 | * <li>All the exceptions except {@link IOException} are there for Object | |
345 | * conversion</li> | |
346 | * </ul> | |
347 | * | |
348 | * @param asString | |
349 | * TRUE for String reading, FALSE for Object reading (which can | |
350 | * still be a String) | |
351 | * | |
352 | * @return the deserialised answer (which can actually be NULL) | |
9fb03c36 | 353 | * |
9fb03c36 NR |
354 | * @throws IOException |
355 | * in case of I/O error | |
08f80ac5 NR |
356 | * @throws NoSuchFieldException |
357 | * if the serialised data contains information about a field | |
358 | * which does actually not exist in the class we know of | |
359 | * @throws NoSuchMethodException | |
360 | * if a class described in the serialised data cannot be created | |
361 | * because it is not compatible with this code | |
362 | * @throws ClassNotFoundException | |
363 | * if a class described in the serialised data cannot be found | |
364 | * @throws java.lang.NullPointerException | |
365 | * for Objects only: if the counter part has no data to send | |
9fb03c36 | 366 | */ |
bd86c221 | 367 | @SuppressWarnings("resource") |
08f80ac5 NR |
368 | private Object rec(boolean asString) throws IOException, |
369 | NoSuchFieldException, NoSuchMethodException, | |
370 | ClassNotFoundException, java.lang.NullPointerException { | |
371 | ||
372 | synchronized (lock) { | |
373 | if (server || contentToSend) { | |
374 | if (contentToSend) { | |
375 | out.flush(); | |
376 | contentToSend = false; | |
377 | } | |
378 | ||
d2219aa0 | 379 | if (in.next() && !in.eof()) { |
08f80ac5 NR |
380 | // TODO: could be possible to check for non-crypt and only |
381 | // do it for crypt | |
382 | InputStream read = new ReplaceInputStream(in.open(), // | |
383 | new String[] { "\\\\", "\\b" }, // | |
384 | new String[] { "\\", "\b" }); | |
385 | ||
386 | try { | |
387 | if (crypt != null) { | |
a6a73de3 | 388 | read = crypt.decrypt64(read); |
08f80ac5 NR |
389 | } |
390 | ||
391 | if (asString) { | |
392 | return IOUtils.readSmallStream(read); | |
393 | } | |
394 | ||
395 | return new Importer().read(read).getValue(); | |
396 | } finally { | |
397 | read.close(); | |
398 | } | |
399 | } | |
400 | ||
401 | if (!asString) { | |
402 | throw new NullPointerException(); | |
403 | } | |
404 | } | |
405 | ||
406 | return null; | |
8468bb79 | 407 | } |
8468bb79 | 408 | } |
ce0974c4 | 409 | } |