| # How ADB uses USB Zero-Length Packets (ZLP) | |
| ## TLDR; | |
| There is no USB mechanism that lets a sender announce the size of a `Transfer`. This is not | |
| a problem when the host side receives packet since is leverage the aprotocol to know what size | |
| of transfer to expect. However, traffic towards the device must include zero length packets to | |
| mark the boundaries since the device does not leverage the aprotocol. | |
| ## Introduction | |
| There is an asymmetry in how ADB communicates over USB. While all USB backends on the host side (Linux, | |
| Windows, Mac, and libusb) send ZLPs, the device side never does. Why is that? This document explains | |
| what ZLPs are, how ADB uses them, and why things are designed this way. | |
| ## USB Transfer 101 | |
| In the context of ADB, USB can be thought of as two unidirectional pipes per device. | |
| One pipe takes care of upload while the other takes care of the download. On the pipe | |
| transit payloads. The maximum size of a payload is set by the pipe buffers located | |
| on the device. These buffers are called the `Endpoint`. | |
| ``` | |
| ┌──────────┐ ┌──────────┐ | |
| │ USB Host │ │USB Device│ | |
| ├──────────┤ ├──────────┤ | |
| │ │ │ │ | |
| │ Pipe ◄──┼─────────────────────────┼ Endpoint │ | |
| │ │ USB │ │ | |
| │ Pipe ───┼─────────────────────────► Endpoint │ | |
| └──────────┘ └──────────┘ | |
| ``` | |
| In USB parlance, sending a buffer of data on a pipe and receiving it on the other end is called a `Transfer`. | |
| On the sender side, the USB Controller is presented with a [buffer,size] pair called IRP. On the receiver | |
| side, a similar IRP is provided to the USB controller. | |
| ``` | |
| ┌────────┐ ┌──────────┐ | |
| │ Sender │ │ Receiver │ | |
| └────┬───┘ └─────┬────┘ | |
| │ │ | |
| ┌──▼───┐ ┌──▼───┐ | |
| │ IRP │ │ IRP │ | |
| └──┬───┘ └──▲───┘ | |
| ┌──────▼───────┐ ┌──────┴───────┐ | |
| │USB Controller│ │USB Controller│ | |
| │ │ │ │ | |
| │ ─┼───►─DP──►─DP ─►──DP──►─┼─► │ | |
| └──────────────┘ └──────────────┘ | |
| ``` | |
| Because of the endpoint buffer size (`wMaxPacketSize`), an IRP is broken down in | |
| several data payloads called `Data Packets` (DP). | |
| Note: On the device, ADB uses `functionfs` which is not based on IRP. However, the logic is the same since received DP | |
| must be re-assembled on the device to rebuild the original IRP. To simplify this document we use the name "IRP" | |
| everywhere in this doc to mean "[buffer,size] pair provided to the USB Controller". | |
| ## When does a USB Transfer ends? | |
| If an IRP is broken down in DPs by the sender, how does the receiver reassemble | |
| the DPs into an IRP on the other side? | |
| The key concept to get out of this whole document is that there is no mechanism | |
| in USB for the sender to announce the size of a `Transfer`. Instead, the receiving | |
| end uses the following rules. A `Transfer` is considered done when either of the following condition | |
| is met. | |
| - An error occurred (device disconnected, ...). | |
| - The IRP is full. | |
| - The size of the packet is less than `wMaxPacketSize` (this is a Short-Packet). This is | |
| a different behavior from the usual UNIX `read(3)` which is allowed to return less than required | |
| without meaning that the stream is over. | |
| - Too much data is received. The IRP overflows (this is also an error). | |
| See USB 2 specifications (5.8.3 Bulk Transfer Packet Size Constraints) for additional information. | |
| ``` | |
| An endpoint must always transmit data payloads with a data field less than or equal to the endpoint’s | |
| reported wMaxPacketSize value. When a bulk IRP involves more data than can fit in one maximum-sized | |
| data payload, all data payloads are required to be maximum size except for the last data payload, which will | |
| contain the remaining data. A bulk transfer is complete when the endpoint does one of the following: | |
| • Has transferred exactly the amount of data expected | |
| • Transfers a packet with a payload size less than wMaxPacketSize or transfers a zero-length packet | |
| ``` | |
| ### Example 1: The IRP is full | |
| For a USB3 bulk pipe, the `wMaxPacketSize` is 1024. The sender "S" wishes | |
| to send 2048 bytes. It creates a IRP, fills it with the 2048 bytes, and gives the IRP | |
| to the USB controller. On the | |
| received side "R", the USB controller is provided with a IRP of side 2048. | |
| ``` | |
| Traffic: | |
| S -> 1024 -> R | |
| S -> 1024 -> R IRP full, Transfer OK! | |
| ``` | |
| At this point R's IRP is full. R USB controller declares the `Transfer` over | |
| and calls whatever callback the client provided the IRP. | |
| ### Example 2: Short-Packet | |
| Same USB3 bulk as Example 1. The `wMaxPacketSize` is 1024. The sender wishes | |
| to send 2148 bytes. It creates a IRP of size 2148 bytes and fills it with data. | |
| On the received side, the USB controller is provided with a IRP of size 4096. | |
| ``` | |
| Traffic: | |
| S -> 1024 -> R | |
| S -> 1024 -> R | |
| S -> 100 -> R Short-Packet, Transfer OK! | |
| ``` | |
| The receiver end detects a short packet. Even though it was provided with a 4906 | |
| byte IRP, it declares the `Transfer` completed (and records the actual size | |
| of the `Transfer` in the IRP). | |
| ### Example 3: Overflow | |
| Same USB3 bulk as Example 1. The `wMaxPacketSize` is 1024. The sender wishes | |
| to send 4096 bytes. It creates a IRP, fills it with the 4096 bytes. On the | |
| receiver side, the USB controller is provided with an IRP of size 2148. | |
| ``` | |
| Traffic: | |
| S -> 1024 -> R | |
| S -> 1024 -> R | |
| S -> 1024 -> R ERROR, Transfer failed! | |
| ``` | |
| On the third packet, the receiver runs out of space in the IRP (it only had 100 | |
| bytes available). Without a way to fully store this packet, | |
| it discards everything and returns an error stating that the `Transfer` was not successful. | |
| ## Preventing overflow and the need for Zero-Length Packets | |
| There are two techniques to avoid overflows. | |
| ### Using a protocol | |
| One technique is to create a protocol on top of `Transfers`. | |
| ADB does that with its "aprotocol" ([protocol.md](protocol.md)). | |
| In aprotocol, the sender creates a `Transfer` containing a header which is | |
| always 24 bytes. Then it sends a separate `Transfer` containing the payload. | |
| The size of the payload is in the header. This way the receiver always knows | |
| what size of IRP to provide to the USB controller: it first requests a 24 byte IRP | |
| read, extracts the size of the payload, then issues a second IRP read request | |
| with the extracted size of the payload. | |
| ### Using a multiple of `wMaxPacketSize` | |
| The other technique to avoid overflows is for the receiver to always use a IRP with | |
| a size which is a multiple of the `wMaxPacketSize`. This way a `Transfer` always ends properly. | |
| * A max size packet will exactly finish to fill the IRP, ending the `Transfer`. | |
| * A short packet will end the `Transfer`. | |
| This technique comes with an edge case. Take the example of a USB3 pipe where | |
| `wMaxPacketSize` is 1024. The sender wishes to send 3072 byte. It creates a IRP | |
| of that size, fills in the data and gives it to the USB controller which breaks | |
| it into Packets. The receiver decides to read with a IRP of size 4096. | |
| ``` | |
| Traffic: | |
| S -> 1024 -> R | |
| S -> 1024 -> R | |
| S -> 1024 -> R | |
| . | |
| . | |
| . | |
| Stalled! | |
| ``` | |
| After the USB controller on the sender side has sent all the data in the IRP, it won't send anything else. | |
| But none of the ending conditions of a `Transfer` have been reached on the receiving end. No overflow, no short-packet, and the IRP is not | |
| full (there is still 1024 bytes unused). As is, the USB controller on the receiving end will never declare the `Transfer` | |
| either successful or failed. This is a stall (at least until another Packet is sent, if ever). | |
| This condition is entered when the size of a IRP to send is a multiple of `wMaxPacketSize` | |
| but less than the size of the IRP provided by the receiving end. To fix this condition, | |
| the sender MUST issue a Zero-Length Packet. Technically, this is a short packet (it is less | |
| than `wMaxPacketSize`). Upon receiving the ZLP, the receiver declares the `Transfer` | |
| finished. | |
| ``` | |
| Traffic: | |
| S -> 1024 -> R | |
| S -> 1024 -> R | |
| S -> 1024 -> R | |
| S -> 0 -> R Short-Packet, Transfer is over! | |
| ``` | |
| ## Implementation choices | |
| By now, it should be clear that whether a sender needs to send a ZLP depends on the way | |
| the receiver end works. | |
| ### ADB Device to Host pipe communication design | |
| The receiver on the host leverages ADB aprotocol ([protocol.md](protocol.md)). It | |
| first creates a IRP of size 24 bytes to receive the header. Then it creates a IRP | |
| `Transfer` | |
| of the size of the payload. Because the IRPs are always exactly the size of the `Transfer` | |
| the device sends, there is no need for LZP. The USB Controller on the host side will always be able | |
| to declare a `Transfer` complete when the IRP is full and there will never be any overflow. | |
| The drawback of this technique is that it can consume a lot of RAM because multiple | |
| IRPs can be in flight at a time. With the maximum size of | |
| a apacket payload being MAX_PAYLOAD (1MiB), things can quickly add up. | |
| ### ADB Host to Device pipe communication design | |
| On the device side, the receiver does not leverage the ADB aprotocol ([protocol.md](protocol.md)). | |
| I suspect this was done to reduce memory consumption (the first Android device had a total RAM size of 192MiB). | |
| The UsbFS connection always requests the same | |
| `Transfer` size. To prevent overflows, the size is picked to be a multiple of the `wMaxPacketSize` (1x would be | |
| valid but the overhead would kill performances). Currently, the value is kUsbReadSize (16384). USB endpoints | |
| have a well known `wMaxPacketSize` so 16384 works for all of them (this list is for bulk transfers only which | |
| ADB exclusively uses). | |
| * Full Speed: 8, 16, 32, or 64 bytes. | |
| * High Speed: 512 bytes. | |
| * Super Speed: 1024 bytes. | |
| When the apacket payload size is a multiple | |
| of the `wMaxPacketSize`, the sender on the host side MUST send a ZLP to avoid stalling | |
| on the receiver end. | |
| ## What happens if the host sender has a bug and ZLPs are not sent? | |
| If there is a bug on the host and ZLPs are not sent, several things can happen. | |
| You can observe normal behavior, stalled communication, or even device disconnection. | |
| Because there are many buffers before the USB controller layer | |
| is hit, the issue won't be deterministic. However my experience showed that attempting to | |
| push 10GiB rarely | |
| fails to bring up instabilities. | |
| ``` | |
| $ dd if=/dev/urandom of=r.bin bs=1G count=10 iflag=fullblock` | |
| $ adb push -Z r.bin /datal/local/tmp | |
| ``` | |
| ### 1. Nothing breaks | |
| You could be unlucky and not trigger the fault. | |
| ### 2. Stall | |
| A payload of a size that's a multiple of `wMaxPacketSize` but of size less than kUsbReadSize (16384) is sent. | |
| This is a stall as previously described. | |
| ### 3. Disconnection (due to merged packets) | |
| In real-life usage, there is rarely a single thing happening on ADB. Users often also run logcat, Studio | |
| monitors metrics, or perhaps the user has a shell opened. What happens if a connection goes stalls | |
| but then something else sends an apacket? | |
| The first `Transfer` of the apacket will be an apacket header which is 24 bytes. This will be considered | |
| a short-packet. The previous stalled `Transfer` will be completed with the header appended. This will | |
| confuse UsbFS since the payload will be 24 bytes more than it should be. In this condition, the connection | |
| is closed. The log message is | |
| ``` | |
| received too many bytes while waiting for payload | |
| ``` | |
| or | |
| ``` | |
| received packet of unexpected length while reading header | |
| ``` | |
| A summary inspection of logs may make it look like a payload `Transfer` | |
| was merged with the next header `Transfer`. |