Let "adb connect" connect to emulators too
- adb can now connect to an emulator configured with an arbitrary
  pair of <console port, adb port>. These two ports do not have to be
  adjacent.
  This can be done from the commandline at any time using
  adb connect emu:<console_port>,<adb_port>
- Emulators running on ports outside the normal range
  (5554/5555-5584/5585) register themselves on startup if they follow
  the convention "console port+1==abd port".
- Emulators outside the normal port range will not be auto-detected on
  adb startup as these ports are not probed.
- The index into local_transports[] array in transport_local.c does no
  longer indicate the port number of the local transport. Use the altered
  atransport struct to get the port number.
- I have chosen not to document the adb connect emu:console_port,adb_port
  syntax on adb's help screen as this might be confusing to most readers
  and useful to very few.
- I don't expect this to introduce any (backwards) compatibility issues.

Change-Id: Iad3eccb2dcdde174b24ef0644d705ecfbff6e59d
diff --git a/adb/adb.c b/adb/adb.c
index 04785fd..dd40eef 100644
--- a/adb/adb.c
+++ b/adb/adb.c
@@ -967,6 +967,99 @@
     return 0;
 }
 
+#if ADB_HOST
+void connect_device(char* host, char* buffer, int buffer_size)
+{
+    int port, fd;
+    char* portstr = strchr(host, ':');
+    char buf[4096];
+
+    if (!portstr) {
+        snprintf(buffer, buffer_size, "unable to parse %s as <host>:<port>", host);
+        return;
+    }
+    if (find_transport(host)) {
+        snprintf(buffer, buffer_size, "already connected to %s", host);
+        return;
+    }
+
+    // zero terminate host by overwriting the ':'
+    *portstr++ = 0;
+    if (sscanf(portstr, "%d", &port) == 0) {
+        snprintf(buffer, buffer_size, "bad port number %s", portstr);
+        return;
+    }
+
+    fd = socket_network_client(host, port, SOCK_STREAM);
+    if (fd < 0) {
+        snprintf(buffer, buffer_size, "unable to connect to %s:%d", host, port);
+        return;
+    }
+
+    D("client: connected on remote on fd %d\n", fd);
+    close_on_exec(fd);
+    disable_tcp_nagle(fd);
+    snprintf(buf, sizeof buf, "%s:%d", host, port);
+    register_socket_transport(fd, buf, port, 0);
+    snprintf(buffer, buffer_size, "connected to %s:%d", host, port);
+}
+
+void connect_emulator(char* port_spec, char* buffer, int buffer_size)
+{
+    char* port_separator = strchr(port_spec, ',');
+    if (!port_separator) {
+        snprintf(buffer, buffer_size,
+                "unable to parse '%s' as <console port>,<adb port>",
+                port_spec);
+        return;
+    }
+
+    // Zero-terminate console port and make port_separator point to 2nd port.
+    *port_separator++ = 0;
+    int console_port = strtol(port_spec, NULL, 0);
+    int adb_port = strtol(port_separator, NULL, 0);
+    if (!(console_port > 0 && adb_port > 0)) {
+        *(port_separator - 1) = ',';
+        snprintf(buffer, buffer_size,
+                "Invalid port numbers: Expected positive numbers, got '%s'",
+                port_spec);
+        return;
+    }
+
+    /* Check if the emulator is already known.
+     * Note: There's a small but harmless race condition here: An emulator not
+     * present just yet could be registered by another invocation right
+     * after doing this check here. However, local_connect protects
+     * against double-registration too. From here, a better error message
+     * can be produced. In the case of the race condition, the very specific
+     * error message won't be shown, but the data doesn't get corrupted. */
+    atransport* known_emulator = find_emulator_transport_by_adb_port(adb_port);
+    if (known_emulator != NULL) {
+        snprintf(buffer, buffer_size,
+                "Emulator on port %d already registered.", adb_port);
+        return;
+    }
+
+    /* Check if more emulators can be registered. Similar unproblematic
+     * race condition as above. */
+    int candidate_slot = get_available_local_transport_index();
+    if (candidate_slot < 0) {
+        snprintf(buffer, buffer_size, "Cannot accept more emulators.");
+        return;
+    }
+
+    /* Preconditions met, try to connect to the emulator. */
+    if (!local_connect_arbitrary_ports(console_port, adb_port)) {
+        snprintf(buffer, buffer_size,
+                "Connected to emulator on ports %d,%d", console_port, adb_port);
+    } else {
+        snprintf(buffer, buffer_size,
+                "Could not connect to emulator on ports %d,%d",
+                console_port, adb_port);
+    }
+}
+#endif
+
 int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s)
 {
     atransport *transport = NULL;
@@ -1024,43 +1117,16 @@
         return 0;
     }
 
