Files
irtt-rs/docs/protocol/IRTT_CLIENT_PROTOCOL_SPEC.md
T

1451 lines
57 KiB
Markdown

# IRTT-Compatible Client Protocol Specification
**Version:** 1.1-verified
**Date:** 2026-04-28
**Verified against:** irtt 0.9.1, protocol version 1, macOS Darwin 25.3.0 arm64
---
## 1. Status
This document describes the externally observable behavior required for an
independent client implementation compatible with existing IRTT (Isochronous
Round-Trip Tester) servers.
This document does **not** describe upstream source structure, internal
algorithms, or implementation organization. It is intended for a clean-room
implementation that has never seen the upstream source code.
The key words "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT",
"RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted
as interoperability requirements, consistent with RFC 2119.
---
## 2. Scope
- **Client-only implementation.** Server implementation is out of scope unless
server behavior is needed to understand client interoperability.
- **Compatible with existing IRTT servers** running protocol version 1.
- This specification describes protocol and measurement behavior; CLI design
is not prescribed.
- Server implementation is out of scope.
---
## 3. Terminology
| Term | Definition |
|------|-----------|
| **Client** | The endpoint that initiates a session, sends test packets, and receives replies. |
| **Server** | The endpoint that accepts sessions and echoes test packets back to the client. |
| **Session** | A stateful association between a client and server, identified by a connection token. |
| **Open / Negotiation phase** | The initial handshake in which the client proposes test parameters and the server responds with (possibly restricted) parameters and a connection token. |
| **Test packet** | A UDP datagram sent by the client during the active test phase. Sometimes called an "echo request." |
| **Reply packet** | A UDP datagram sent by the server in response to a test packet. Sometimes called an "echo reply." |
| **Close / Finalization phase** | The exchange in which the client notifies the server that the session is ending. |
| **Connection token** | A 64-bit value assigned by the server during negotiation, used to identify the session in all subsequent packets. |
| **Round-trip time (RTT)** | The time between sending a test packet and receiving its reply, optionally minus server processing time. |
| **Send interval** | The fixed time period between successive test packet transmissions. |
| **Sequence number** | A 32-bit unsigned integer, starting at 0, assigned to each test packet in order of transmission. |
| **Timeout** | The duration the client waits for a server reply during the open phase before retransmitting. |
| **Packet loss** | A test packet for which no reply was received. |
| **Duplicate** | A reply received with a sequence number for which a reply was already recorded. |
| **Late packet** | A reply received with a sequence number lower than the most recently received sequence number (indicates out-of-order delivery). |
| **IPDV (Instantaneous Packet Delay Variation)** | The difference in delay between two successive successfully returned replies. Commonly called "jitter." |
| **Server processing delay** | The time the server spent between receiving a request and sending its reply, measured by server timestamps. |
| **Received count** | A running count of all packets the server has received for this session. |
| **Received window** | A 64-bit bitmap indicating the receipt status of the most recent 64 packets at the server. |
| **Wall clock** | System real-time clock (nanoseconds since Unix epoch). Subject to NTP adjustments. |
| **Monotonic clock** | A clock that only moves forward and is not subject to system time adjustments. Used for duration calculations. |
| **HMAC** | Hash-based Message Authentication Code (MD5), used for packet authentication. |
| **DSCP** | Differentiated Services Code Point, set in the IP header TOS/Traffic Class field. |
| **Drain period** | The period after the last test packet is sent during which the client waits for outstanding replies. |
---
## 4. Conformance
A conforming client MUST implement:
1. Session lifecycle: open, test, close.
2. The binary wire format defined in this document.
3. Parameter negotiation.
4. Connection token handling.
5. Sequence number assignment and reply matching.
6. RTT measurement.
7. Packet loss detection.
8. Tolerance for UDP reordering and duplication.
A conforming client SHOULD implement:
1. HMAC authentication.
2. Server timestamp handling (one-way delay, IPDV).
3. Received stats (count and window) processing.
4. Configurable DSCP.
Implementations are not required to use any specific internal architecture,
data structure, function layout, naming convention, concurrency model, or
source organization. Only the externally observable behavior described in
this document is normative.
---
## 5. Transport Requirements
### 5.1 Transport Protocol
IRTT uses **UDP** exclusively. There is no TCP fallback.
### 5.2 Default Port
The default server port is **2112**. The default client local port is
ephemeral (OS-assigned, port 0).
### 5.3 IPv4 and IPv6
A conforming client MUST support IPv4. IPv6 support is RECOMMENDED. When the
user does not specify an IP version preference, the client SHOULD attempt
address resolution for both IPv4 and IPv6, and use whichever resolves
successfully. If both resolve, the selection behavior is
implementation-defined.
The client MUST use a single connected UDP socket for the entire session. The
client calls the equivalent of `connect()` on the socket, binding it to the
resolved server address before the open phase.
### 5.4 Socket Options
- **DSCP/TOS:** A client SHOULD support setting the DSCP value on outgoing
packets. For IPv4, this is the TOS field. For IPv6, this is the Traffic
Class field. The DSCP value is negotiated as a parameter; if the server
disallows DSCP, the negotiated value will be 0.
- **TTL/Hop Limit:** A client MAY support setting the IP TTL (IPv4) or Hop
Limit (IPv6).
- **Don't Fragment (DF):** A client MAY support setting the DF bit.
- **ECN:** ECN bit handling is OPTIONAL and experimental.
### 5.5 Connectionless Behavior
Although the client uses a connected UDP socket, the protocol is
connectionless at the network layer. The client MUST tolerate:
- Packet reordering.
- Packet duplication.
- Packet loss.
- Receiving unrelated UDP traffic on the socket (which MUST be discarded
based on validation rules).
### 5.6 MTU Considerations
Common maximum unfragmented UDP payload sizes for a 1500-byte MTU:
- IPv4: 1472 bytes (1500 - 20 IPv4 header - 8 UDP header)
- IPv6: 1452 bytes (1500 - 40 IPv6 header - 8 UDP header)
The client does not need to perform path MTU discovery. Packet length is a
user-configured parameter.
---
## 6. Session Lifecycle
A client session progresses through the following externally observable states:
```
Resolving ──► Opening ──► Active Test ──► Draining ──► Closing ──► Completed
│ │
▼ ▼
Failed Completed
```
### 6.1 Resolving
- **Entry:** Client is given a server address (hostname or IP) and port.
- **Behavior:** The client resolves the address to a UDP endpoint. The
default port (2112) MUST be appended if no port is specified.
- **Exit:** Successful resolution transitions to Opening. Failure transitions
to Failed.
### 6.2 Opening / Negotiating
- **Entry:** The client has a resolved server address and a connected UDP socket.
- **Behavior:**
1. The client constructs an open request packet (see Section 8.2) containing
the proposed test parameters serialized in the payload.
2. The client sends the open request and waits for a reply.
3. If no reply is received within the current timeout, the client
retransmits the open request with the next timeout value from its
timeout list.
4. The default timeout sequence is: 1s, 2s, 4s, 8s (total maximum wait:
15s, maximum 4 packets). The minimum timeout value MUST be at least
200ms.
5. If all timeouts are exhausted with no reply, the session transitions to
Failed.
- **Valid server responses:**
- An open reply with a non-zero connection token: session parameters are
extracted from the reply payload. The client stores the connection token.
Transitions to Active Test (or Completed if the no-test flag was set).
- An open reply with the close flag set, **when the client did NOT set
Close in its request**: the server rejected the session (e.g., protocol
version mismatch). Transition to Failed.
- An open reply with the close flag set, **when the client DID set Close
in its request** (no-test mode): the server acknowledged the open+close.
The connection token is zero. Transition to Completed.
[**Verified 2026-04-28** — see BLACKBOX_VERIFICATION_REPORT.md Finding D.]
- **Parameter negotiation:** The server MAY restrict parameters. The client
MUST compare returned parameters against its requested parameters. A
conforming client MUST, by default, reject the session if parameters were
restricted (transition to Failed with an appropriate error). A "loose" mode
MAY be provided that accepts server restrictions with a warning.
The client MUST reject (as an error) the following server parameter changes:
- Duration increased beyond what was requested.
- Length increased beyond what was requested.
- Interval reduced below a safety floor (see Section 19.9 for verification).
- **Protocol version check:** The client MUST verify that the server's
protocol version matches exactly. Protocol version 1 is the current
version. Mismatch MUST cause the session to fail.
### 6.3 Active Test
- **Entry:** Open reply received and parameters accepted.
- **Behavior:**
1. The client sends test packets at fixed intervals for the configured
duration.
2. The client MUST be able to receive reply packets while continuing
to send test packets (i.e., sending and receiving occur concurrently).
3. Test packets are numbered with sequence numbers starting at 0.
4. The test duration is **exclusive**: the client MUST NOT send a packet at
or after the end time. The end time is calculated as start time + duration.
The next scheduled send time is computed, and if it would be at or past
the end time, sending stops.
5. The expected number of packets for a given duration `d` and interval `i`
is: `ceil(d / i)`. [**Verified 2026-04-28** by black-box testing with
irtt 0.9.1. See BLACKBOX_VERIFICATION_REPORT.md Finding A.]
- **Exit:** All scheduled packets have been sent. Transition to Draining.
### 6.4 Draining Replies
- **Entry:** All test packets have been sent.
- **Behavior:** The client waits for outstanding replies. The wait duration
is configurable:
- Default: 3 times the maximum observed RTT, falling back to 4 seconds if
no RTT has been measured.
- Alternatives: a factor of mean RTT, or a fixed duration.
- If all replies have already been received, no wait is needed.
- **Exit:** Wait period expires, or all replies received. Transition to Closing.
### 6.5 Closing
- **Entry:** Test and drain are complete.
- **Behavior:** The client sends a single close packet (see Section 8.4) to
the server. The close packet includes the connection token and the close
flag. The client does NOT wait for a close acknowledgement (this is a
known limitation of protocol version 1).
- **Exit:** Close packet sent. Transition to Completed.
### 6.6 Completed
- **Entry:** Session has finished normally.
- **Behavior:** Results are computed and made available.
### 6.7 Failed
- **Entry:** An unrecoverable error occurred.
- **Behavior:** Partial results MAY be available if the failure occurred
during or after the Active Test phase.
---
## 7. Packet Format Overview
All IRTT packets share a common header structure. The presence or absence of
optional fields is determined by context (packet type and negotiated
parameters).
| Packet Type | Direction | When Sent | Required | Purpose |
|------------|-----------|-----------|----------|---------|
| Open Request | Client → Server | Opening phase | Yes | Propose session parameters |
| Open Reply | Server → Client | Opening phase | Yes | Return connection token and (possibly restricted) parameters |
| Echo Request (Test Packet) | Client → Server | Active test | Yes | Carry test payload with sequence number |
| Echo Reply | Server → Client | Active test | Yes | Return test payload with timestamps and stats |
| Close Request | Client → Server | Closing phase | Yes | Signal session end |
| Close Reply | Server → Client | Closing phase | No | Acknowledge close (server MAY send this) |
---
## 8. Binary Wire Format
### 8.1 General Header Structure
All multi-byte integer fields use **little-endian** byte order.
The packet format uses a flexible field layout. Fields are placed sequentially
in the packet buffer. A field is either present at its full capacity or absent
(zero length). The set of present fields is determined by the packet type and
negotiated parameters.
#### 8.1.1 Fixed Header Fields (always present)
| Offset | Size (bytes) | Field | Encoding | Description |
|--------|-------------|-------|----------|-------------|
| 0 | 3 | Magic | Raw bytes | Fixed value: `0x14 0xA7 0x5B` |
| 3 | 1 | Flags | Unsigned byte | Bit flags (see below) |
**Total minimum header: 4 bytes.**
#### 8.1.2 Flag Bits
| Bit | Mask | Name | Meaning |
|-----|------|------|---------|
| 0 | 0x01 | Open | Set on open request and open reply packets |
| 1 | 0x02 | Reply | Set on all packets from server to client; clear on all packets from client to server |
| 2 | 0x04 | Close | Set on close request and close reply packets |
| 3 | 0x08 | HMAC | Set if an HMAC field is present in the packet |
Bits 4-7 are reserved and MUST be zero. A client MUST reject packets where
reserved flag bits are set.
#### 8.1.3 Optional Fields
After the fixed header, optional fields appear **in the following order** when
present. A field is either present at full size or entirely absent. There are
no length-prefix or presence-indicator bytes; the receiver knows which fields
are present based on the packet type and negotiated session parameters.
**Verification note:** The field ordering below is critical for
interoperability. [**Verified 2026-04-28** by packet capture with multiple
field combinations — see BLACKBOX_VERIFICATION_REPORT.md Section 19.11.]
| Order | Field | Size (bytes) | Encoding | When Present |
|-------|-------|-------------|----------|-------------|
| 1 | HMAC | 16 | MD5 HMAC | When HMAC flag (0x08) is set |
| 2 | Connection Token | 8 | Little-endian uint64 | All packets except initial open request sent from client before token is known. Present in open reply and all subsequent packets. |
| 3 | Sequence Number | 4 | Little-endian uint32 | Echo request and echo reply packets |
| 4 | Received Count | 4 | Little-endian uint32 | Echo reply, when "count" stats negotiated |
| 5 | Received Window | 8 | Little-endian uint64 | Echo reply, when "window" stats negotiated |
| 6 | Receive Wall Timestamp | 8 | Little-endian int64 (ns since Unix epoch) | Echo reply, when receive timestamps negotiated with wall clock |
| 7 | Receive Mono Timestamp | 8 | Little-endian int64 (ns, duration) | Echo reply, when receive timestamps negotiated with monotonic clock |
| 8 | Midpoint Wall Timestamp | 8 | Little-endian int64 (ns since Unix epoch) | Echo reply, when midpoint timestamp negotiated with wall clock |
| 9 | Midpoint Mono Timestamp | 8 | Little-endian int64 (ns, duration) | Echo reply, when midpoint timestamp negotiated with monotonic clock |
| 10 | Send Wall Timestamp | 8 | Little-endian int64 (ns since Unix epoch) | Echo reply, when send timestamps negotiated with wall clock |
| 11 | Send Mono Timestamp | 8 | Little-endian int64 (ns, duration) | Echo reply, when send timestamps negotiated with monotonic clock |
**Important:** Midpoint timestamps are **mutually exclusive** with
receive/send timestamps. If a midpoint timestamp is present, receive and send
timestamp fields MUST NOT be present, and vice versa. A packet containing
both midpoint and receive/send timestamps is malformed.
When both receive and send timestamps are present, they MUST use the same
clock mode (both wall, both monotonic, or both wall+monotonic).
#### 8.1.4 Payload
Everything after the last present header field is **payload**. The total
packet length is the negotiated packet length. If the negotiated length is
larger than the header, the remaining bytes are payload. If the negotiated
length is 0 or unspecified, the packet length equals the header length (no
payload).
The client fills the payload with zeros, random data, or a repeating
pattern, as configured. The server fills reply payloads according to its
configuration and the negotiated server fill parameter.
### 8.2 Open Request Packet
**Direction:** Client → Server
**Flags:** Open (0x01) set. Reply (0x02) clear. HMAC (0x08) set if HMAC key
configured.
**Header fields present:**
- Magic (3 bytes)
- Flags (1 byte)
- HMAC (16 bytes) — if HMAC configured
The connection token field is NOT present in the open request (the token has
not yet been assigned). [**Verified 2026-04-28** by packet capture — see
BLACKBOX_VERIFICATION_REPORT.md Section 19.1.]
**Payload:** Serialized parameters (see Section 8.6).
If the client wants to open and immediately close (no-test mode), both the
Open (0x01) and Close (0x04) flags are set.
### 8.3 Open Reply Packet
**Direction:** Server → Client
**Flags:** Open (0x01) set. Reply (0x02) set. Close (0x04) set if the server
is rejecting the connection. HMAC (0x08) set if applicable.
**Header fields present:**
- Magic (3 bytes)
- Flags (1 byte)
- HMAC (16 bytes) — if applicable
- Connection Token (8 bytes)
**Payload:** Serialized parameters (see Section 8.6), reflecting any server
restrictions.
**Validation:**
- If the Close flag is NOT set, the connection token MUST be non-zero. A
zero token without the close flag is an error.
- If the Close flag IS set **and the client did NOT set Close in its open
request**, the server is rejecting the session (transition to Failed).
- If the Close flag IS set **and the client DID set Close in its open
request** (no-test mode), the server acknowledged the open+close. The
connection token is zero. Transition to Completed.
[**Verified 2026-04-28** — see BLACKBOX_VERIFICATION_REPORT.md Finding D.]
### 8.4 Echo Request Packet (Test Packet)
**Direction:** Client → Server
**Flags:** Reply (0x02) clear. HMAC (0x08) if applicable. Open (0x01) and
Close (0x04) clear.
**Header fields present:**
- Magic (3 bytes)
- Flags (1 byte)
- HMAC (16 bytes) — if applicable
- Connection Token (8 bytes)
- Sequence Number (4 bytes)
- Received Count (4 bytes, zeroed) — if "count" stats negotiated
- Received Window (8 bytes, zeroed) — if "window" stats negotiated
- Timestamp fields (zeroed) — according to negotiated stamp_at and clock
The echo request includes zeroed placeholder fields for received stats and
timestamps so that the total packet reaches the negotiated length.
**Payload:** Filled according to client configuration (zeros, random, or
pattern).
**Total packet length:** The negotiated `length` parameter. If the header
(including zeroed optional fields) is larger than the negotiated length, the
actual packet length is the header length. The final negotiated length
reflects this minimum.
### 8.5 Echo Reply Packet
**Direction:** Server → Client
**Flags:** Reply (0x02) set. HMAC (0x08) if applicable. Close (0x04) MAY be
set (see Section 19.3 — not yet verified by black-box testing).
**Header fields present:**
- Magic (3 bytes)
- Flags (1 byte)
- HMAC (16 bytes) — if applicable
- Connection Token (8 bytes)
- Sequence Number (4 bytes)
- Received Count (4 bytes) — if negotiated, contains server's running count
- Received Window (8 bytes) — if negotiated, contains 64-bit received bitmap
- Timestamp fields — according to negotiated stamp_at and clock settings
**Payload:** Server-filled according to negotiation.
### 8.5.1 Close Request Packet
**Direction:** Client → Server
**Flags:** Close (0x04) set. Reply (0x02) clear. HMAC (0x08) if applicable.
**Header fields present:**
- Magic (3 bytes)
- Flags (1 byte)
- HMAC (16 bytes) — if applicable
- Connection Token (8 bytes)
No sequence number, no payload.
### 8.6 Parameter Serialization Format
Parameters are serialized as a sequence of tag-value pairs in the packet
payload during the open request and open reply.
Each parameter is encoded as:
1. **Tag:** Unsigned varint (LEB128 encoding).
2. **Value:** Signed varint (zigzag + LEB128 encoding), EXCEPT for
string-typed values.
For string-typed values, the value is:
1. Length as unsigned varint.
2. Raw UTF-8 bytes.
**Varint encoding:** This uses the same variable-length integer encoding as
Google Protocol Buffers:
- **Unsigned varint (uvarint):** Standard unsigned LEB128. Each byte uses
the high bit as a continuation flag. The remaining 7 bits contribute to
the value, least significant group first.
- **Signed varint (varint):** Uses zigzag encoding: `(v << 1) ^ (v >> 63)`
for encoding, decoded as `(uv >> 1) ^ -(uv & 1)`. This maps signed
integers to unsigned integers so that small-magnitude values (positive and
negative) have compact encodings.
See Section 19.4 for cross-language verification notes.
**Parameter tags:**
| Tag | Name | Value Type | Unit / Encoding | Description |
|-----|------|-----------|-----------------|-------------|
| 1 | ProtocolVersion | Signed varint | Integer | Protocol version (currently 1) |
| 2 | Duration | Signed varint | Nanoseconds | Test duration |
| 3 | Interval | Signed varint | Nanoseconds | Send interval |
| 4 | Length | Signed varint | Bytes | Packet length |
| 5 | ReceivedStats | Signed varint | Enum (see below) | Server received packet statistics mode |
| 6 | StampAt | Signed varint | Enum (see below) | Server timestamp mode |
| 7 | Clock | Signed varint | Enum (see below) | Clock selection for timestamps |
| 8 | DSCP | Signed varint | Integer (TOS byte value) | IP TOS/Traffic Class byte value (see Section 10.8) |
| 9 | ServerFill | String (length-prefixed) | UTF-8, max 32 bytes | Server payload fill mode |
Parameters with a zero value MAY be omitted from serialization (they will
default to zero on the receiving end).
Unknown parameter tags MUST be silently ignored by the receiver.
The maximum serialized parameter buffer size is believed to be 128 bytes
(see Section 19.10 for verification).
**ReceivedStats enum values:**
| Value | Name | Meaning |
|-------|------|---------|
| 0 | None | No received packet statistics |
| 1 | Count | Include received packet count |
| 2 | Window | Include 64-bit received window |
| 3 | Both | Include both count and window |
**StampAt enum values:**
| Value | Name | Meaning |
|-------|------|---------|
| 0 | None | No server timestamps |
| 1 | Send | Timestamp at server send |
| 2 | Receive | Timestamp at server receive |
| 3 | Both | Timestamps at both server receive and send |
| 4 | Midpoint | Midpoint timestamp (average of receive and send) |
**Clock enum values:**
| Value | Name | Meaning |
|-------|------|---------|
| 1 | Wall | Wall clock only |
| 2 | Monotonic | Monotonic clock only |
| 3 | Both | Both wall and monotonic clocks |
---
## 9. Versioning and Compatibility
### 9.1 Protocol Version
The protocol version is negotiated during the open phase. The current (and
only) protocol version is **1**.
A conforming client MUST set `ProtocolVersion` to 1 in its open request.
A conforming client MUST verify that the server's returned protocol version
matches exactly. If there is a mismatch, the client MUST abort the session.
The server will also set the Close flag if there is a version mismatch.
### 9.2 Unknown Fields and Extensions
Unknown parameter tags in the negotiation payload MUST be silently ignored.
Reserved flag bits (4-7) MUST NOT be set. Packets with unknown flag bits set
MUST be rejected.
### 9.3 Server Versions
This specification targets interoperability with servers implementing
protocol version 1, as found in IRTT versions 0.9.0 and later. Earlier
development versions (0.1.x) used an incompatible protocol and are not
supported.
---
## 10. Client Configuration Parameters
The following configuration parameters affect protocol behavior:
### 10.1 Server Address
- **Effect:** Determines the destination for all packets.
- **Default port:** 2112.
- **Valid values:** Hostname, IPv4 address, IPv6 address (in brackets),
optionally with port.
### 10.2 Duration
- **Effect:** How long the client sends test packets.
- **Default:** 60 seconds (1 minute).
- **Valid values:** Positive duration.
- **Protocol:** Sent as nanoseconds in the Duration parameter. The server
MAY reduce this value.
### 10.3 Interval
- **Effect:** Time between successive test packets.
- **Default:** 1 second.
- **Valid values:** Positive duration.
- **Protocol:** Sent as nanoseconds in the Interval parameter. The server
MAY increase this value (enforce minimum interval). The server MAY also
decrease it to stay within its timeout constraints.
### 10.4 Length
- **Effect:** Total UDP payload length of each test packet.
- **Default:** 0, which means the packet is the minimum size needed for the
header fields.
- **Valid values:** 0 or positive integer. The actual minimum is determined
by the header fields present.
- **Protocol:** Sent in the Length parameter. The server MAY reduce this.
### 10.5 Received Stats
- **Effect:** What statistics the server includes about received packets.
- **Default:** Both (count + window).
- **Valid values:** None (0), Count (1), Window (2), Both (3).
### 10.6 Timestamp Mode (StampAt)
- **Effect:** When the server records timestamps.
- **Default:** Both (send + receive).
- **Valid values:** None (0), Send (1), Receive (2), Both (3), Midpoint (4).
- **Note:** The server MAY restrict timestamps (e.g., allow only single or
no timestamps).
### 10.7 Clock Selection
- **Effect:** Which clock(s) are used for server timestamps.
- **Default:** Both (wall + monotonic).
- **Valid values:** Wall (1), Monotonic (2), Both (3).
### 10.8 DSCP
- **Effect:** Sets the TOS/Traffic Class byte in the IP header for both
client and server packets during the active test phase.
- **Default:** 0 (best effort).
- **Valid values:** 0-255 (full TOS byte value). The DSCP field occupies
the upper 6 bits; the lower 2 bits (ECN) are typically zero.
Common values: 0 (best effort), 0xb8 (EF), 0xa0 (CS5), 0x20 (CS1).
[**Verified 2026-04-28** — see BLACKBOX_VERIFICATION_REPORT.md Finding B.]
- **Protocol:** The server echoes packets with the negotiated DSCP value.
The server MAY disallow DSCP and reset it to 0.
- **Observed behavior:** DSCP/TOS is applied only to echo request and echo
reply packets. Open and close packets use TOS=0 regardless of the
negotiated DSCP value. [**Verified 2026-04-28** — Finding C.]
### 10.9 Open Timeouts
- **Effect:** Controls retransmission during the open phase.
- **Default:** 1s, 2s, 4s, 8s.
- **Valid values:** List of positive durations, each >= 200ms.
### 10.10 Wait (Drain Period)
- **Effect:** How long to wait after the last send for outstanding replies.
- **Default:** 3 times the maximum observed RTT, falling back to 4 seconds
if no RTT has been measured.
- **Variants:**
- A factor of maximum observed RTT, with a fallback duration if no RTT
is available.
- A factor of mean observed RTT, with a fallback duration if no RTT is
available.
- A fixed duration.
### 10.11 HMAC Key
- **Effect:** When set, an MD5-based HMAC is computed over each packet and
included in the HMAC field. The server MUST also have the same key.
- **Default:** None (no HMAC).
- **Protocol:** When an HMAC key is configured, the HMAC flag (0x08) is set
in every packet.
### 10.12 Server Fill
- **Effect:** Requests the server to fill reply payloads with specific data.
- **Default:** Not specified (server uses its default fill).
- **Valid values:** String, max 32 characters. Common values: "none", "rand",
"pattern:XXXX" (hex pattern).
- **Protocol:** The server MAY restrict this to allowed fill types.
### 10.13 No-Test Mode
- **Effect:** Opens a connection and immediately closes it without running a
test. Useful for parameter validation.
- **Protocol:** Both the Open and Close flags are set in the open request.
### 10.14 Loose Mode
- **Effect:** When enabled, the client accepts server-restricted parameters
with a warning instead of aborting.
- **Default:** Disabled (strict mode).
---
## 11. Timing Semantics
### 11.1 Timestamp Capture Points
- **Client send timestamp:** SHOULD be captured as close as possible to the
send system call. For best RTT accuracy, the timestamp should be taken
immediately after the send call returns.
- **Client receive timestamp:** SHOULD be captured as close as possible to
the receive system call return, before any packet processing.
- **Server timestamps:** Determined by the negotiated StampAt parameter:
- Receive: captured when the server receives the packet.
- Send: captured just before the server sends the reply.
- Midpoint: the average of receive and send times.
### 11.2 Clock Types
A conforming client MUST track both wall clock and monotonic clock times
for client-side timestamps. The monotonic clock SHOULD be used for all
duration calculations (RTT, IPDV) because it is immune to system time
adjustments. The wall clock is needed for one-way delay calculations.
### 11.3 Send Scheduling
The client sends packets on a **fixed isochronous schedule**:
1. Record the start time.
2. The ideal send time for packet N is: `start + N * interval`.
3. The client SHOULD send each packet as close to its ideal send time as
the system timer allows.
4. The client SHOULD compensate for cumulative timer drift so that packets
remain aligned to integer multiples of the interval from the start time.
The specific drift-compensation strategy is implementation-defined.
5. Before each send, the client checks whether the next scheduled time
would be at or past `start + duration`. If so, sending stops.
The observable effect is that packets are sent at approximately
`start + 0`, `start + interval`, `start + 2*interval`, etc., with the
last packet sent strictly before `start + duration`.
### 11.4 Duration and Count Interaction
- The expected packet count is `ceil(duration / interval)`.
[**Verified 2026-04-28** — see BLACKBOX_VERIFICATION_REPORT.md Finding A.]
- The duration is **exclusive**: the last packet is sent at approximately
`start + (ceil(duration / interval) - 1) * interval`, which is strictly
before `start + duration`.
- Example: duration=1s, interval=200ms → packets at 0ms, 200ms, 400ms,
600ms, 800ms = 5 packets = ceil(1000/200).
- Example: duration=1s, interval=1s → packet at 0ms only = 1 packet =
ceil(1000/1000). The packet at 1000ms is NOT sent (exclusive end).
### 11.5 Timer Compensation
The client SHOULD use timer error compensation to improve send accuracy.
The specific compensation algorithm is implementation-defined. Any approach
that produces accurate isochronous sending is acceptable.
### 11.6 Drain Period
After the last packet is sent, if not all replies have been received, the
client MUST wait for outstanding replies for the configured wait duration
before closing the session.
---
## 12. Measurement Semantics
### 12.1 Round-Trip Time (RTT)
RTT MUST be calculated using client-side monotonic clock values:
```
RTT = client_receive_mono - client_send_mono
```
If both server send and receive timestamps are available (monotonic
preferred, wall clock as fallback), the server processing time SHOULD be
subtracted:
```
server_processing_time = server_send - server_receive
RTT = (client_receive_mono - client_send_mono) - server_processing_time
```
This subtraction SHOULD only be applied when both server timestamps are
available. See Section 19.12 for edge-case behavior when server processing
time appears to exceed the raw round-trip time.
### 12.2 One-Way Delay
One-way delay requires wall clock timestamps and externally synchronized
clocks (e.g., NTP, PTP). Values are only meaningful when clocks are
synchronized.
```
send_delay = server_best_receive_wall - client_send_wall
receive_delay = client_receive_wall - server_best_send_wall
```
Where "best receive" prefers the actual receive timestamp, falling back to
the send timestamp if receive is not available (and vice versa for "best
send"). For midpoint timestamps, both best_receive and best_send are the
midpoint value.
### 12.3 Packet Loss
```
packet_loss_percent = 100 * (packets_sent - unique_replies_received) / packets_sent
```
If the received count is available from the server:
```
upstream_loss_percent = 100 * (packets_sent - server_packets_received) / packets_sent
downstream_loss_percent = 100 * (server_packets_received - unique_replies_received) / server_packets_received
```
### 12.4 Duplicate Packets
A duplicate is a reply with a sequence number for which a reply has already
been recorded. Duplicates MUST be counted but MUST NOT update the original
round-trip data for that sequence number.
```
duplicate_percent = 100 * duplicates / total_packets_received
```
### 12.5 Late Packets (Out-of-Order)
A late packet is one whose sequence number is lower than the most recently
received sequence number.
```
late_packets_percent = 100 * late_packets / total_packets_received
```
### 12.6 IPDV (Jitter)
IPDV is the difference in delay between two successive successfully received
replies. It is only calculable for sequence number N when both packet N and
packet N-1 have received replies.
```
round_trip_ipdv[N] = RTT[N] - RTT[N-1]
send_ipdv[N] = send_delay[N] - send_delay[N-1] (if timestamps available)
receive_ipdv[N] = receive_delay[N] - receive_delay[N-1] (if timestamps available)
```
For send and receive IPDV, monotonic clock differences are RECOMMENDED
over wall clock differences when both are available, as the monotonic clock
is not subject to NTP adjustments. Note that IPDV does NOT require
synchronized clocks — only consistent clocks within each endpoint.
### 12.7 Server Processing Time
When both server send and receive timestamps are available:
```
server_processing_time = server_send - server_receive
```
Prefer monotonic clock values if available; fall back to wall clock.
### 12.8 Per-Packet Upstream/Downstream Loss (Received Window)
When the 64-bit received window is present in a reply:
- Bit 0 (LSB) represents the current packet (the one being replied to).
- Bit 1 represents the packet with sequence number one less than current, etc.
- A set bit means the server received that packet; a clear bit means it
did not.
The window may not be valid for all replies. Bit 0 (LSB) is always set
for valid windows, since it represents the current packet which was
received. [**Verified 2026-04-28** — see Section 19.7.] Implementations
SHOULD treat a window value of 0 as potentially invalid and avoid using
it for loss classification.
The client processes the received window to classify previous packets:
- If a packet was not received by the client but the window says the server
received it → lost downstream.
- If a packet was not received by the client and the window says the server
did not receive it → lost upstream.
- If the window cannot confirm (e.g., more than 64 packets ago, or window
was invalid) → generic loss.
The window has a maximum lookback of 63 packets (bits 1-63).
### 12.9 Statistics
For RTT, send delay, receive delay, and IPDV, a conforming client SHOULD
calculate:
- Minimum, Maximum, Mean, Median, Standard Deviation, Variance.
- Total count (N).
Median requires storing all values. The method of computing running
statistics is implementation-defined.
---
## 13. Validation Rules
### 13.1 Magic Bytes
Every received packet MUST begin with the magic bytes `0x14 0xA7 0x5B`.
Packets with incorrect magic MUST be discarded.
### 13.2 Flag Validation
Reserved flag bits (4-7) MUST be zero. Packets with invalid flag bits MUST
be discarded.
### 13.3 Reply Flag
All packets received by the client MUST have the Reply flag (0x02) set.
Packets without the Reply flag MUST be discarded.
### 13.4 HMAC Validation
If an HMAC key is configured:
- Received packets MUST have the HMAC flag set.
- The HMAC MUST be validated:
1. Extract the HMAC value from the packet.
2. Zero the HMAC field in the packet buffer.
3. Compute HMAC-MD5 over the entire packet buffer.
4. Compare the computed HMAC with the extracted value using a
constant-time comparison.
- Packets with missing or invalid HMAC MUST be discarded.
If no HMAC key is configured:
- Received packets MUST NOT have the HMAC flag set. Packets with an
unexpected HMAC MUST be discarded.
### 13.5 Sequence Number Validation
Echo reply sequence numbers MUST correspond to a previously sent packet.
Replies with sequence numbers outside the range [0, packets_sent) MUST be
rejected.
### 13.6 Timestamp Consistency
If both receive and send timestamps are present in a reply, they MUST use
the same clock mode (both wall, both monotonic, or both sets). Inconsistent
clock modes MUST cause the packet to be rejected.
Midpoint timestamps MUST be exclusive — if a midpoint timestamp is present,
no receive or send timestamp fields may also be present.
### 13.7 Close Flag in Echo Reply
If the server sets the Close flag in an echo reply, the client SHOULD
process the reply normally (record measurements) and then close the
connection. This is defensive handling for the possibility that the
server can forcibly end a session mid-test.
**Note:** This behavior has not been directly verified by black-box
testing (see Section 19.3). Implementations SHOULD handle it defensively.
---
## 14. Error Handling
| Error Category | Client Behavior |
|---------------|----------------|
| Name resolution failure | MUST abort. |
| Socket creation / bind failure | MUST abort. |
| Server unreachable (all open timeouts exhausted) | MUST abort. No partial results. |
| Protocol version mismatch | MUST abort. |
| Server restriction rejected (strict mode) | MUST abort. |
| Server restriction accepted (loose mode) | SHOULD warn, continue. |
| DSCP not supported by OS | MUST abort if DSCP was requested non-zero. |
| DF bit not supported by OS | MUST abort if DF was requested non-default. |
| Open reply with Close flag (not no-test) | MUST abort (server rejected session). |
| Zero connection token (without close flag) | MUST abort. |
| Send error during test | SHOULD abort, report partial results. |
| Receive error during test | SHOULD abort, report partial results. |
| Individual malformed packet during test | SHOULD abort. |
| Short reply (shorter than negotiated length) | SHOULD abort. |
| Stamp-at mismatch in reply | SHOULD abort. |
| Clock mismatch in reply | SHOULD abort. |
| Unexpected sequence number | SHOULD abort. |
| Context cancellation (user interrupt) | SHOULD stop gracefully, close connection, report partial results. |
| Insufficient resources for test parameters | MUST abort. |
---
## 15. Result Model
After a test, the client implementation SHOULD expose the following
information:
### 15.1 Per-Packet Data (Round Trips)
For each sent packet (indexed by sequence number):
- Sequence number.
- Lost status: not lost, lost (generic), lost upstream, lost downstream.
- Client send timestamp (wall + monotonic).
- Client receive timestamp (wall + monotonic) — empty if lost.
- Server receive timestamp (wall and/or monotonic) — as negotiated.
- Server send timestamp (wall and/or monotonic) — as negotiated.
- Computed RTT.
- Computed send delay.
- Computed receive delay.
- Computed IPDV (round-trip, send, receive) relative to previous packet.
### 15.2 Aggregate Statistics
- Test start time (wall + monotonic).
- Actual test duration.
- Packets sent.
- Packets received (unique).
- Packet loss percentage.
- Upstream loss percentage (if server count available).
- Downstream loss percentage (if server count available).
- Duplicate count and percentage.
- Late packet count and percentage.
- RTT statistics: min, max, mean, median, stddev, variance.
- Send delay statistics (if timestamps available).
- Receive delay statistics (if timestamps available).
- IPDV statistics (round-trip, send, receive).
- Server processing time statistics.
- Bytes sent and received.
- Send and receive bitrate.
- Timer error statistics.
- Timer miss count and percentage.
- Wait duration actually used.
### 15.3 Configuration
- Local and remote addresses (resolved).
- Negotiated parameters.
- Originally supplied parameters.
- IP version used.
### 15.4 Errors
- Send error (if the send operation failed).
- Receive error (if the receive operation failed).
---
## 16. Interoperability Requirements
1. A conforming client MUST be able to open a session with a compatible IRTT
server running protocol version 1.
2. A conforming client MUST send open requests with correctly serialized
parameters using the varint-encoded tag-value format.
3. A conforming client MUST correctly parse server open replies, including
restricted parameters.
4. A conforming client MUST include the connection token in all post-open
packets.
5. A conforming client MUST send test packets in the negotiated format, with
the correct field layout and packet length.
6. A conforming client MUST correctly match replies to sent packets using
sequence numbers.
7. A conforming client MUST tolerate UDP reordering, duplication, and loss.
8. A conforming client MUST validate magic bytes, flags, and HMAC on all
received packets.
9. A conforming client SHOULD produce RTT measurements consistent with
upstream implementations within the precision of the system clocks.
10. A conforming client MUST send a close packet at the end of the session.
11. A conforming client MUST correctly handle the HMAC calculation:
compute HMAC-MD5 over the full packet with the HMAC field zeroed.
12. When computing an outgoing HMAC, the client MUST:
a. Set the HMAC flag bit.
b. Zero the HMAC field.
c. Compute HMAC-MD5 over the entire packet buffer.
d. Write the resulting MAC into the HMAC field.
---
## 17. Black-Box Test Plan
### Test 1: Basic Connectivity
- **Setup:** Start an upstream IRTT server with default settings.
- **Client config:** Default parameters (1s interval, 1m duration), reduce
to 5s duration for testing. Server address: localhost.
- **Expected:** Session opens, packets are exchanged, results include RTT
measurements, session closes cleanly.
- **Pass criteria:** Non-zero packets_sent and packets_received. RTT values
present. No errors.
### Test 2: Parameter Negotiation
- **Setup:** Start a server with `--max-length=100`.
- **Client config:** Request length 200.
- **Expected (strict mode):** Client aborts with server restriction error.
- **Expected (loose mode):** Client continues with length 100.
- **Pass criteria (strict):** Client exits with error indicating server
reduced length.
- **Pass criteria (loose):** Successful test with negotiated length 100.
### Test 3: HMAC Authentication
- **Setup:** Start server with `--hmac=testkey`.
- **Client config:** `--hmac=testkey`.
- **Expected:** Session succeeds.
- **Pass criteria:** Successful test with no errors.
- **Variant:** Client uses wrong key. Expected: open timeout (server drops
packets with bad HMAC silently).
### Test 4: Packet Capture Verification
- **Setup:** Start server on localhost. Capture with tcpdump on the loopback
interface, port 2112.
- **Client config:** Short test (5s, 1s interval).
- **Expected:** Capture shows:
- Open request: magic bytes 14 a7 5b, flags=0x01, followed by varint
params in payload.
- Open reply: magic bytes, flags=0x03 (open+reply), 8-byte token, params.
- Echo requests: magic bytes, flags=0x00, 8-byte token, 4-byte sequence number.
- Echo replies: magic bytes, flags=0x02 (reply), 8-byte token, 4-byte
sequence number, optional stats and timestamps.
- Close request: magic bytes, flags=0x04.
- **Pass criteria:** All packets have correct magic, flags, and field layout.
### Test 5: Sequence Number Handling
- **Setup:** Server with default settings.
- **Client config:** 10s duration, 200ms interval (expect ~51 packets).
- **Expected:** Reply sequence numbers are in range [0, packets_sent).
Results show correct per-packet data.
- **Pass criteria:** All received replies have valid sequence numbers. No
unexpected sequence number errors.
### Test 6: Timestamp Modes
- **Setup:** Server with default settings.
- **Variants:**
- `--tstamp=none --clock=wall`
- `--tstamp=send --clock=wall`
- `--tstamp=receive --clock=monotonic`
- `--tstamp=both --clock=both`
- `--tstamp=midpoint --clock=both`
- **Expected:** Each variant produces the correct timestamp fields in replies.
RTT is always available. One-way delay is only available with wall clock
timestamps.
- **Pass criteria:** Results match expected availability of delay measurements.
### Test 7: Loss Detection
- **Setup:** Use a network impairment tool (e.g., `tc netem loss 10%`) or a
server on a lossy link.
- **Client config:** 30s duration, 100ms interval.
- **Expected:** packet_loss_percent is non-zero. With `--stats=both`,
upstream and downstream loss are differentiated.
- **Pass criteria:** Loss percentages are reported. With received window,
per-packet loss direction is classified.
### Test 8: IPv6
- **Setup:** Server listening on IPv6 (::1).
- **Client config:** `-6 ::1`.
- **Expected:** Successful test over IPv6.
- **Pass criteria:** IP version in results is IPv6.
### Test 9: No-Test Mode
- **Setup:** Server with default settings.
- **Client config:** `-n localhost`.
- **Expected:** Connection opens, parameters are negotiated, connection
closes immediately. No test packets sent.
- **Pass criteria:** No round-trip data. No errors.
### Test 10: Large Packet Size
- **Setup:** Server with default settings.
- **Client config:** `-l 1472 -d 5s localhost`.
- **Expected:** Successful test with 1472-byte packets.
- **Pass criteria:** Non-zero results.
### Test 11: Server Fill
- **Setup:** Server with `--allow-fills=rand`.
- **Client config:** `--sfill=rand -l 172 -d 5s localhost`.
- **Expected:** Successful test. Server fills reply payloads with random data.
- **Pass criteria:** Test completes without error.
### Test 12: DSCP
- **Setup:** Server with default settings (DSCP allowed).
- **Client config:** `--dscp=46 -d 5s localhost`.
- **Expected:** Packets are sent with EF DSCP marking. Capture verification
shows TOS/TC field set.
- **Pass criteria:** Test completes. Packet capture confirms DSCP value.
---
## 18. Packet Test Vectors
### 18.1 Echo Request with HMAC
**Scenario:** Echo request with HMAC, received stats (both), timestamps
(both, both clocks), pattern fill `0xFF 0xFE 0xFD 0xFC`, 92-byte packet
(76-byte header + 16-byte payload).
[**Size corrected 2026-04-28** — originally described as 256 bytes, but
HMAC verification confirms the test vector is 92 bytes. See
BLACKBOX_VERIFICATION_REPORT.md Finding E.]
**Connection token:** `0x886bc9a722b33eea` (little-endian)
**Sequence number:** `0x6fe2a1bb` (little-endian)
**HMAC key:** `0x3c 0x68 0x1d 0x39 0x41 0x1d 0x72 0x43`
**Fields present (in order):**
1. Magic: 3 bytes
2. Flags: 1 byte (0x08 = HMAC)
3. HMAC: 16 bytes
4. Connection Token: 8 bytes
5. Sequence Number: 4 bytes
6. Received Count: 4 bytes (zeroed)
7. Received Window: 8 bytes (zeroed)
8. Receive Wall: 8 bytes (zeroed)
9. Receive Mono: 8 bytes (zeroed)
10. Send Wall: 8 bytes (zeroed)
11. Send Mono: 8 bytes (zeroed)
**Header total:** 3+1+16+8+4+4+8+8+8+8+8 = 76 bytes
**Payload:** 92 - 76 = 16 bytes of repeating pattern
**Hex (92 bytes):**
```
14 a7 5b 08 e7 03 41 e7 d4 08 cf 69 41 f3 f4 78
5a 56 0c 4c ea 3e b3 22 a7 c9 6b 88 bb a1 e2 6f
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 ff fe fd fc
ff fe fd fc ff fe fd fc ff fe fd fc
```
**Field breakdown:**
- Bytes 0-2: Magic `14 a7 5b`
- Byte 3: Flags `08` (HMAC set)
- Bytes 4-19: HMAC `e7 03 41 e7 d4 08 cf 69 41 f3 f4 78 5a 56 0c 4c`
- Bytes 20-27: Connection Token `ea 3e b3 22 a7 c9 6b 88` (LE: 0x886bc9a722b33eea)
- Bytes 28-31: Sequence Number `bb a1 e2 6f` (LE: 0x6fe2a1bb)
- Bytes 32-35: Received Count (zeroed)
- Bytes 36-43: Received Window (zeroed)
- Bytes 44-51: Receive Wall Timestamp (zeroed)
- Bytes 52-59: Receive Mono Timestamp (zeroed)
- Bytes 60-67: Send Wall Timestamp (zeroed)
- Bytes 68-75: Send Mono Timestamp (zeroed)
- Bytes 76+: Payload (pattern fill)
**Client behavior:** This packet is sent to the server during the active test phase.
### 18.2 Echo Reply with HMAC and Full Timestamps
**Scenario:** Echo reply with HMAC, received stats (both), timestamps (both,
both clocks), pattern fill, 92-byte packet (76-byte header + 16-byte payload).
[**Size corrected 2026-04-28** — see Finding E.]
**Connection token:** `0xe666ceb6766fcbc3` (little-endian)
**Sequence number:** `0x1d3b0706` (little-endian)
**HMAC key:** `0xda 0xb3 0xe9 0x04 0xa6 0x87 0x92 0x49`
**Received count:** `0xa3bc9f19` (little-endian)
**Received window:** `0xd7e939e586f83b9b` (little-endian)
**Timestamps (little-endian int64):**
- Receive wall: `0x525af13dee2a75a1`
- Receive mono: `0x2d5562223e4ac69a`
- Send wall: `0x589705d446293f69`
- Send mono: `0x461df12fdd2c5066`
**Fields present (in order):**
1. Magic: 3 bytes
2. Flags: 1 byte (0x08 = HMAC, plus 0x02 = Reply → but let's verify from the test vector)
3. HMAC: 16 bytes
4. Connection Token: 8 bytes
5. Sequence Number: 4 bytes
6. Received Count: 4 bytes
7. Received Window: 8 bytes
8. Receive Wall: 8 bytes
9. Receive Mono: 8 bytes
10. Send Wall: 8 bytes
11. Send Mono: 8 bytes
**Note on flags:** The flags byte in this test vector is `0x08` (only HMAC
set). This vector is provided for verifying field layout and HMAC
computation only. **In actual protocol operation, the Reply flag (0x02)
MUST also be set on all server-to-client packets**, making the expected
flags byte `0x0A` for a normal HMAC echo reply. Implementers MUST
set the Reply flag in actual protocol exchanges; the HMAC computation
itself does not depend on the Reply flag's value.
**Hex (first 92 bytes):**
```
14 a7 5b 08 d2 98 a3 4a 6a 13 41 02 68 b2 67 a8
d6 7e 28 25 c3 cb 6f 76 b6 ce 66 e6 06 07 3b 1d
19 9f bc a3 9b 3b f8 86 e5 39 e9 d7 a1 75 2a ee
3d f1 5a 52 9a c6 4a 3e 22 62 55 2d 69 3f 29 46
d4 05 97 58 66 50 2c dd 2f f1 1d 46 ff fe fd fc
ff fe fd fc ff fe fd fc ff fe fd fc
```
**Field breakdown:**
- Bytes 0-2: Magic `14 a7 5b`
- Byte 3: Flags `08` (HMAC; see note above about Reply flag)
- Bytes 4-19: HMAC `d2 98 a3 4a 6a 13 41 02 68 b2 67 a8 d6 7e 28 25`
- Bytes 20-27: Connection Token `c3 cb 6f 76 b6 ce 66 e6` (LE: 0xe666ceb6766fcbc3)
- Bytes 28-31: Sequence Number `06 07 3b 1d` (LE: 0x1d3b0706)
- Bytes 32-35: Received Count `19 9f bc a3` (LE: 0xa3bc9f19)
- Bytes 36-43: Received Window `9b 3b f8 86 e5 39 e9 d7` (LE: 0xd7e939e586f83b9b)
- Bytes 44-51: Receive Wall `a1 75 2a ee 3d f1 5a 52` (LE: 0x525af13dee2a75a1)
- Bytes 52-59: Receive Mono `9a c6 4a 3e 22 62 55 2d` (LE: 0x2d5562223e4ac69a)
- Bytes 60-67: Send Wall `69 3f 29 46 d4 05 97 58` (LE: 0x589705d446293f69)
- Bytes 68-75: Send Mono `66 50 2c dd 2f f1 1d 46` (LE: 0x461df12fdd2c5066)
- Bytes 76+: Payload (pattern fill)
**Expected client behavior:** Validate magic, validate HMAC, extract
connection token, extract sequence number, extract received stats, extract timestamps,
record round-trip data.
---
## 19. Open Questions / Verification Required
### 19.1 Open Request Field Layout — ✅ RESOLVED
**Status:** Verified 2026-04-28, irtt 0.9.1, macOS arm64.
**Result:** The open request does NOT include a connection token field. The
layout is: magic (3) + flags (1) + [HMAC (16) if applicable] + params.
Verified by packet capture (`captures/full-session.pcapng`,
`captures/hmac-session.pcapng`).
### 19.2 Minimum Packet Length Calculation — ✅ RESOLVED
**Status:** Verified 2026-04-28, irtt 0.9.1, macOS arm64.
**Result:** Minimum echo packet lengths verified for all stats/tstamp/clock
combinations. See BLACKBOX_VERIFICATION_REPORT.md Section 19.2 for the
complete table. The minimum is 16 bytes (no stats, no timestamps) and the
maximum default is 76 bytes (both+both+both+HMAC).
### 19.3 Server Close During Test — OPEN / Verification Required
**Status:** Not tested. Hard to trigger reliably with black-box testing.
**Question:** Can the server set the Close flag on an echo reply mid-test
(e.g., due to exceeding a duration limit)? If so, how should the client
respond?
**Compatibility recommendation:** The client SHOULD check for the Close
flag in echo replies and, if set, process the reply normally (record
measurements) and then terminate the session. This is defensive handling
for a scenario that has not been directly observed in black-box testing.
### 19.4 Varint Encoding Compatibility — ✅ RESOLVED
**Status:** Verified 2026-04-28, irtt 0.9.1, macOS arm64.
**Result:** Parameters decoded from captured packets match expected values
using standard protobuf-style zigzag + LEB128. All parameter tags and values
verified. See BLACKBOX_VERIFICATION_REPORT.md Section 19.4.
### 19.5 HMAC Computation Scope — ✅ RESOLVED
**Status:** Verified 2026-04-28, irtt 0.9.1, macOS arm64.
**Result:** HMAC-MD5 is computed over the entire packet buffer (including
payload) with the 16-byte HMAC field zeroed. Independently verified with
Python for all four packet types (open request, echo request, echo reply,
close request). The spec's test vectors (18.1 and 18.2) also verify
correctly at 92-byte packet size.
### 19.6 Send Timestamp Capture Timing — OPEN (not externally testable)
This is an implementation-internal detail that cannot be verified via
black-box testing. The client SHOULD capture timestamps as close to the
send/receive system calls as possible (see Section 11.1), but the exact
timing is an implementation choice.
### 19.7 Server Received Window Validity — ✅ RESOLVED
**Status:** Verified 2026-04-28, irtt 0.9.1, macOS arm64.
**Result:** Bit 0 (LSB) of the received window is always set for valid
windows (it represents the current packet which was received). Observed
window values: seq 0 → 0x01, seq 1 → 0x03, seq 2 → 0x07. A window
value of 0 can be used as the invalid sentinel. See
BLACKBOX_VERIFICATION_REPORT.md Section 19.7.
### 19.8 Open Reply Without HMAC When Client Expects HMAC — ✅ RESOLVED
**Status:** Verified 2026-04-28, irtt 0.9.1, macOS arm64.
**Result:** The server silently drops packets with missing or invalid HMAC.
The client times out with `[OpenTimeout] no reply from server`. Same
behavior for wrong key and no key. See BLACKBOX_VERIFICATION_REPORT.md
Section 19.8.
### 19.9 Minimum Restricted Interval Safety Floor — PARTIALLY RESOLVED
**Status:** Partially tested 2026-04-28.
**Result:** In strict mode, the client rejects ANY server parameter change
with a non-zero exit code. In loose mode, changes are accepted with a
warning. The exact safety floor for server-decreased intervals could not
be tested without a cooperative malicious server. See
BLACKBOX_VERIFICATION_REPORT.md Section 19.9.
### 19.10 Maximum Parameter Buffer Size — PARTIALLY RESOLVED
**Status:** Partially tested 2026-04-28.
**Result:** Largest observed parameter payload was 52 bytes (all params
including 24-character ServerFill). Upper bound estimate is ~82 bytes. The
128-byte limit appears safe. See BLACKBOX_VERIFICATION_REPORT.md
Section 19.10.
### 19.11 Field Ordering Verification — ✅ RESOLVED
**Status:** Verified 2026-04-28, irtt 0.9.1, macOS arm64.
**Result:** The field ordering in Section 8.1.3 is correct. Verified with
six different field combinations including no optional fields, various
stats/tstamp combinations, midpoint timestamps, and HMAC. See
BLACKBOX_VERIFICATION_REPORT.md Section 19.11.
### 19.12 RTT When Server Processing Time Exceeds Raw Round-Trip — OPEN / Verification Required
**Status:** Not tested. Hard to trigger on localhost.
**Question:** When server processing time (as reported by server
timestamps) exceeds the client-measured round-trip time — possible due to
clock granularity or drift — the subtraction would produce a negative RTT.
Should the client skip the subtraction, clamp to zero, or report the
negative value?
This edge case requires server processing time to exceed the raw
client-measured RTT, which is extremely unlikely under normal conditions.
---
## 20. Non-Goals
This specification does **not** require:
- Matching upstream source layout, file organization, or module structure.
- Matching upstream private names (functions, types, variables).
- Matching upstream internal algorithms where equivalent behavior can be
achieved independently.
- Matching exact CLI text output.
- Implementing the server.
- Implementing every optional upstream feature in the first milestone.
- Implementing timer compensation with any specific algorithm (only accurate
send timing is required).
- Implementing any specific concurrency model.
- Using any specific programming language features or libraries.
- Supporting IRTT protocol versions other than 1.
- Supporting the development-era (0.1.x) protocol.