| ``` | |
| --- a replacement for aproto ------------------------------------------- | |
| When it comes down to it, aproto's primary purpose is to forward | |
| various streams between the host computer and client device (in either | |
| direction). | |
| This replacement further simplifies the concept, reducing the protocol | |
| to an extremely straightforward model optimized to accomplish the | |
| forwarding of these streams and removing additional state or | |
| complexity. | |
| The host side becomes a simple comms bridge with no "UI", which will | |
| be used by either commandline or interactive tools to communicate with | |
| a device or emulator that is connected to the bridge. | |
| The protocol is designed to be straightforward and well-defined enough | |
| that if it needs to be reimplemented in another environment (Java | |
| perhaps), there should not problems ensuring perfect interoperability. | |
| The protocol discards the layering aproto has and should allow the | |
| implementation to be much more robust. | |
| --- protocol overview and basics --------------------------------------- | |
| The transport layer deals in "messages", which consist of a 24 byte | |
| header followed (optionally) by a payload. The header consists of 6 | |
| 32 bit words which are sent across the wire in little endian format. | |
| struct message { | |
| unsigned command; /* command identifier constant (A_CNXN, ...) */ | |
| unsigned arg0; /* first argument */ | |
| unsigned arg1; /* second argument */ | |
| unsigned data_length; /* length of payload (0 is allowed) */ | |
| unsigned data_crc32; /* crc32 of data payload */ | |
| unsigned magic; /* command ^ 0xffffffff */ | |
| }; | |
| Receipt of an invalid message header, corrupt message payload, or an | |
| unrecognized command MUST result in the closing of the remote | |
| connection. The protocol depends on shared state and any break in the | |
| message stream will result in state getting out of sync. | |
| The following sections describe the six defined message types in | |
| detail. Their format is COMMAND(arg0, arg1, payload) where the payload | |
| is represented by a quoted string or an empty string if none should be | |
| sent. | |
| The identifiers "local-id" and "remote-id" are always relative to the | |
| *sender* of the message, so for a receiver, the meanings are effectively | |
| reversed. | |
| --- CONNECT(version, maxdata, "system-identity-string") ---------------- | |
| Command constant: A_CNXN | |
| The CONNECT message establishes the presence of a remote system. | |
| The version is used to ensure protocol compatibility and maxdata | |
| declares the maximum message body size that the remote system | |
| is willing to accept. | |
| Currently, version=0x01000000 and maxdata=256*1024. Older versions of adb | |
| hard-coded maxdata=4096, so CONNECT and AUTH packets sent to a device must not | |
| be larger than that because they're sent before the CONNECT from the device | |
| that tells the adb server what maxdata the device can support. | |
| Both sides send a CONNECT message when the connection between them is | |
| established. Until a CONNECT message is received no other messages may | |
| be sent. Any messages received before a CONNECT message MUST be ignored. | |
| If a CONNECT message is received with an unknown version or insufficiently | |
| large maxdata value, the connection with the other side must be closed. | |
| The system identity string should be "<systemtype>:<serialno>:<banner>" | |
| where systemtype is "bootloader", "device", or "host", serialno is some | |
| kind of unique ID (or empty), and banner is a human-readable version | |
| or identifier string. The banner is used to transmit useful properties. | |
| --- STLS(type, version, "") -------------------------------------------- | |
| Command constant: A_STLS | |
| The TLS message informs the recipient that the connection will be encrypted | |
| and will need to perform a TLS handshake. version is the current version of | |
| the protocol. | |
| --- AUTH(type, 0, "data") ---------------------------------------------- | |
| Command constant: A_AUTH | |
| The AUTH message informs the recipient that authentication is required to | |
| connect to the sender. If type is TOKEN(1), data is a random token that | |
| the recipient can sign with a private key. The recipient replies with an | |
| AUTH packet where type is SIGNATURE(2) and data is the signature. If the | |
| signature verification succeeds, the sender replies with a CONNECT packet. | |
| If the signature verification fails, the sender replies with a new AUTH | |
| packet and a new random token, so that the recipient can retry signing | |
| with a different private key. | |
| Once the recipient has tried all its private keys, it can reply with an | |
| AUTH packet where type is RSAPUBLICKEY(3) and data is the public key. If | |
| possible, an on-screen confirmation may be displayed for the user to | |
| confirm they want to install the public key on the device. | |
| --- OPEN(local-id, 0, "destination") ----------------------------------- | |
| Command constant: A_OPEN | |
| The OPEN message informs the recipient that the sender has a stream | |
| identified by local-id that it wishes to connect to the named | |
| destination in the message payload. The local-id may not be zero. | |
| The OPEN message MUST result in either a READY message indicating that | |
| the connection has been established (and identifying the other end) or | |
| a CLOSE message, indicating failure. An OPEN message also implies | |
| a READY message sent at the same time. | |
| Common destination naming conventions include: | |
| * "tcp:<host>:<port>" - host may be omitted to indicate localhost | |
| * "udp:<host>:<port>" - host may be omitted to indicate localhost | |
| * "vsock:<CID>:<port>" | |
| * "local-dgram:<identifier>" | |
| * "local-stream:<identifier>" | |
| * "shell" - local shell service | |
| * "upload" - service for pushing files across (like aproto's /sync) | |
| * "fs-bridge" - FUSE protocol filesystem bridge | |
| --- READY(local-id, remote-id, "") ------------------------------------- | |
| Command constant: A_OKAY | |
| The READY message informs the recipient that the sender's stream | |
| identified by local-id is ready for write messages and that it is | |
| connected to the recipient's stream identified by remote-id. | |
| Neither the local-id nor the remote-id may be zero. | |
| A READY message containing a remote-id which does not map to an open | |
| stream on the recipient's side is ignored. The stream may have been | |
| closed while this message was in-flight. | |
| The local-id is ignored on all but the first READY message (where it | |
| is used to establish the connection). Nonetheless, the local-id MUST | |
| not change on later READY messages sent to the same stream. | |
| --- WRITE(local-id, remote-id, "data") --------------------------------- | |
| Command constant: A_WRTE | |
| The WRITE message sends data to the recipient's stream identified by | |
| remote-id. The payload MUST be <= maxdata in length. | |
| A WRITE message containing a remote-id which does not map to an open | |
| stream on the recipient's side is ignored. The stream may have been | |
| closed while this message was in-flight. | |
| A WRITE message may not be sent until a READY message is received. | |
| Once a WRITE message is sent, an additional WRITE message may not be | |
| sent until another READY message has been received. Recipients of | |
| a WRITE message that is in violation of this requirement will CLOSE | |
| the connection. | |
| --- CLOSE(local-id, remote-id, "") ------------------------------------- | |
| Command constant: A_CLSE | |
| The CLOSE message informs recipient that the connection between the | |
| sender's stream (local-id) and the recipient's stream (remote-id) is | |
| broken. The remote-id MUST not be zero, but the local-id MAY be zero | |
| if this CLOSE indicates a failed OPEN. | |
| A CLOSE message containing a remote-id which does not map to an open | |
| stream on the recipient's side is ignored. The stream may have | |
| already been closed by the recipient while this message was in-flight. | |
| The recipient should not respond to a CLOSE message in any way. The | |
| recipient should cancel pending WRITEs or CLOSEs, but this is not a | |
| requirement, since they will be ignored. | |
| --- SYNC(online, sequence, "") ----------------------------------------- | |
| Command constant: A_SYNC | |
| *** obsolete, no longer used *** | |
| The SYNC message was used by the io pump to make sure that stale | |
| outbound messages are discarded when the connection to the remote side | |
| is broken. It was only used internally to the bridge and never valid | |
| to send across the wire. | |
| * when the connection to the remote side goes offline, the io pump | |
| sends a SYNC(0, 0) and starts discarding all messages | |
| * when the connection to the remote side is established, the io pump | |
| sends a SYNC(1, token) and continues to discard messages | |
| * when the io pump receives a matching SYNC(1, token), it once again | |
| starts accepting messages to forward to the remote side | |
| --- message command constants ------------------------------------------ | |
| #define A_SYNC 0x434e5953 | |
| #define A_CNXN 0x4e584e43 | |
| #define A_AUTH 0x48545541 | |
| #define A_OPEN 0x4e45504f | |
| #define A_OKAY 0x59414b4f | |
| #define A_CLSE 0x45534c43 | |
| #define A_WRTE 0x45545257 | |
| #define A_STLS 0x534C5453 | |
| --- implementation details --------------------------------------------- | |
| The core of the bridge program will use three threads. One thread | |
| will be a select/epoll loop to handle io between various inbound and | |
| outbound connections and the connection to the remote side. | |
| The remote side connection will be implemented as two threads (one for | |
| reading, one for writing) and a datagram socketpair to provide the | |
| channel between the main select/epoll thread and the remote connection | |
| threadpair. The reason for this is that for usb connections, the | |
| kernel interface on linux and osx does not allow you to do meaningful | |
| nonblocking IO. | |
| The endian swapping for the message headers will happen (as needed) in | |
| the remote connection threadpair and that the rest of the program will | |
| always treat message header values as native-endian. | |
| The bridge program will be able to have a number of mini-servers | |
| compiled in. They will be published under known names (examples | |
| "shell", "fs-bridge", etc) and upon receiving an OPEN() to such a | |
| service, the bridge program will create a stream socketpair and spawn | |
| a thread or subprocess to handle the io. | |
| --- simplified / embedded implementation ------------------------------- | |
| For limited environments, like the bootloader, it is allowable to | |
| support a smaller, fixed number of channels using pre-assigned channel | |
| ID numbers such that only one stream may be connected to a bootloader | |
| endpoint at any given time. The protocol remains unchanged, but the | |
| "embedded" version of it is less dynamic. | |
| The bootloader will support two streams. A "bootloader:debug" stream, | |
| which may be opened to get debug messages from the bootloader and a | |
| "bootloader:control", stream which will support the set of basic | |
| bootloader commands. | |
| Example command stream dialogues: | |
| "flash_kernel,2515049,........\n" "okay\n" | |
| "flash_ramdisk,5038,........\n" "fail,flash write error\n" | |
| "bogus_command......" <CLOSE> | |
| --- future expansion --------------------------------------------------- | |
| I plan on providing either a message or a special control stream so that | |
| the client device could ask the host computer to setup inbound socket | |
| translations on the fly on behalf of the client device. | |
| The initial design does handshaking to provide flow control, with a | |
| message flow that looks like: | |
| >OPEN <READY >WRITE <READY >WRITE <READY >WRITE <CLOSE | |
| The far side may choose to issue the READY message as soon as it receives | |
| a WRITE or it may defer the READY until the write to the local stream | |
| succeeds. A future version may want to do some level of windowing where | |
| multiple WRITEs may be sent without requiring individual READY acks. | |
| ------------------------------------------------------------------------ | |
| --- smartsockets ------------------------------------------------------- | |
| Port 5037 is used for smart sockets which allow a client on the host | |
| side to request access to a service in the host adb daemon or in the | |
| remote (device) daemon. The service is requested by ascii name, | |
| preceeded by a 4 digit hex length. Upon successful connection an | |
| "OKAY" response is sent, otherwise a "FAIL" message is returned. Once | |
| connected the client is talking to that (remote or local) service. | |
| client: <hex4> <service-name> | |
| server: "OKAY" | |
| client: <hex4> <service-name> | |
| server: "FAIL" <hex4> <reason> | |
| ``` |