# Signaling Messages

All signaling is serialized as lowercase snake case JSON with a `type` discriminator. Every payload must validate before it reaches the transport layer.

## Offer

```json
{
  "type": "offer",
  "sdp": "v=0..."
}
```

Rules: `sdp` must be a non-empty UTF-8 string.

## Answer

Identical structure to `Offer` with `type: "answer"`.

## IceCandidate

```json
{
  "type": "ice_candidate",
  "candidate": "candidate:0 1 UDP 2122260223 192.0.2.1 3478 typ host",
  "sdp_mid": "audio",
  "sdp_mline_index": 0
}
```

Rules:
- `candidate` is required and must be non-empty.
- `sdp_mid` is optional but, when present, must be a string.
- `sdp_mline_index` is optional unsigned integer.

## Hangup

```json
{
  "type": "hangup",
  "reason": "remote peer closed"
}
```

`reason` is optional; empty strings are stripped.

## Deterministic helpers

- `encode_signaling` returns a canonical JSON string.
- `decode_signaling` returns a strongly typed `SignalingMessage` or one of `JsonError`, `InvalidSdp`, or `InvalidCandidate`.
