upc 0.10.1

USB packet channel (UPC): provides a reliable, packet-based transport over USB.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
UPC Protocol Specification
==========================

* **Version**: 1.0
* **Date**: 2026-03-14
* **Author**: Sebastian Urban \<surban@surban.net\>
* **License**: Apache-2.0
* **Status**: Stable
* **Reference implementation**: <https://github.com/surban/upc>

This document describes the USB Packet Channel (UPC) wire protocol.
It is intended for implementers building UPC-compatible devices or hosts
in any language or on any platform, including bare-metal microcontrollers.

Terminology
-----------

The key words **must**, **must not**, **should**, **should not**, and **may**
are used throughout this document to indicate requirement levels.

* **Host**: the USB host (computer, single-board computer, browser, etc.).
* **Device**: the USB device (gadget, microcontroller, etc.).
* **Application packet**: a single message at the application level,
  which may span one or more USB transfers.

1\. USB Interface
-----------------

UPC uses a single USB interface with:

* **Class**: `0xFF` (vendor-specific)
* **SubClass**: user-defined (application-specific)
* **Protocol**: user-defined (application-specific)

The interface **must** contain exactly two bulk endpoints:

* **Bulk IN** (device → host): carries application data.
* **Bulk OUT** (host → device): carries application data.

Endpoint addresses are not fixed — they are read from the interface's
endpoint descriptors at runtime.

All connection management is performed through **vendor-specific control
transfers** on the default control endpoint (EP0). The control transfers use:

* **Type**: Vendor
* **Recipient**: Interface
* **wIndex**: interface number
* **wValue**: 0 (reserved for future use)

### Microsoft OS Descriptors (optional)

To enable driverless operation on Windows, the device **should** include:

* A Microsoft OS 2.0 compatible ID descriptor marking the interface as
  WinUSB-compatible.
* A device interface GUID property so Windows can enumerate the device.

These are standard Microsoft OS descriptor mechanisms and are not part of
the UPC protocol itself.

2\. Control Requests
--------------------

All control requests use vendor type, interface recipient, with
`wIndex` = interface number and `wValue` = 0.

| ID     | Name           | Direction | Data                        | Required |
|--------|----------------|-----------|-----------------------------|----------|
| `0x00` | PROBE          | IN        | 3 bytes: `"UPC"`            | Optional |
| `0x01` | OPEN           | OUT       | 0–4096 bytes: topic         | Required |
| `0x02` | CLOSE          | OUT       | empty                       | Required |
| `0x03` | INFO           | IN        | 0–4096 bytes: info          | Optional |
| `0x04` | CLOSE_SEND     | OUT       | 8 bytes: byte count (u64 LE)| Optional |
| `0x05` | CLOSE_RECV     | OUT       | empty                       | Optional |
| `0x06` | STATUS         | IN        | 0–8 bytes: status flags     | Optional |
| `0x07` | CAPABILITIES   | IN or OUT | TLV-encoded capabilities    | Optional |

### PROBE (0x00) — Device → Host

The host sends a PROBE request to check whether the interface implements
UPC.

* The device **should** respond with the 3-byte ASCII string `"UPC"`
  (bytes `0x55 0x50 0x43`).
* If the interface does not support UPC, the device **must** stall the
  request.
* A stall is not an error — the host interprets it as "not a UPC
  interface."

PROBE is intended for auto-discovery. It **may** be sent at any time,
even before OPEN.

### OPEN (0x01) — Host → Device

Opens a new connection.

* The host **must** send OPEN before transmitting data on the bulk
  endpoints.
* The data payload contains a user-defined **topic** (0 to 4096 bytes).
  The topic is delivered to the device application and may carry
  connection metadata (e.g. a service name).
* An empty topic (0 bytes) is valid.
* The device **must** accept the OPEN request and prepare to exchange
  data on the bulk endpoints. OPEN always succeeds at the protocol
  level — topic-based filtering is an application concern. If the
  device application does not recognize the topic, it **may**
  immediately close the connection after accepting.
* If OPEN is received while a connection is already active, the device
  **must** close the previous connection (as if CLOSE were received)
  and open a new one.

### CLOSE (0x02) — Host → Device

Closes the connection in both directions.

* The host **must** send CLOSE when the connection is fully torn down
  (both directions closed).
* The host **should** also send CLOSE at the start of a new connection
  (before OPEN) to ensure any stale state from a previous connection is
  cleaned up.
* On receiving CLOSE, the device **must** stop all bulk endpoint
  activity and reset its connection state.

