The data structure of asocket, with their queue, amessage, and apackets are described in internals.md. But understanding asocket, how they are used, and how they are paired is non-trivial. This document hopefully explains how bytes flow through them.
The concept of asocket was created to achieve two things.
With the introduction of TCP support, an abstraction layer (transport) was created but TCP multiplexing was not leveraged. Even when using TCP, a transport still uses asocket to multiplex streams.
enqueued.There are several types of asocket. Some are easy to understand because they extend asocket.
However there are "undeclared" types, whose behavior differs only via the asocket function pointers.
A LS interfaces with a file descriptor to forward a stream of bytes without altering it (as opposed to a LSS). To perform its task, a LS leverages fdevent to request FDE_READ/FDE_WRITE notification.
LOCAL SOCKET TRANSPORT
┌────────────────────────────────────────────────┐ ┌──┐
┌──┐ write(3) │ ┌─────┐ enqueue() │ │
│ │◄─────────┼──┤Queue├─────────────◄──────────────◄──────────┼─────────(A_WRTE)◄──
│fd│ │ └─────┘ │ │ │
│ ├──────────►─────────────────┐ │ │ │
└──┘ read(3) └─────────────────┼──────────────────────────────┘ │ │
▼ └──┘
peer.enqueue()
A_WRTE apackets are forwarded directly to the LS by the transport. The transport is able to route the apacket to the local asocket by using apacket.msg1 which points to the target local asocket id.
When a payload is enqueued, an LS tries to write as much as possible to its fd. After the write attempt, the LS stores in its queue what could not be written. Based on the volume of data in the queue, it sets FDE_WRITE and allows/forbids more data to come.
FDE_WRITE events so it can write the outstanding data.MAX_PAYLOAD in the queue, LS calls ready on its peer (a RS), so an A_OKAY apacket is sent (which trigger another A_WRTE packet to be send).MAX_PAYLOAD in the queue, back-pressure is propagated by not calling peer->ready. This will trigger the other side to not send more A_WRTE until the volume of data in the queue has decreased.When it is created, a LS requests FDE_READ from fdevent. When it triggers, it reads as much as possible from the fd (within MAX_PAYLOAD to make sure transport will take it). The data is then enqueueed on the peer.
If peer.enqueue indicates that the peer cannot take more updates, the LS deletes the FDE_READ request. It is re-installed when A_OKAY is received by transport.
A RS handles outbound traffic and interfaces with a transport. It is simple compared to a LS since it merely translates function calls into transport packets.
A RS is often paired with a LS or a LSS.
LOCAL SOCKET (THIS) TRANSPORT
┌────────────────────────────────────────────────┐ ┌──┐
┌──┐ write(3) │ ┌─────┐ enqueue() │ │ │
│ │◄─────────┼──┤Queue├─────────────◄──────────────◄──────────┼─────────(A_WRTE)◄──
│fd│ │ └─────┘ │ │ │
│ ├──────────►─────────────────┐ │ ─ │ │
└──┘ read(3) └─────────────────┼──────────────────────────────┘ │ │
│ │ │
┌─────────────────▼─────────────────▲────────────┐ │ │
│ │ │ │ │
│ │ │ │ │
│ └─────────────────────►──────────────────(A_WRTE)───►
│ enqueue() │ │ │
└────────────────────────────────────────────────┘ └──┘
REMOTE SOCKET (PEER)
A RS is always created by the transport layer (on A_OKAY or A_OPEN) and paired with a LS or LSS.
Upon A_OPEN: The transport creates a LSS to handle inbound traffic and peers it with a RS to handle outbound traffic.
Upon A_OKAY: When receiving this packet, the transport always checks if there is a LS with the id matching msg1. If there is and it does not have a peer yet, a RS is created, which completes a bi-directional chain.
A LSS is a wrapper around a fd (which is used to build a LS). The purpose is to process inbound and outbound traffic when it needs modification. e.g.: The "framebuffer" service involves invoking executable screencap and generating a header describing the payload before forwarding the color payload. This could not be done with a "simple" LS.
The fd created by the LSS is often a pipe backed by a thread.
These Smart sockets are only created on the host by adb server on accept(3) by the listener service. They interface with a TCP socket.
Upon creation, a SS enqueue does not forward anything until the smart protocol has provided a target device and a service to invoke.
When these two conditions are met, the SS selects a transport and A_OPEN the service on the device. It gives the TCP socket fd to a LS and creates a RS to build a data flow similar to what was described in the Local Socket section.
Let's take the example of the command adb install -S <SIZE> -. There are several install strategies but for the sake of simplicity, let's focus on the one resulting in invoking pm install -S <SIZE> - on the device and then streaming the content of the APK.
In the beginning there is only a listener service, waiting for connect(3) on the server.
ADB Client ADB Server TRANSPORT ADBd
┌──────────────────────┐ ┌─────────────────┐ │ ┌─────────────────┐
│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ tcp * ───►* alistener │ │ │ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
└──────────────────────┘ └─────────────────┘ │ └─────────────────┘
┌──────────┐ ┌───────┐
│ APK │ │Console│
└──────────┘ └───────┘
Upon accept(3), the listener service creates a SS and gives it the socket fd. Then the client starts writing to the socket |host:transport:XXXXXXX| |exec:pm pm install -S <SIZE> ->|.
ADB Client ADB Server TRANSPORT ADBd
┌──────────────────────┐ ┌─────────────────┐ │ ┌─────────────────┐
│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ tcp * ───►* SS │ │ │ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
└──────────────────────┘ └─────────────────┘ │ └─────────────────┘
┌──────────┐ ┌───────┐
│ APK │ │Console│
└──────────┘ └───────┘
The SS buffers the smart protocol requests until it has everything it needs from the client. The first part, host:transport:XXXXXXX lets the SS know which transport to use (it contains the device identified XXXXXXX). The second part is the service to execute exec:pm pm install -S <SIZE> -.
When it has both, the SS creates a LS to handle the TCP fd, and creates a RS to let the LS talk to the transport. The last thing the SS does before replacing itself with a LS (and giving it its socket fd) is sending an A_OPEN apacket.
ADB Client ADB Server TRANSPORT ADBd
┌──────────────────────┐ ┌─────────────────┐ │ ┌─────────────────┐
│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ tcp * ◄───►* LS │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ └─────────► RS─┼──────┼─►───┼──────►A_OPEN │
└──────────────────────┘ └─────────────────┘ │ └─────────────────┘
┌──────────┐ ┌───────┐
│ APK │ │Console│
└──────────┘ └───────┘
So far only one side of the pipeline has been set up.
Upon reception of the A_OPEN on the device side, pm is invoked via fork/exec. A socket pair end is given to a LS. A RS is also created to handle bytes generated by pm. Now we have a full pipeline able to handle bidirectional streams.
ADB Client ADB Server TRANSPORT ADBd
┌──────────────────────┐ ┌─────────────────┐ │ ┌─────────────────┐
│ │ │ │ │ │ │
│ │ │ ┌──────────────┼──◄───┼─────┼─RS ◄───┐ │
│ │ │ ▼ │ │ │ │ │
│ ┌───────────►tcp * ◄───►* LS │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ └─────────► RS─┼──────┼─►───┼──────►LS │
└─────┼─────────────┼──┘ └─────────────────┘ │ └────────▲────────┘
│ │ │
┌─────┴────┐ ┌────▼──┐ ┌─────▼────┐
│ APK │ │Console│ │ PM │
└──────────┘ └───────┘ └──────────┘
At this point the client can write(3) the content of the apk to feed it to pm. It is also able to read(3) to show the output of pm in the console.