Add 'src/be/nikiroo/utils/' from commit '46add0670fdee4bd936a13fe2448c5e20a7ffd0a'
[fanfix.git] / src / be / nikiroo / utils / serial / server / ConnectAction.java
index 50d9ffcf61abdc8991094769ab8b6fd3cf3d68aa..6a19368bbf97824deb624ffaf9eac238b44dfad7 100644 (file)
@@ -9,6 +9,8 @@ import javax.net.ssl.SSLException;
 
 import be.nikiroo.utils.CryptUtils;
 import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.StringUtils;
+import be.nikiroo.utils.Version;
 import be.nikiroo.utils.serial.Exporter;
 import be.nikiroo.utils.serial.Importer;
 import be.nikiroo.utils.streams.BufferedOutputStream;
@@ -27,9 +29,18 @@ import be.nikiroo.utils.streams.ReplaceOutputStream;
  * @author niki
  */
 abstract class ConnectAction {
+       // We separate each "packet" we send with this character and make sure it
+       // does not occurs in the message itself.
+       static private char STREAM_SEP = '\b';
+       static private String[] STREAM_RAW = new String[] { "\\", "\b" };
+       static private String[] STREAM_CODED = new String[] { "\\\\", "\\b" };
+
        private Socket s;
        private boolean server;
 
+       private Version clientVersion;
+       private Version serverVersion;
+
        private CryptUtils crypt;
 
        private Object lock = new Object();
@@ -41,10 +52,28 @@ abstract class ConnectAction {
         * Method that will be called when an action is performed on either the
         * client or server this {@link ConnectAction} represent.
         * 
+        * @param version
+        *            the version on the other side of the communication (client or
+        *            server)
+        * 
         * @throws Exception
         *             in case of I/O error
         */
-       abstract protected void action() throws Exception;
+       abstract protected void action(Version version) throws Exception;
+
+       /**
+        * Method called when we negotiate the version with the client.
+        * <p>
+        * Thus, it is only called on the server.
+        * <p>
+        * Will return the actual server version by default.
+        * 
+        * @param clientVersion
+        *            the client version
+        * 
+        * @return the version to send to the client
+        */
+       abstract protected Version negotiateVersion(Version clientVersion);
 
        /**
         * Handler called when an unexpected error occurs in the code.
@@ -66,13 +95,40 @@ abstract class ConnectAction {
         * @param key
         *            an optional key to encrypt all the communications (if NULL,
         *            everything will be sent in clear text)
+        * @param version
+        *            the client-or-server version (depending upon the boolean
+        *            parameter <tt>server</tt>)
         */
-       protected ConnectAction(Socket s, boolean server, String key) {
+       protected ConnectAction(Socket s, boolean server, String key,
+                       Version version) {
                this.s = s;
                this.server = server;
                if (key != null) {
                        crypt = new CryptUtils(key);
                }
+
+               if (version == null) {
+                       version = new Version();
+               }
+
+               if (server) {
+                       serverVersion = version;
+               } else {
+                       clientVersion = version;
+               }
+       }
+
+       /**
+        * The version of this client-or-server.
+        * 
+        * @return the version
+        */
+       public Version getVersion() {
+               if (server) {
+                       return serverVersion;
+               }
+
+               return clientVersion;
        }
 
        /**
@@ -98,15 +154,34 @@ abstract class ConnectAction {
         */
        public void connect() {
                try {
-                       // TODO: assure that \b is never used, make sure \n usage is OK
                        in = new NextableInputStream(s.getInputStream(),
-                                       new NextableInputStreamStep('\b'));
-
+                                       new NextableInputStreamStep(STREAM_SEP));
                        try {
                                out = new BufferedOutputStream(s.getOutputStream());
-
                                try {
-                                       action();
+                                       // Negotiate version
+                                       Version version;
+                                       if (server) {
+                                               String HELLO = recString();
+                                               if (HELLO == null || !HELLO.startsWith("VERSION ")) {
+                                                       throw new SSLException(
+                                                                       "Client used bad encryption key");
+                                               }
+                                               version = negotiateVersion(new Version(
+                                                               HELLO.substring("VERSION ".length())));
+                                               sendString("VERSION " + version);
+                                       } else {
+                                               String HELLO = sendString("VERSION " + clientVersion);
+                                               if (HELLO == null || !HELLO.startsWith("VERSION ")) {
+                                                       throw new SSLException(
+                                                                       "Server did not accept the encryption key");
+                                               }
+                                               version = new Version(HELLO.substring("VERSION "
+                                                               .length()));
+                                       }
+
+                                       // Actual code
+                                       action(version);
                                } finally {
                                        out.close();
                                }
@@ -293,15 +368,10 @@ abstract class ConnectAction {
                                sub = out.open();
                        }
 
-                       // TODO: could be possible to check for non-crypt and only
-                       // do it for crypt
-                       sub = new ReplaceOutputStream(sub, //
-                                       new String[] { "\\", "\b" }, //
-                                       new String[] { "\\\\", "\\b" });
-
+                       sub = new ReplaceOutputStream(sub, STREAM_RAW, STREAM_CODED);
                        try {
                                if (asString) {
-                                       sub.write(data.toString().getBytes("UTF-8"));
+                                       sub.write(StringUtils.getBytes(data.toString()));
                                } else {
                                        new Exporter(sub).append(data);
                                }
@@ -309,7 +379,7 @@ abstract class ConnectAction {
                                sub.close();
                        }
 
-                       out.write('\b');
+                       out.write(STREAM_SEP);
 
                        if (server) {
                                out.flush();
@@ -363,6 +433,7 @@ abstract class ConnectAction {
         * @throws java.lang.NullPointerException
         *             for Objects only: if the counter part has no data to send
         */
+       @SuppressWarnings("resource")
        private Object rec(boolean asString) throws IOException,
                        NoSuchFieldException, NoSuchMethodException,
                        ClassNotFoundException, java.lang.NullPointerException {
@@ -374,13 +445,9 @@ abstract class ConnectAction {
                                        contentToSend = false;
                                }
 
-                               if (in.next()) {
-                                       // TODO: could be possible to check for non-crypt and only
-                                       // do it for crypt
-                                       InputStream read = new ReplaceInputStream(in.open(), //
-                                                       new String[] { "\\\\", "\\b" }, //
-                                                       new String[] { "\\", "\b" });
-
+                               if (in.next() && !in.eof()) {
+                                       InputStream read = new ReplaceInputStream(in.open(),
+                                                       STREAM_CODED, STREAM_RAW);
                                        try {
                                                if (crypt != null) {
                                                        read = crypt.decrypt64(read);