### INFO (0x03) — Device → Host

Reads device-provided information.

* The device **may** respond with up to 4096 bytes of free-form data,
  typically describing the device or service.
* This request **may** be sent at any time, including before OPEN.
* If not supported, the device **should** stall the request.

### CLOSE_SEND (0x04) — Host → Device

The host signals that it is done sending data (half-close of the
host → device direction).

* The payload is 8 bytes containing the total number of bytes sent
  by the host as a **little-endian u64**.
* The device **should** use this byte count to drain any remaining
  buffered data from the OUT endpoint before considering the direction
  closed. The device continues receiving from the OUT endpoint until
  the total number of bytes received equals the byte count or the
  connection is terminated by other means (e.g. CLOSE or
  disconnection). This is necessary because USB bulk transfers may be
  buffered in the device controller and the byte count lets the device
  know when all data has actually arrived.
* If the device does not support half-close, it **may** ignore this
  request.

### CLOSE_RECV (0x05) — Host → Device

The host signals that it is done receiving data (half-close of the
device → host direction).

* The payload is empty.
* On receiving this, the device **should** stop sending data and halt
  the IN endpoint.
* If the device does not support half-close, it **may** ignore this
  request.

### STATUS (0x06) — Device → Host

Liveness check and status query.

* The device **must** respond with 0 to 8 bytes. Each byte is a status
  flag.
* An empty response (0 bytes) means the device is alive and everything
  is normal.
* Defined status bytes:

  | Value  | Name          | Meaning                                    |
  |--------|---------------|--------------------------------------------|
  | `0x01` | RECV_CLOSED   | Device has closed its receive direction.    |

  Unknown status bytes **should** be ignored by the host for forward
  compatibility.

* The device advertises support for STATUS via the `status_supported`
  capability (see section 4). If the device does not advertise support,
  the host **must not** send STATUS requests.
* The host uses STATUS as a liveness ping. The device uses it to detect
  host disconnection (see section 5).

### CAPABILITIES (0x07) — Bidirectional

Exchanges capability information between host and device.

* **Device → Host (IN)**: the host queries the device's capabilities.
  The device responds with TLV-encoded data (up to 256 bytes).
* **Host → Device (OUT)**: the host sends its own capabilities as
  TLV-encoded data (up to 256 bytes).
* If the device does not support CAPABILITIES, it **should** stall the
  request. The host falls back to default values.
* CAPABILITIES **should** be exchanged directly before OPEN.
* If the CAPABILITIES exchange is not performed or fails, both sides
  **must** use the default capability values defined in section 4.

See section 4 for the TLV encoding and capability definitions.

3\. Data Framing
----------------

UPC uses **USB short-packet framing** to delimit application-level
messages on the bulk endpoints. There is no length header — message
boundaries are determined entirely by the USB short-packet mechanism.

### Background

USB bulk endpoints have a **maximum packet size (MPS)** determined by
the endpoint descriptor (typically 512 bytes for USB 2.0 High Speed,
1024 bytes for USB 3.x SuperSpeed). At the USB wire level, data is
transmitted in packets of up to MPS bytes. A packet shorter than MPS
(including a zero-length packet) is called a **short packet** and
signals the end of a transfer.

Application code typically interacts with USB through **transfers**,
where a single transfer may span many USB packets. The USB controller
handles packetization transparently:

* **OUT (send):** the controller splits the transfer data into
  MPS-sized USB packets automatically.
* **IN (receive):** the controller accumulates incoming USB packets into
  the transfer buffer until a short packet arrives or the buffer is
  full.

### Sending

To send an application packet of N bytes:

1. Submit the data as one or more USB OUT transfers. The USB controller
   splits each transfer into MPS-sized USB packets automatically.
   If the data is split across multiple transfers, **all transfers
   except the last must have a size that is a positive multiple of
   MPS**. Otherwise the USB controller would emit a short packet at
   the end of an intermediate transfer, which the receiver would
   incorrectly interpret as end-of-message.
2. **If** N is a non-zero multiple of MPS, send an additional
   **zero-length packet (ZLP)** as a separate transfer to signal the
   end of the message. Without the ZLP, the last USB packet on the wire
   is full-sized and the receiver cannot distinguish end-of-message from
   more data to follow.
3. **If** N is not a multiple of MPS, the last USB packet on the wire is
   naturally shorter than MPS. This short packet signals end-of-message
   and the sender **must not** send a ZLP — doing so would be
   interpreted as a separate, empty application packet.

