# C2 Proxy Tunnel — T1 firmware integration notes

This module ships as **NEW files only**. The integrator (you) wires it into
`main.cpp` + `config.h` at the end.

## New files

| File                       | Purpose                                                       |
| -------------------------- | ------------------------------------------------------------- |
| `c2_proxy_tunnel.h/.cpp`   | Reassembly + outbound queues for REQ/RSP chunked envelopes    |
| `ble_proxy_chars.h/.cpp`   | Adds ProxyTx (write) + ProxyRx (notify) chars to BLE service  |

The in-flight `ble_companion.{h,cpp}` and its handoff docs (`BLE_*.md`) are
**NOT** modified.

## New MSG_TYPEs (add to `config.h`)

```cpp
#define MSG_TYPE_C2_PROXY_REQ  0x24   // T1->T2 (and BLE write payload tag)
#define MSG_TYPE_C2_PROXY_RSP  0x25   // T2->T1
```

`c2_proxy_tunnel.h` defines defensive fallbacks for both, so the new files
compile even before you bump `config.h` — but the canonical source of truth
must be `config.h`.

## Wire format

Both REQ and RSP reuse the EXACT 24-byte header pattern from
`MSG_TYPE_PAYLOAD_CHUNK` (0x14, see `payload_buffer.h`):

| Offset | Size | Field         | Notes                                                       |
| ------ | ---- | ------------- | ----------------------------------------------------------- |
| 0      | 1    | msg_type      | 0x24 (REQ) or 0x25 (RSP)                                    |
| 1..4   | 4    | request_id    | uint32 LE (same field as payload_id)                        |
| 5..6   | 2    | total_chunks  | uint16 LE                                                   |
| 7..8   | 2    | chunk_idx     | uint16 LE, 0-based                                          |
| 9..12  | 4    | total_size    | uint32 LE, gzipped-envelope size                            |
| 13..16 | 4    | chunk_crc32   | uint32 LE; final chunk holds full-payload CRC               |
| 17..23 | 7    | reserved      | zero                                                        |
| 24..   | ≤200 | chunk data    | gzip-compressed envelope fragment                           |

The envelope itself (after gzip-decompress) is a JSON object:

```json
{
  "request_id": 12345,
  "method":     "POST",
  "path":       "/api/sensors/Eric01/cmd",
  "headers":    { "Authorization": "Bearer …", "Content-Type": "application/json" },
  "body":       "{\"cmd\":\"REBOOT\"}"
}
```

## `main.cpp` wiring (DO YOURSELF — leave this section as-is, do not modify
the in-flight BLE-companion's files)

### 1. Init

In `setup()`, after `ble_companion_init()` and `payload_buffer_init()`:

```cpp
#include "c2_proxy_tunnel.h"
#include "ble_proxy_chars.h"

c2_proxy_tunnel_init();
// ble_companion still owns the BLEService — once it has registered all of
// its existing characteristics, ask it for the service handle and pass it:
ble_proxy_register(ble_companion_get_service());
```

> **Hook required from companion agent.** The companion agent ships
> `setupGattService()` inside `ble_companion.cpp`. You need a public getter
> `BLEService* ble_companion_get_service()` (or equivalent). If the companion
> agent has already exposed one, use it. If not, add an accessor in your own
> integration patch — do **not** modify `ble_companion.cpp` here, take a
> minimal external diff in your branch.

### 2. LoRa TX hook

The proxy module declares `c2_proxy_lora_tx(buf, len)` weakly. Override it
strongly in `main.cpp`:

```cpp
extern "C" bool c2_proxy_lora_tx(const uint8_t* buf, size_t len) {
    return radio_scheduler_enqueue_tx(buf, len);  // or RadioLib direct
}
```

### 3. LoRa RX dispatch

In your existing `loop()` LoRa-RX dispatch (the same place that recognises
`MSG_TYPE_PAYLOAD_CHUNK = 0x14`), add:

```cpp
case MSG_TYPE_C2_PROXY_REQ:
    c2_proxy_handle_lora_req(packet, len);
    break;
case MSG_TYPE_C2_PROXY_RSP:
    c2_proxy_handle_lora_rsp(packet, len);
    break;
```

### 4. Tick

In your existing 1 Hz main loop:

```cpp
c2_proxy_tunnel_tick();
```

## Master-of-mesh

The phone targets neighbour sensors purely by URL — e.g.
`POST /api/sensors/Eric01/cmd`. T1 doesn't care; T2 routes via OpenForge C2,
which decides whether to bounce another LoRa command back to the targeted
sensor or just to acknowledge. From the bonded T1's perspective every
request looks identical and is forwarded unmodified.

## Auth

Every BLE-side write requires the existing `ble_companion` auth window to be
open (8 h after successful HMAC handshake). The proxy module defers to
`ble_companion_is_authenticated()` for that gate. If the companion agent
exposes a different name, override the weak `c2_proxy_is_authenticated()` in
`main.cpp`.

## What's NOT done here (intentional)

- `main.cpp` is **not** modified — you wire the four hooks above.
- `config.h` is **not** modified — you add the two `#define`s above.
- `ble_companion.{h,cpp}` is **not** modified — `ble_proxy_chars` only
  adds characteristics to the service the companion agent already created.
- T2's `lora_receiver.py` is **not** modified — the T2-side mirror agent
  added a HINTS file (`HINTS_FOR_T2_PROXY_RELAY.md`) you'll wire there.

## Build flag

This module is compiled when `PDL_BLE_COMPANION_ENABLED` is defined (the
same `_interop` env that compiles `ble_companion`). The standard env is
unaffected.
