# TraxProbe Transports

Communication interface implementations for different physical channels.

## Available Transports

| Transport | Description | Use Case |
|-----------|-------------|----------|
| `RTT` | SEGGER RTT via J-Link | High-speed debug probe |
| `UART` | Serial UART (user-provided HW) | PC connection, wireless modules |
| `TCP` | TCP/IP sockets (user-provided HW) | MCU with Ethernet/WiFi |
| `RingBuffer` | Memory-only buffer | DMA transfer, polling (planned) |
| `USB_CDC` | USB CDC device | USB serial (planned) |

## Structure

Each transport follows the same structure:

```
transports/<TransportName>/
├── config/
│   └── trax_transport_config.h      # Transport-specific configuration
├── trax_transport_<name>.c          # Transport implementation (defines trax_transport_*)
└── README.md                        # Transport documentation
```

## Selecting a transport (compile time)

The active transport is chosen by `TRAX_CFG_TRANSPORT` in `trax_config.h`:

- `TRAX_TRANSPORT_RTT` (default) — built-in SEGGER RTT, used automatically.
- `TRAX_TRANSPORT_CUSTOM` — RTT is compiled out; you provide the transport by
  defining the `trax_transport_*()` functions in your application.

There is no dispatcher and no shared fallback file: the core calls the
`trax_transport_*()` functions directly and the selected backend defines them.
For RTT, add the `RTT/` sources (they define the functions). For custom, define
the functions yourself in any `.c` in your build — see [`Custom/`](Custom/README.md)
for a guide and a copy-paste skeleton.

## Writing a custom transport

> Start in [`Custom/`](Custom/README.md): it has a step-by-step guide and a
> `trax_transport_custom.c.example` skeleton you can copy, rename to `.c`, and
> fill in. The prototypes themselves are declared in
> [`inc/trax_transport.h`](../inc/trax_transport.h).

1. Set `#define TRAX_CFG_TRANSPORT TRAX_TRANSPORT_CUSTOM` in `trax_config.h`.
2. Define **all four** functions in any `.c` in your build (no template to
   copy, no ops struct, no `register()` call — the linker binds them by name):

   ```c
   #include <trax_transport.h>

   int    trax_transport_init(void)                     { return 0; }
   size_t trax_transport_write(const void *p, size_t n) { /* all-or-nothing */ }
   size_t trax_transport_read(void *p, size_t n)        { /* non-blocking */ }
   void   trax_transport_clear_tx(void)                 { /* drop stale TX */ }
   ```

   - `trax_transport_init()`  — one-time arm of the binding (typically the RX
     path); `return 0` if there is nothing to do
   - `trax_transport_write()` — data path (up-channel, all-or-nothing:
     return `n` with every byte committed, or `0` with nothing accepted —
     `0` is retryable backpressure, a partial count fatally stops the stream)
   - `trax_transport_read()`  — data path (down-channel, non-blocking). The
     core drains by calling it until it returns less than requested, so a
     short read means "nothing left". It runs on every pass including idle
     ones, and per-poll housekeeping (e.g. a non-blocking `accept()`) goes here
   - `trax_transport_clear_tx()` — session restart: empty any software TX
     ring (leave RX alone). A blocking UART that cannot recall bytes already
     on the wire implements this as an empty body

   (No `writev`: the core drains the ring one contiguous, size-capped batch
   per pass and sends it with a single all-or-nothing `write()`. No has-data
   hook either: `read()` alone drives the down-channel.)
3. **All four are required.** The core references each one, so leaving any
   undefined fails the link with `undefined reference to 'trax_transport_…'` —
   never a silent fake. `init()` is often a one-line `return 0;` but stays
   mandatory: it is the only place the RX path gets armed, so a silent default
   would kill the down-channel instead of failing the build. `clear_tx()` is
   the same: session restart always calls it, so an empty body is the
   fallback you write, not a config-macro no-op.
4. Optionally opt in to the rest from `trax_config.h`, naming your own
   functions. Every one of these defaults to the behaviour a transport had
   before it existed, so nothing breaks by leaving them out:

   ```c
   #define TRAX_CFG_TRANSPORT_TX_FREE     my_tx_free       /* recommended */
   #define TRAX_CFG_TRANSPORT_BUF_SIZE    sizeof(my_ring)  /* diagnostics  */
   #define TRAX_CFG_TRANSPORT_BYTES_USED  my_tx_pending    /* diagnostics  */
   ```

   `TX_FREE` is the one that matters for throughput: it reports the bytes a
   `write()` is guaranteed to accept right now, and the core sizes each drain
   batch to it BEFORE collecting frames so writes never have to reject a
   batch. Without it the core assumes free space is unknown and falls back to
   try-and-retry, which also makes `TRAX_CFG_TX_BATCH_MAX_BYTES` load-bearing
   — keep that cap below your TX buffer's real capacity. TX occupancy for the
   diagnostics gauge is derived as `BUF_SIZE − TX_FREE`, so `BYTES_USED` is
   only worth defining when that subtraction does not match your TX path.

> **Switching back to RTT is safe.** The built-in RTT backend defines
> `trax_transport_rtt_*()` (distinct names) and the core reaches the active
> backend through the compile-time `TRAX_TRANSPORT_*` selector macros in
> `inc/trax_transport.h`. So you can leave your custom-transport source in the
> build and just flip `TRAX_CFG_TRANSPORT` back to `TRAX_TRANSPORT_RTT` without a
> `multiple definition of 'trax_transport_write'` error — in RTT mode your
> `trax_transport_*()` functions are simply unreferenced. (This only
> de-conflicts the transport functions; any ISR handlers / other symbols in
> that file are still compiled, so guard the file if those clash.)

See each function's `@brief` in `inc/trax_transport.h` for per-function
guidance and examples, and `.cursor/rules/trax-transport-transmission.mdc` for
the all-or-nothing write rule.