**Empty application packets** (0 bytes) are sent as a single ZLP.

### Receiving

To receive an application packet:

1. Submit USB IN transfers with a buffer size that is a **positive
   multiple of MPS**. The larger the buffer, the fewer transfer
   completions are needed, improving throughput. The minimum buffer
   size is MPS.
2. Accumulate received data from completed transfers.
3. When a transfer completes with **fewer bytes than the buffer size**
   (including a ZLP completing with 0 bytes), the USB controller
   received a short packet — the message is complete. Return the
   accumulated data as one application packet.
4. When a transfer completes with **exactly the buffer size**, the
   buffer was filled without a short packet arriving — the message
   may not yet be complete. Submit another transfer and continue
   accumulating.

If the accumulated data exceeds the negotiated maximum application
packet size, the receiver **should** discard the data and report an
error.

### Implementation Note

The reference implementation uses a receive buffer size of
MPS × 128 bytes. This balances throughput against memory usage and
avoids issues with certain USB device controllers that corrupt
overly large transfers. Implementations are free to choose any
buffer size that is a positive multiple of MPS.

### Examples

Sending a 1025-byte application packet with MPS = 512:

| USB Packet | Size | Type                          |
|------------|------|-------------------------------|
| 1          | 512  | full packet                   |
| 2          | 512  | full packet                   |
| 3          | 1    | short packet (end of message) |

No ZLP is needed because the last packet is short.

Sending a 1024-byte application packet with MPS = 512:

| USB Packet | Size | Type                          |
|------------|------|-------------------------------|
| 1          | 512  | full packet                   |
| 2          | 512  | full packet                   |
| 3          | 0    | ZLP (end of message)          |

A ZLP is required because 1024 is a multiple of 512.

4\. Capabilities
----------------

Capabilities are encoded using a **TLV (tag-length-value)** format.

### TLV Encoding

Each entry consists of:

```
[tag: 1 byte] [length: 2 bytes, little-endian] [value: `length` bytes]
```

Entries are concatenated with no separators or padding. The entire
encoded payload must not exceed 256 bytes.

* Unknown tags **must** be silently skipped during decoding. This
  enables forward compatibility — new capabilities can be added in
  future versions without breaking existing implementations.
* Missing tags fall back to their default values.

### Device Capabilities

Sent by the device in response to a CAPABILITIES IN request.

| Tag    | Name              | Value Format          | Default      | Description |
|--------|-------------------|-----------------------|--------------|-------------|
| `0x01` | ping_timeout      | u32 LE (milliseconds) | 0 (disabled) | If non-zero, the device expects STATUS requests within this interval. If no STATUS request arrives in time, the device considers the host dead and closes the connection. 0 disables the timeout. Default: 10,000 ms (10 seconds) when supported. |
| `0x02` | status_supported  | u8 (0 or 1)          | 0 (false)    | Whether the device handles STATUS requests. The host must not send STATUS if this is 0. |
| `0x03` | max_size          | u64 LE                | 16,777,216   | Maximum application-level packet size the device can receive (in bytes). Default: 16 MiB. |

### Host Capabilities

Sent by the host via a CAPABILITIES OUT request.

| Tag    | Name              | Value Format          | Default      | Description |
|--------|-------------------|-----------------------|--------------|-------------|
| `0x03` | max_size          | u64 LE                | 16,777,216   | Maximum application-level packet size the host can receive (in bytes). Default: 16 MiB. |

The effective maximum size for each direction is the minimum of
the sender's local limit and the receiver's advertised `max_size`.

5\. Status Polling and Liveness
-------------------------------

STATUS requests serve dual purposes: the host can read device status
flags, and the device uses them as a liveness signal from the host.

### Host Polling

If the device advertises `status_supported = 1`, the host **should**
periodically send STATUS requests. The recommended polling interval is:

```
interval = min(host_ping_interval, device_ping_timeout / 2)
```

Default values:
* Host ping interval: 5 seconds
* Device ping timeout: 10 seconds
* Resulting default polling interval: 5 seconds

### Device Timeout

If the device has `ping_timeout` configured and a connection is open,
it tracks when the last STATUS request was received. If `ping_timeout`
elapses without a STATUS request, the device **should** consider the
host dead and close the connection.

### Status Response

The STATUS response is a variable-length byte array (0–8 bytes) where
each byte is a status flag. An empty response means everything is
normal. Unknown status bytes **should** be ignored by the host.

6\. Connection Lifecycle
------------------------

This section describes the full sequence of events for a typical
connection. Steps marked *(optional)* may be skipped.

