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