252 lines
10 KiB
Text
252 lines
10 KiB
Text
|
||
--- 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 */
|
||
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") ----------------
|
||
|
||
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=4096
|
||
|
||
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 (informational only).
|
||
|
||
|
||
--- OPEN(local-id, 0, "destination") -----------------------------------
|
||
|
||
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
|
||
* "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, "") -------------------------------------
|
||
|
||
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(0, remote-id, "data") ----------------------------------------
|
||
|
||
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, "") -------------------------------------
|
||
|
||
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, "") -----------------------------------------
|
||
|
||
The SYNC message is used by the io pump to make sure that stale
|
||
outbound messages are discarded when the connection to the remote side
|
||
is broken. It is 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_OPEN 0x4e45504f
|
||
#define A_OKAY 0x59414b4f
|
||
#define A_CLSE 0x45534c43
|
||
#define A_WRTE 0x45545257
|
||
|
||
|
||
|
||
--- 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>
|
||
|