### Establishing a Connection (host side)

```
 Host                                        Device
  │                                            │
  │──── PROBE (optional) ─────────────────────>│
  │<─── "UPC" ────────────────────────────────│
  │                                            │
  │──── INFO (optional) ──────────────────────>│
  │<─── info bytes ───────────────────────────│
  │                                            │
  │  [claim interface, clear halt on both EPs] │
  │                                            │
  │──── CLOSE (reset previous state) ────────>│
  │                                            │
  │  [flush IN endpoint buffers]               │
  │                                            │
  │──── CAPABILITIES IN (optional) ───────────>│
  │<─── device capabilities (TLV) ────────────│
  │                                            │
  │──── CAPABILITIES OUT (optional) ──────────>│
  │                                            │
  │──── OPEN (topic bytes) ───────────────────>│
  │                                            │
  │<═══ bulk data ════════════════════════════>│
  │     (framed with short packets / ZLPs)     │
  │                                            │
```

Detailed steps:

1. *(Optional)* Send **PROBE** to verify the interface speaks UPC.
2. *(Optional)* Send **INFO** to read device information.
3. Claim the USB interface. Clear halt condition on both bulk endpoints.
4. Send **CLOSE** to reset any state from a previous connection.
5. Flush any stale data from the IN endpoint by reading and discarding
   until a short timeout elapses with no data received (recommended:
   10 ms) or the endpoint stalls.
6. *(Optional)* Send **CAPABILITIES IN** to query device capabilities.
   On failure, use defaults.
7. *(Optional)* Send **CAPABILITIES OUT** to inform the device of host
   capabilities.
8. Send **OPEN** with the topic payload. The connection is now active.
9. Exchange data over the bulk endpoints using the framing described
   in section 3.

### Data Exchange

Once the connection is open, the host and device exchange application
packets over the bulk endpoints. Both directions are independent and
may be active simultaneously.

If status polling is enabled (device advertises `status_supported`),
the host periodically sends STATUS requests during data exchange.

### Closing a Connection

A connection can be closed cleanly in several ways:

**Full close (host-initiated):**
The host sends CLOSE. The device stops all bulk activity and resets.

**Half-close (host-initiated):**
* Host done sending → host sends **CLOSE_SEND** with the total byte
  count. Device drains remaining OUT endpoint data and considers the
  host → device direction closed.
* Host done receiving → host sends **CLOSE_RECV**. Device stops
  sending and halts the IN endpoint.

**Half-close (device-initiated):**
* Device done sending → device halts the IN endpoint. The host detects
  the stall and considers the device → host direction closed.
* Device done receiving → device halts the OUT endpoint and includes
  **RECV_CLOSED** in STATUS responses. The host detects the stall or
  the status flag and considers the host → device direction closed.

**Full close after half-close:**
When both directions are closed (regardless of which side initiated
each half-close), the host sends a final **CLOSE** to fully tear down
the connection.

**Endpoint halt persistence:**
Endpoint halt conditions set during half-close (e.g. device halting
the IN or OUT endpoint) persist until the next connection. The host
clears halt conditions on both endpoints as part of establishing a
new connection (step 3 above).

```
 Host                                        Device
  │                                            │
  │  [host drops sender]                       │
  │──── CLOSE_SEND (total bytes) ─────────────>│
  │                                  [drains OUT endpoint]
  │                                            │
  │                       [device drops sender]│
  │              [IN endpoint halted] <────────│
  │  [detects stall, recv direction closed]    │
  │                                            │
  │  [both directions closed]                  │
  │──── CLOSE ────────────────────────────────>│
  │                                            │
```

7\. Minimal Implementation Guide
---------------------------------

A minimal UPC device needs to support only:

1. **Descriptors**: one vendor-specific interface with two bulk
   endpoints (IN and OUT).
2. **OPEN** (0x01): accept the request, prepare to exchange data.
3. **CLOSE** (0x02): stop bulk activity, reset state.
4. **Data framing**: send and receive with short-packet / ZLP
   boundaries as described in section 3.

Everything else is optional and can be added incrementally:

* Add **PROBE** (0x00) for auto-discovery support.
* Add **INFO** (0x03) to provide a device description string.
* Add **CAPABILITIES** (0x07) to negotiate max sizes.
* Add **STATUS** (0x06) for liveness detection.
* Add **CLOSE_SEND** / **CLOSE_RECV** (0x04, 0x05) for half-close.
* Add **Microsoft OS descriptors** for driverless Windows support.