-    // add a new TCP transport
+    // add a new TCP transport, device or emulator
     if (!strncmp(service, "connect:", 8)) {
         char buffer[4096];
-        int port, fd;
         char* host = service + 8;
-        char* portstr = strchr(host, ':');
-
-        if (!portstr) {
-            snprintf(buffer, sizeof(buffer), "unable to parse %s as <host>:<port>", host);
-            goto done;
+        if (!strncmp(host, "emu:", 4)) {
+            connect_emulator(host + 4, buffer, sizeof(buffer));
+        } else {
+            connect_device(host, buffer, sizeof(buffer));
         }
-        if (find_transport(host)) {
-            snprintf(buffer, sizeof(buffer), "Already connected to %s", host);
-            goto done;
-        }
-
-        // zero terminate host by overwriting the ':'
-        *portstr++ = 0;
-        if (sscanf(portstr, "%d", &port) == 0) {
-            snprintf(buffer, sizeof(buffer), "bad port number %s", portstr);
-            goto done;
-        }
-
-        fd = socket_network_client(host, port, SOCK_STREAM);
-        if (fd < 0) {
-            snprintf(buffer, sizeof(buffer), "unable to connect to %s:%d", host, port);
-            goto done;
-        }
-
-        D("client: connected on remote on fd %d\n", fd);
-        close_on_exec(fd);
-        disable_tcp_nagle(fd);
-        snprintf(buf, sizeof buf, "%s:%d", host, port);
-        register_socket_transport(fd, buf, port, 0);
-        snprintf(buffer, sizeof(buffer), "connected to %s:%d", host, port);
-
-done:
+        // Send response for emulator and device
         snprintf(buf, sizeof(buf), "OKAY%04x%s",(unsigned)strlen(buffer), buffer);
         writex(reply_fd, buf, strlen(buf));
         return 0;
diff --git a/adb/adb.h b/adb/adb.h
index a2b611e..292e415 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -183,6 +183,7 @@
         /* used to identify transports for clients */
     char *serial;
     char *product;
+    int adb_port; // Use for emulators (local transport)
 
         /* a list of adisconnect callbacks called when the transport is kicked */
     int          kicked;
@@ -262,6 +263,9 @@
 void   kick_transport( atransport*  t );
 
 /* initialize a transport object's func pointers and state */
+#if ADB_HOST
+int get_available_local_transport_index();
+#endif
 int  init_socket_transport(atransport *t, int s, int port, int local);
 void init_usb_transport(atransport *t, usb_handle *usb, int state);
 
@@ -280,6 +284,9 @@
 void unregister_usb_transport(usb_handle *usb);
 
 atransport *find_transport(const char *serial);
+#if ADB_HOST
+atransport* find_emulator_transport_by_adb_port(int adb_port);
+#endif
 
 int service_to_fd(const char *name);
 #if ADB_HOST
@@ -368,6 +375,7 @@
 
 void local_init(int port);
 int  local_connect(int  port);
+int  local_connect_arbitrary_ports(int console_port, int adb_port);
 
 /* usb host/client interface */
 void usb_init();
diff --git a/adb/transport_local.c b/adb/transport_local.c
index cfd3b4b..8dfc98d 100644
--- a/adb/transport_local.c
+++ b/adb/transport_local.c
@@ -41,9 +41,9 @@
 #endif
 
 #if ADB_HOST
-/* we keep a list of opened transports, transport 0 is bound to 5555,
- * transport 1 to 5557, .. transport n to 5555 + n*2. the list is used
- * to detect when we're trying to connect twice to a given local transport
+/* we keep a list of opened transports. The atransport struct knows to which
+ * local transport it is connected. The list is used to detect when we're
+ * trying to connect twice to a given local transport.
  */
 #define  ADB_LOCAL_TRANSPORT_MAX  16
 
@@ -102,7 +102,11 @@
 }
 
 
-int  local_connect(int  port)
+int local_connect(int port) {
+    return local_connect_arbitrary_ports(port-1, port);
+}
+
+int local_connect_arbitrary_ports(int console_port, int adb_port)
 {
     char buf[64];
     int  fd = -1;
@@ -110,19 +114,19 @@
 #if ADB_HOST
     const char *host = getenv("ADBHOST");
     if (host) {
-        fd = socket_network_client(host, port, SOCK_STREAM);
+        fd = socket_network_client(host, adb_port, SOCK_STREAM);
     }
 #endif
     if (fd < 0) {
-        fd = socket_loopback_client(port, SOCK_STREAM);
+        fd = socket_loopback_client(adb_port, SOCK_STREAM);
     }
 
     if (fd >= 0) {
         D("client: connected on remote on fd %d\n", fd);
         close_on_exec(fd);
         disable_tcp_nagle(fd);
-        snprintf(buf, sizeof buf, "%s%d", LOCAL_CLIENT_PREFIX, port - 1);
-        register_socket_transport(fd, buf, port, 1);
+        snprintf(buf, sizeof buf, "%s%d", LOCAL_CLIENT_PREFIX, console_port);
+        register_socket_transport(fd, buf, adb_port, 1);
         return 0;
     }
     return -1;
@@ -227,7 +231,50 @@
     adb_close(t->fd);
 }
 
-int init_socket_transport(atransport *t, int s, int port, int local)
+
+#if ADB_HOST
+/* Only call this function if you already hold local_transports_lock. */
+atransport* find_emulator_transport_by_adb_port_locked(int adb_port)
+{
+    int i;
+    for (i = 0; i < ADB_LOCAL_TRANSPORT_MAX; i++) {
+        if (local_transports[i] && local_transports[i]->adb_port == adb_port) {
+            return local_transports[i];
+        }
+    }
+    return NULL;
+}
+
+atransport* find_emulator_transport_by_adb_port(int adb_port)
+{
+    adb_mutex_lock( &local_transports_lock );
+    atransport* result = find_emulator_transport_by_adb_port_locked(adb_port);
+    adb_mutex_unlock( &local_transports_lock );
+    return result;
+}
+
+/* Only call this function if you already hold local_transports_lock. */
+int get_available_local_transport_index_locked()
+{
+    int i;
+    for (i = 0; i < ADB_LOCAL_TRANSPORT_MAX; i++) {
+        if (local_transports[i] == NULL) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+int get_available_local_transport_index()
+{
+    adb_mutex_lock( &local_transports_lock );
+    int result = get_available_local_transport_index_locked();
+    adb_mutex_unlock( &local_transports_lock );
+    return result;
+}
+#endif
+
+int init_socket_transport(atransport *t, int s, int adb_port, int local)
 {
     int  fail = 0;
 
@@ -239,26 +286,30 @@
     t->sync_token = 1;
     t->connection_state = CS_OFFLINE;
     t->type = kTransportLocal;
+    t->adb_port = 0;
 
 #if ADB_HOST
     if (HOST && local) {
         adb_mutex_lock( &local_transports_lock );
         {
-            int  index = (port - DEFAULT_ADB_LOCAL_TRANSPORT_PORT)/2;
-
-            if (!(port & 1) || index < 0 || index >= ADB_LOCAL_TRANSPORT_MAX) {
-                D("bad local transport port number: %d\n", port);
-                fail = -1;
-            }
-            else if (local_transports[index] != NULL) {
+            t->adb_port = adb_port;
+            atransport* existing_transport =
+                    find_emulator_transport_by_adb_port_locked(adb_port);
+            int index = get_available_local_transport_index_locked();
+            if (existing_transport != NULL) {
                 D("local transport for port %d already registered (%p)?\n",
-                port, local_transports[index]);
+                adb_port, existing_transport);
                 fail = -1;
-            }
-            else
+            } else if (index < 0) {
+                // Too many emulators.
+                D("cannot register more emulators. Maximum is %d\n",
+                        ADB_LOCAL_TRANSPORT_MAX);
+                fail = -1;
+            } else {
                 local_transports[index] = t;
-        }
-        adb_mutex_unlock( &local_transports_lock );
+            }
+       }
+       adb_mutex_unlock( &local_transports_lock );
     }
 #endif
     return fail;