# Envelope Format

> Byte-level layout of an RPC frame on the wire.

Every RPC request, response, audio frame, and event uses the same envelope.

## Frame layout

```
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------------------------------------------------------+
|                       length (u32 LE)                         |
+---------------------------------------------------------------+
|                      method_id (u32 LE)                       |
+---------------------------------------------------------------+
|                                                               |
.                  serde envelope body (length-4 bytes)         .
|                                                               |
+---------------------------------------------------------------+
```

| Field        | Width   | Meaning                                                                            |
| ------------ | ------- | ---------------------------------------------------------------------------------- |
| `length`     | 4 bytes | Little-endian byte count of `method_id` + body. **Excludes itself.** Wire total = `4 + length`. |
| `method_id`  | 4 bytes | Little-endian. One of the [Method IDs](/rpc/method-ids).                          |
| `body`       | `length - 4` | A serde envelope (see below).                                                  |

> **WARNING:**
> `length` does **not** include the four bytes of the length field itself. A
> zero-arg request (`Empty`) has `length = 4 + sizeof(serde-envelope-header)`,
> not zero.

## Serde envelope

The body of every frame is a serde envelope:

```
+----+----+--------------------+-----------------------+
| u8 | u8 | i32 LE             | fields...             |
+----+----+--------------------+-----------------------+
  v    cv   payload_size            (payload_size B)
```

| Field          | Width   | Meaning                                                  |
| -------------- | ------- | -------------------------------------------------------- |
| `version`      | 1 byte  | Schema version of the producer.                          |
| `compat_version` | 1 byte | Oldest version the producer knows it is compatible with. |
| `payload_size` | 4 bytes (signed LE) | Byte count of the fields that follow. Used to skip past unknown trailing fields. |
| `fields`       | variable | Each field encoded inline in declaration order.        |

A receiver that doesn't recognize a tail of newer fields simply skips
`(payload_size - bytes_consumed)` bytes and moves on. This is how forward
compatibility works.

## Field encoding

Primitives are little-endian, two's-complement, no padding:

| Logical type   | Wire encoding                                              |
| -------------- | ---------------------------------------------------------- |
| `bool`         | `u8` (0 or 1)                                              |
| `int32` / `uint32` | 4 bytes LE                                             |
| `int64` / `uint64` | 8 bytes LE                                             |
| `double`       | 8 bytes IEEE 754 LE                                        |
| `enum`         | encoded as `int32` LE                                      |
| `string`       | `i32 LE length` followed by `length` bytes of UTF-8        |
| `vector<T>`    | `i32 LE length` followed by `length` elements              |
| nested struct  | another full serde envelope (recursive)                    |

> **NOTE:**
> Strings are length-prefixed, not null-terminated. Binary payloads (e.g.
> µ-law audio) are also encoded with the `string` shape — the bytes are
> treated as opaque.

## Worked example: `BargeRequest`

Schema:

```cpp
struct BargeRequest {
    string call_sid;
};
```

Serialize `call_sid = "abc"` with `method_id = 3854301714` (Barge):

```
length        = 14   (4 method_id + 10 envelope body)
method_id     = 3854301714
envelope:
  version     = 0
  compat_ver  = 0
  payload_sz  = 7    (4 length-prefix + 3 string bytes)
  call_sid    = "abc"  → 03 00 00 00 61 62 63
```

On the wire (hex, spaces for clarity):

```
0e 00 00 00     length = 14
12 64 b0 e5     method_id = 3854301714
00              version
00              compat_version
07 00 00 00     payload_size = 7
03 00 00 00     string length = 3
61 62 63        "abc"
```

Total bytes written: **18** (`4 + length`).

## Reading a frame

```python
import struct

def read_frame(stream):
    header = stream.recv_exact(4)
    length = struct.unpack("<I", header)[0]
    body = stream.recv_exact(length)
    method_id = struct.unpack("<I", body[:4])[0]
    payload = body[4:]               # serde envelope
    return method_id, payload
```

The serde envelope can then be parsed structurally per the schema for that
`method_id`.
