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