Wire protocol
Edit on GitHub →The normative specification every RateLimitly client implements. Client libraries and their API references live on the docs index.
On this page
- Request/Response Messages
- Ordering of records in a message
- Little-endian ordering of numbers
- Size Field Semantics
- Data Alignment and Padding
- Zero-Copy Parsing Support
- Tenant Header
- Bech32 Credential Formats
- rl-secret
- rl-none
- rl-cookie
- rl-aes
- API-key quota word, format version 1
- Credential Validation Rules
- Auth Header
- 4.1 No Authentication
- Cookie Authentication
- AES-256-GCM Encryption
- General PDU Format
- Ratelimitly PDU Definitions
- PDU Type Summary
- Rate Request
- rate_response PDU
- Latency Report PDU
- Administrative PDUs
____ _ _ _ _ _ _ __ ___
| _ \ __ _| |_ ___| (_)_ __ ___ (_) |_| |_ _ \ \ / (_)_ __ ___
| |_) / _` | __/ _ \ | | '_ ` _ \| | __| | | | | \ \ /\ / /| | '__/ _ \
| _ < (_| | || __/ | | | | | | | | |_| | |_| | \ V V / | | | | __/
|_| \_\__,_|\__\___|_|_|_| |_| |_|_|\__|_|\__, | \_/\_/ |_|_| \___|
|___/
____ _ _
| _ \ _ __ ___ | |_ ___ ___ ___ | |
| |_) | '__/ _ \| __/ _ \ / __/ _ \| |
| __/| | | (_) | || (_) | (_| (_) | |
|_| |_| \___/ \__\___/ \___\___/|_|
Ratelimitly Wire Protocol (MVP)
Ratelimitly rate limiter clients/servers exchange request/response messages through User Datagram Protocol (UDP) sockets as single datagrams per message with a maximum payload of ~1200 bytes to all but eliminate the possibility of IP fragmentation on typical networks with Maximum Transmission Units (MTUs) of 1500 bytes.
Request/Response Messages
Clients send request messages to Ratelimitly servers, expecting the servers to process them and return corresponding response messages. All request and response messages consist of a sequence of records, including tenant and authentication data. Individual messages contain information specific to their intended purpose, represented as PDU (Protocol Data Unit) with a common prefix header (called the general PDU header) that is used to discriminate the specific message data that follows it.
Ordering of records in a message
All request and response messages MUST obey the following sequence of records: Tenant Header TLV, Auth Header TLV, and (one) Ratelimitly PDU. If the records are not in order, the server MUST drop the packet.
TLV (Type-Length-Value) is an encoding scheme with record type, the record value’s length, and the value itself. PDU (Protocol Data Unit) represents a Ratelimitly operation. Both TLV and PDU are specified below.
Little-endian ordering of numbers
Integers are represented (stored, transmitted) with the least significant byte first. For example, a 32-bit integer 0x01234567 must be represented as a sequence of bytes 0x67, 0x45, 0x23, 0x01.
When memory bytes are printed sequentially from left to right (e.g. in a hex dump), little-endian representation appears backwards when visualized. Such ordering was taken into account for numeric constants that were intentionally defined as two ASCII characters, easily recognizable in a binary view.
Size Field Semantics
All size fields in the protocol include the complete structure size (header + body):
- tlv_size: Includes the 4-byte TLV header plus the TLV body
- pdu_size: Includes the 8-byte PDU header plus the PDU body
- This applies consistently to all TLV and PDU structures throughout the protocol
Normative formulas:
-
tlv_body_len = tlv_size - 4 -
pdu_body_len = pdu_size - 8 -
Valid TLV requires
tlv_size >= 4 -
Valid PDU requires
pdu_size >= 8 -
Receiver MUST parse exactly
tlv_sizebytes for a TLV and exactlypdu_sizebytes for a PDU
Data Alignment and Padding
The protocol is optimized for zero-copy parsing with strict alignment requirements:
- Guard blocks: 40 bytes (8-byte aligned for optimal performance)
- Resource blocks: 28 bytes (4-byte aligned, required for zero-copy parsing)
- Service latency blocks: 36 bytes (4-byte aligned, no trailing padding)
- TLV structures: Minimal padding where needed for alignment
The 2-byte padding in resource blocks is essential for high-performance implementations using zero-copy parsing in Rust (#[repr(C)]) and Zig. Guard and service latency block fields already maintain proper 4-byte alignment without trailing padding. This ensures that arrays of fixed-size blocks maintain proper alignment for direct memory mapping from UDP packets.
Zero-Copy Parsing Support
The protocol is designed to support zero-copy parsing for maximum performance:
// Rust zero-copy structures
#[repr(C)]
struct GuardBlock {
latency_tracker_id: [u8; 16],
ttl_ms: u32,
max_samples: u32,
buffer_size: u32,
min_sample_threshold: u32,
latency_threshold: u32,
current_latency: u32,
}
#[repr(C)]
struct ServiceLatencyBlock {
latency_tracker_id: [u8; 16],
ttl_ms: u32,
max_samples: u32,
buffer_size: u32,
min_sample_threshold: u32,
observed_latency: u32,
}
// Zig zero-copy structures
const GuardBlock = extern struct {
latency_tracker_id: [16]u8,
ttl_ms: u32,
max_samples: u32,
buffer_size: u32,
min_sample_threshold: u32,
latency_threshold: u32,
current_latency: u32,
};
const ServiceLatencyBlock = extern struct {
latency_tracker_id: [16]u8,
ttl_ms: u32,
max_samples: u32,
buffer_size: u32,
min_sample_threshold: u32,
observed_latency: u32,
};
These structures are designed for direct casting from UDP packet buffers without additional parsing overhead.
Tenant Header
The Tenant Header contains tenant identification and request metadata that must be accessible without decryption for performance optimization. This includes the tenant or server identifier, the request deduplication identifier, and the request creation timestamp used for freshness validation.
The Tenant Header does not define the replay window for mutating requests. Replay-window control is carried by the mutating PDU body via dedup_ttl_ms.
+------------+-------------------+--------------+---------+----------------------------------------------------------+
| Tenant Header |
+------------+-------------------+--------------+---------+----------------------------------------------------------+
| | TLV Field | Size (bytes) | Type | Description |
+------------+-------------------+--------------+---------+----------------------------------------------------------+
| TLV Header | tlv_type | 2 | Integer | Tenant TLV type value will be 0x4C52 ("RL" when viewed |
| | | | | in hexdump) |
| | | | | to be easily identifiable. |
| +-------------------+--------------+---------+----------------------------------------------------------+
| | tlv_size | 2 | Integer | Overall size of TLV parameter |
| | | | | including header and value(s). Value is 40 for this TLV |
| | | | | (TLV body length = `tlv_size - 4` = 36). |
+------------+-------------------+--------------+---------+----------------------------------------------------------+
| TLV Body | key_id | 8 | Integer | Key ID (request) / Server ID (response) |
| +-------------------+--------------+---------+----------------------------------------------------------+
| | unique_id | 16 | Binary | Unique logical request identifier used for correlation |
| | | | | and deduplication |
| +-------------------+--------------+---------+----------------------------------------------------------+
| | time_stamp | 8 | Integer | Milliseconds since Epoch when the request was composed; |
| | | | | used for request freshness validation |
| +-------------------+--------------+---------+----------------------------------------------------------+
| | steering_feedback | 1 | Boolean | Source port steering: 0 = change port, 1 = keep port |
| +-------------------+--------------+---------+----------------------------------------------------------+
| | tenant_mgmt_flag | 1 | Boolean | Tenant management flag: 0 = regular operation, 1 = admin |
| +-------------------+--------------+---------+----------------------------------------------------------+
| | padding | 2 | Binary | Padding for 4-byte alignment (MUST be zero) |
+------------+-------------------+--------------+---------+----------------------------------------------------------+
Dual-Purpose key_id Field:
- Requests: Contains 64-bit key identifier (tenant/namespace identifier)
- Responses: Server overwrites with its structured 64-bit server identifier
- HA Usage: Clients use server-provided value to identify and validate server instances
Structured server_id Format:
For all responses, the key_id field MUST contain a structured server_id encoded as an unsigned 64-bit integer:
-
Bits
63..23(41 bits):start_s_since_2025 -
Bits
22..15(8 bits):listener_id -
Bits
14..0(15 bits):node_id
Definitions:
-
start_s_since_2025 = server_start_s - 1735689600 -
1735689600is January 1, 2025 00:00:00 +0000 UTC in Unix seconds -
server_start_sis the server thread startup time in Unix seconds -
listener_idis an 8-bit server-local listener discriminator in the range0..255 -
node_idis a deployment-scoped 15-bit server-node identifier in the range0..32767
Normative encoding:
-
server_id = (start_s_since_2025 << 23) | (listener_id << 15) | node_id -
Servers MUST encode
server_idexactly as above -
Servers MUST use 1-second resolution for
server_start_s -
Servers MUST ensure
listener_id <= 255 -
Servers on different nodes that may appear in the same SRV answer set SHOULD use different
node_idvalues -
Different listener threads on the same server node that may appear in the same SRV answer set SHOULD use different
listener_idvalues -
Two simultaneously live server threads that may appear in the same SRV answer set MUST NOT intentionally reuse the same tuple
(server_start_s, listener_id, node_id)
This layout preserves age ordering in the high bits while keeping the low bits focused on server identity and collision reduction rather than routing.
Source Port Steering:
The steering_feedback field is a 1-byte boolean that controls source port behavior for package steering.
Tenant Management Flag:
The tenant_mgmt_flag field is a 1-byte boolean that selects the
administrative authentication domain for the packet. The field name is retained
for wire compatibility, but value 1 now covers
administrative/control-plane traffic in general, not only tenant mutation
requests.
- Value 0 (false): Regular tenant operation (rate limiting, latency reporting)
- Value 1 (true): Administrative operation authenticated with the dedicated admin key
- Values 2-255: Reserved for future use
Padding and Alignment:
The remaining 2 bytes of padding MUST be set to zero for future extensibility and maintain 4-byte alignment for zero-copy parsing:
- Values: MUST be 0x00
- Purpose: Future protocol extensions and memory alignment optimization
Usage Patterns:
-
Request: Client sets
steering_feedbackbased on session requirements -
Request: Client sets
tenant_mgmt_flag = 1for all administrative PDUs -
Response: Server may update
steering_feedbackwith recommended port behavior -
Authentication: Server uses
tenant_mgmt_flagto select appropriate decryption key -
Session Affinity: Use
steering_feedback = 1when maintaining client session state -
Load Balancing: Use
steering_feedback = 0to allow optimal port distribution
Bech32 Credential Formats
Ratelimitly uses Bech32 strings for provisioning secrets and API keys. These strings are configuration artifacts, not on-wire TLVs by themselves. The human-readable part (HRP) selects the credential format; the payload layout is fixed by that HRP.
All multi-byte integers embedded in Bech32 payloads are encoded in little-endian byte order before Bech32 conversion.
rl-secret
rl-secret carries only raw secret material and no tenant metadata. It is intended for out-of-band configuration, primarily the tenant-management AES key supplied when starting a ratelimitly server.
| Payload field | Size (bytes) | Type | Description |
|---|---|---|---|
| secret | 32 | Binary | Raw 32-byte AES-256 key material |
rl-none
rl-none carries a versioned API-key identifier and quota word. It is used for API keys with no request authentication.
| Payload field | Size (bytes) | Type | Description |
|---|---|---|---|
| format_version | 1 | Integer |
API-key payload schema; MUST be 1 |
| key_id | 8 | Integer | API-key identifier |
| quotas | 4 | Integer | Packed quota word defined below |
rl-cookie
rl-cookie carries the same version and quota word plus the cookie secret material. The secret is the 32-byte cookie value used directly by the protocol, not a textual password.
| Payload field | Size (bytes) | Type | Description |
|---|---|---|---|
| format_version | 1 | Integer |
API-key payload schema; MUST be 1 |
| key_id | 8 | Integer | API-key identifier |
| secret | 32 | Binary | Raw 32-byte cookie value |
| quotas | 4 | Integer | Packed quota word defined below |
rl-aes
rl-aes carries the same version and quota word plus a raw 32-byte AES-256 key.
| Payload field | Size (bytes) | Type | Description |
|---|---|---|---|
| format_version | 1 | Integer |
API-key payload schema; MUST be 1 |
| key_id | 8 | Integer | API-key identifier |
| secret | 32 | Binary | Raw 32-byte AES-256 key |
| quotas | 4 | Integer | Packed quota word defined below |
API-key quota word, format version 1
The quota word is a little-endian u32. Bit zero below is the least-significant bit.
| Bits | Width | Code | Decoded quota |
|---|---|---|---|
| 0..4 | 5 |
rate_exp |
rate_buckets_max = 2^rate_exp; codes above 24 are invalid |
| 5..9 | 5 |
latency_exp |
latency_services_max = 2^latency_exp; codes above 24 are invalid |
| 10..14 | 5 |
labels_exp |
metrics_labels_max = 2^labels_exp |
| 15..18 | 4 |
buffer_exp |
latency_buffer_size_max = 2^buffer_exp |
| 19..26 | 8 |
dedup_units |
dedup_ttl_ms_max = dedup_units * 10; only codes 1..200 are valid |
| 27..31 | 5 |
window_exp |
rate_window_size_ms_max = 2^window_exp for 0..30; code 31 means 4294967295 |
The two c-map-backed cardinalities are encoded as logical capacity exponents,
not internal c-map bucket exponents. This matches the allocator’s power-of-two
geometry and prevents a credential from advertising an arbitrary capacity that
the server cannot allocate exactly. All four exponent-encoded count quotas MUST
be exact powers of two when written. dedup_ttl_ms_max MUST be a multiple of
10 ms in the range 10..2000 ms. rate_window_size_ms_max MUST be an exact power
of two through 2^30, or 4294967295 for the complete u32 window range.
The canonical default quota word is 0xf8f2b150 (bytes 50 b1 f2 f8) and decodes
to 65,536 rate buckets, 1,024 latency services, 4,096 metrics labels, a latency
buffer of 32, a 300 ms deduplication cap, and the complete u32 rate-window range.
Canonical payload and Bech32 examples are recorded in
api_key_v1_test_vectors.json is the normative,
cross-language conformance set. It covers the representable boundaries, every
control-plane plan, reserved exponent values, invalid deduplication codes,
unsupported versions, and invalid payload lengths. Rust server and control-plane
tests consume it directly. Standalone C and Python repositories consume generated
copies; check_api_key_v1_vectors.py verifies that those copies have not drifted.
Credential Validation Rules
- Unknown HRPs MUST be rejected.
-
rl-secretMUST contain exactly 32 payload bytes. -
rl-secretis unchanged and does not carry a format-version byte. -
rl-noneMUST contain exactly 13 payload bytes. -
rl-cookieandrl-aesMUST each contain exactly 45 payload bytes. -
API keys MUST carry
format_version = 1. Legacy unversioned payloads and unknown versions MUST be rejected; runtime readers MUST NOT infer defaults. -
In tenant-management ADD mutations, the decoded
key_idinsiderl-none,rl-cookie, orrl-aesMUST equal thekey_idfrom the outer Tenant Header. - Packed quota codes MUST satisfy the version-1 validation rules above.
Auth Header
Auth can be one of the following TLV structures. It MUST immediately follow the tenant TLV. If authentication fails, Ratelimitly MUST act as a blackhole by dropping the request and sending no response.
4.1 No Authentication
Used for closed systems where security is not an issue or for testing.
| TLV Field | Size (bytes) | Type | Description |
|---|---|---|---|
| tlv_type | 2 | Integer | Auth None 0x414E (“NA” in hexdump) |
| tlv_size | 2 | Integer | Overall size of TLV parameter (always 4; header only, no body) |
Cookie Authentication
Cookie authentication can be used in scenarios where man-in-the-middle attacks are considered highly unlikely. The integrity of the packet is not guaranteed by the Cookie.
+------------+-----------+--------------+---------+---------------------------------------------+
| | TLV Field | Size (bytes) | Type | Description |
+------------+-----------+--------------+---------+---------------------------------------------+
| TLV Header | tlv_type | 2 | Integer | Auth Cookie 0x4143 ("CA" when viewed in |
| | | | | hexdump) |
| +-----------+--------------+---------+---------------------------------------------+
| | tlv_size | 2 | Integer | Overall size of TLV parameter |
| | | | | including header and value(s) |
| | | | | This value is always 36 (4 + 32). |
| | | | | TLV body length = `tlv_size - 4` = 32. |
+------------+-----------+--------------+---------+---------------------------------------------+
| TLV Body | cookie | 32 | Binary | The Cookie is the SHA256 hash of the secret |
| | | | | shared by the client and server. |
| | | | | |
| | | | | SHA256("secret password") = Cookie |
| | | | | The Cookie is not stored as a hex string, |
| | | | | it is stored as raw binary data. |
+------------+-----------+--------------+---------+---------------------------------------------+
Cookie-authenticated API keys use an rl-cookie Bech32 credential. The on-wire Cookie Auth TLV carries the same 32-byte cookie value provisioned in that API key.
AES-256-GCM Encryption
AES-256-GCM provides authenticated encryption (confidentiality + integrity) with unique symmetric keys per tenant. This is the recommended authentication method for production deployments.
+------------+----------------+--------------+---------+----------------------------------------------------+
| | TLV Field | Size (bytes) | Type | Description |
+------------+----------------+--------------+---------+----------------------------------------------------+
| TLV Header | tlv_type | 2 | Integer | Auth AES-256-GCM 0x4541 ("AE" when viewed in |
| | | | | hexdump) |
| +----------------+--------------+---------+----------------------------------------------------+
| | tlv_size | 2 | Integer | Overall size of TLV parameter |
| | | | | including header and value(s) |
| | | | | This value is always 32 (4-byte header + 28-byte |
| | | | | body). |
| | | | | TLV body length = `tlv_size - 4` = 28. |
+------------+----------------+--------------+---------+----------------------------------------------------+
| TLV Body | nonce | 12 | Binary | 96-bit nonce for AES-GCM encryption |
| +----------------+--------------+---------+----------------------------------------------------+
| | auth_tag | 16 | Binary | 128-bit authentication tag from AES-GCM |
+------------+----------------+--------------+---------+----------------------------------------------------+
AES-authenticated API keys use an rl-aes Bech32 credential. The on-wire AES Auth TLV carries nonce and authentication tag only; the API key’s AES-256 secret is supplied out of band by the rl-aes credential.
AES-256-GCM Normative Framing
The following rules are normative for interoperability:
- Encryption scope: The PDU bytes are encrypted. Tenant TLV and Auth TLV remain plaintext.
- Ciphertext placement: Ciphertext replaces the plaintext PDU in-place and has the same byte length as the plaintext PDU.
-
AAD bytes: AAD is the exact byte sequence
tenant_tlv || auth_tlv_prefix, whereauth_tlv_prefixistlv_type(2) || tlv_size(2) || nonce(12). -
Auth tag:
auth_tagis the 16-byte AES-GCM tag computed over ciphertext and the AAD above. - Nonce uniqueness: Nonce MUST be unique per AES key. Reusing a nonce with the same key is forbidden.
- Auth failure behavior: If tag verification fails, server MUST drop the packet and send no response.
-
Replay handling: Replay decisions are defined by server-side deduplication keyed by
(pdu_type, tenant_mgmt_flag, key_id, unique_id); AES nonce is not a replay-decision key.
General PDU Format
The general format of a Ratelimitly Protocol Data Unit (PDU) consists of a PDU header followed by a body as outlined in the following:
+------------------------------------------------------------------------+
| Ratelimitly PDU |
+---------------------------------------------------+--------------------+
| General PDU Header (mandatory) | Optional |
+----------+----------+--------------+--------------+--------------------+
| pdu_type | pdu_size | <<reserved>> | <<reserved>> | PDU body |
+----------+----------+--------------+--------------+--------------------+
| 2 bytes | 2 bytes | 2 bytes | 2 bytes | pdu_size - 8 bytes |
| | | | | (body only) |
+----------+----------+--------------+--------------+--------------------+
pdu_size is always the total PDU length (header + body).
Ratelimitly PDU Definitions
This section defines the various operational PDUs that make up the Ratelimitly protocol.
PDU Type Summary
The following PDU types are defined in this protocol:
| PDU Type | Hex Value | ASCII | Description |
|---|---|---|---|
| Rate Request | 0x5452 | “RT” | Request rate-limited tokens with guards |
| Rate Response | 0x5252 | “RR” | Response with token grants and latency metrics |
| Latency Report | 0x524C | “LR” | Fire-and-forget latency metric report |
| Tenant Mutation | 0x544D | “TM” | Tenant configuration mutation (add/update/remove) |
| Tenant Ack | 0x5441 | “TA” | Acknowledgment response for tenant mutations |
| Metrics Query | 0x514D | “MQ” | Administrative metrics snapshot request |
| Metrics Response | 0x524D | “MR” | Administrative metrics snapshot response |
Rate Request
The essence of the Ratelimitly protocol is that clients request rate-limited tokens with dynamic rules and latency guards. Clients specify resource IDs, rate limits, and latency thresholds directly in each request. Ratelimitly servers evaluate guard conditions and grant tokens based on current resource state.
rate_request PDU
The format of the Ratelimitly rate_request PDU is defined in the following table:
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| pdu_type | 2 | Integer | 0x5452 (“RT” when viewed in hexdump) |
| pdu_size | 2 | Integer |
Defines the overall size of the rate_request PDU (PDU body length = pdu_size - 8) |
| dedup_ttl_ms | 4 | Integer | Required replay-window request in milliseconds |
| guard_count | 2 | Integer | Number of guard blocks that follow |
| resource_count | 2 | Integer | Number of resource blocks that follow |
| Guard Blocks | Variable | Binary | guard_count × 40-byte guard blocks |
| Resource Blocks | Variable | Binary | resource_count × 28-byte resource blocks |
| TLV Parameters | Variable | TLV | Optional metrics label |
dedup_ttl_ms (Rate Request)
dedup_ttl_ms is a required per-request replay window for mutating
rate-limit requests.
Client intent:
- the client composes the request with a concrete replay window
-
the client should not resend the request after
time_stamp + dedup_ttl_ms
Quota interaction:
-
each tenant credential carries a quota field
dedup_ttl_ms_max -
clients should not set
dedup_ttl_msabovededup_ttl_ms_max
Server behavior:
- the server accepts, rejects, or clamps the requested value according to implementation policy
- while deduplication is healthy, the server guarantees replay behavior for the accepted window
- if deduplication becomes unhealthy, replay guarantees are suspended and this must be surfaced by logs and metrics
This field controls replay behavior only. It does not replace request freshness
validation via time_stamp.
unique_id
The unique_id field is a 16-byte identifier for a logical request instance.
Clients may resend the same mutating request after packet loss by reusing the
same unique_id.
Deduplication Key:
Replay and deduplication decisions are scoped by:
-
pdu_type -
tenant_mgmt_flag -
key_id -
unique_id
This prevents collisions between different mutating request classes that happen
to reuse the same key_id and unique_id.
Healthy-State Guarantee:
While the server deduplication subsystem is healthy, the server guarantees that
a duplicate mutating request received within its accepted dedup_ttl_ms window
will replay the cached result rather than being reprocessed.
Degraded-State Behavior:
Under deduplication-capacity pressure or replay-state loss, implementations may evict live deduplication entries. When that happens:
-
the server MUST emit
ERRORlogs - the server MUST expose unhealthy deduplication state via metrics and/or health status
- replay guarantees for live requests are suspended until the deduplication subsystem returns to healthy operation
For Rate Request, degraded-state reprocessing is semantically undesirable but
possible once replay state has been lost. For Tenant Mutation, degraded-state
re-execution is acceptable because the operation semantics are idempotent enough
for this proof-of-concept.
A 16-byte field is provided to allow clients to populate it with a globally unique value for each request. We recommend using Universally Unique IDentifier version 4 (UUIDv4), defined as a 128-bit non-string binary value (16-bytes). For rendering purposes, the recommended human readable representation is a lowercase hex dump as a sequence of 16 bytes in the network byte order; as a 32 character ASCII string with no spaces and no leading 0x. This guarantees consistency between client and server for troubleshooting and correlation of logs. Example: 00004c2fd0624dea93a44654506faba7. Note that client and server do not need to agree on endianness, as long as the values are unique.
time_stamp
The time_stamp field is an 8-byte integer that defines the time at which the
request message was composed in milliseconds since the UNIX Epoch,
1970-01-01 00:00:00 +0000 (UTC).
This field is used for request freshness validation and trivial rejection of stale packets. It does not define the deduplication or replay window.
Replay-window control for mutating requests is carried explicitly in the mutating
PDU body via dedup_ttl_ms.
Deduplication Health Model
The protocol defines replay semantics for mutating request PDUs under the assumption that the server deduplication subsystem is healthy.
A healthy deduplication subsystem guarantees replay for admitted mutating requests during the accepted dedup window.
Implementations may degrade under deduplication-capacity pressure or replay-state loss. When that occurs:
- replay guarantees are suspended
-
the server MUST emit
ERRORlogs - the server MUST expose unhealthy deduplication metrics and/or health state
Clients and operators should treat unhealthy deduplication state as a degradation of duplicate-suppression guarantees.
Guard Blocks
Guard blocks are fixed 40-byte structures that specify latency thresholds and latency tracking configuration. Guards act as preconditions for rate limiting - if any guard fails (current latency exceeds threshold), the entire request is rejected and no tokens are consumed from any resource. This enables load shedding by preventing token consumption when the system is under stress.
Guard Semantics:
- Load Shedding: Guards protect against consuming tokens when latency indicates system stress
- Atomic Evaluation: All guards must pass for any tokens to be granted
- Real-time Metrics: Server provides current latency observations for client decision-making
- Self-Contained Configuration: Each guard carries its own latency tracking parameters
Guard Block (40 bytes total):
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| latency_tracker_id | 16 | Binary | Canonical content-defined latency-tracker identifier |
| ttl_ms | 4 | Integer | Time-to-live for latency samples in milliseconds |
| max_samples | 4 | Integer | Maximum number of samples to keep for latency tracking |
| buffer_size | 4 | Integer | Circular buffer size for latency tracking |
| min_sample_threshold | 4 | Integer | Minimum insertion rate required for reliable minimum |
| latency_threshold | 4 | Integer | Maximum acceptable latency (units must be uniform) |
| current_latency | 4 | Integer | Server fills with current observed latency (response only) |
Content-defined latency-tracker identity
Latency trackers do not require prior server-side registration. A client starts with an application-defined latency-tracker name, represented as a byte string, and the state-defining tracker configuration. A latency-tracker name identifies one logical latency signal within a tenant; it is not an r-server name, a DNS name, or necessarily a network service.
Before constructing either a guard block or a service latency block, a
conforming client MUST derive latency_tracker_id as:
first_16_bytes(
BLAKE2s-256(
"ratelimitly.latency-tracker.v1\0"
|| uint32_le(latency_tracker_name_length)
|| latency_tracker_name_bytes
|| uint32_le(ttl_ms)
|| uint32_le(max_samples)
|| uint32_le(buffer_size)
|| uint32_le(min_sample_threshold)
)
)
Here:
- the quoted domain expands to 30 ASCII bytes followed by one zero byte (31 bytes total);
- every length and configuration value is an unsigned 32-bit integer encoded in little-endian order;
-
latency_tracker_name_lengthis the number of bytes, not characters; - string-oriented APIs MUST encode names as UTF-8, while byte-oriented APIs MUST hash the exact supplied bytes, including embedded zero bytes;
-
the final effective configuration participates in the identity; when
buffer_sizeis omitted and resolved from the API-key limit, the resolved value MUST be used; - tenant identity is omitted because every latency-tracker map is tenant-isolated.
latency_threshold, current_latency, and observed_latency MUST NOT
participate in the identity. A threshold is a request-specific query over a
tracker, current_latency is a server result, and observed_latency is one
reported sample.
BLAKE2s-256 means the standard 32-byte digest. Implementations then retain
its first 16 bytes; they MUST NOT substitute the distinct BLAKE2s variant
configured with a 16-byte digest size. Shared known-answer vectors are in
latency_tracker_id_vectors.json.
An r-server MUST use the received 16-byte latency_tracker_id directly as
the tenant-local state key. A live entry also retains ttl_ms, max_samples,
buffer_size, and min_sample_threshold. If a guard or report presents the
same live ID with a different value for any of those fields, the server
MUST treat the containing PDU as malformed:
- a rate request must not mutate guard counters or consume resource tokens;
- a latency-report PDU must not record any partial set of samples;
- the mismatch is neither a guard failure nor a rate denial;
- because the MVP has no general error PDU, the server drops the packet;
- implementations SHOULD count the event with a bounded diagnostic metric and SHOULD NOT emit an unbounded per-packet log.
Once the latency-tracker state expires, the server may forget the binding and a later request may establish a new one.
Resource Blocks
Resource blocks are fixed 28-byte structures that specify rate limits and token requests. Each resource block contains:
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| bucket_id | 16 | Binary | Canonical content-defined resource identifier |
| window_size_ms | 4 |
u32 |
Rate-limiting window in milliseconds |
| rate_limit | 4 |
u32 |
Maximum tokens in the window (request) / actual rate (response) |
| tokens_requested | 2 | Integer | Number of tokens requested (request) / deficit (response) |
padding |
2 | Integer | Padding for 4-byte alignment (zero-copy parsing) |
Content-defined resource identity
Resources do not require prior server-side registration. A client starts with
an application-defined logical bucket name, represented as a byte string, and
the requested window_size_ms and rate_limit. Before constructing the
resource block, a conforming client MUST derive bucket_id as:
first_16_bytes(
BLAKE2s-256(
"ratelimitly.resource.v1\0"
|| uint32_le(bucket_name_length)
|| bucket_name_bytes
|| uint32_le(window_size_ms)
|| uint32_le(rate_limit)
)
)
Here:
- the quoted domain expands to 23 ASCII bytes followed by one zero byte (24 bytes total);
-
bucket_name_length,window_size_ms, andrate_limitare unsigned 32-bit integers encoded in little-endian order; -
bucket_name_lengthis the number of bytes, not characters; - string-oriented APIs MUST encode bucket names as UTF-8, while byte-oriented APIs MUST hash the exact supplied bytes, including embedded zero bytes;
-
tokens_requestedis operation-specific consumption and MUST NOT participate in the identity; - tenant identity is omitted because every rate-state map is tenant-isolated.
BLAKE2s-256 means the standard 32-byte digest. Implementations then retain
its first 16 bytes; they MUST NOT substitute the distinct BLAKE2s variant
configured with a 16-byte digest size. Shared known-answer vectors are in
resource_id_vectors.json.
The derivation always uses the configured request rate_limit. A response
reuses that wire field for actual_rate; clients MUST NOT derive a future
identifier from the response value.
An r-server MUST use the received 16-byte bucket_id directly as the
tenant-local state key. A live entry also retains the request’s
window_size_ms and rate_limit. If another request presents the same
bucket_id with a different value for either field, the server MUST treat
the request as malformed:
- no resource in the request may consume tokens or otherwise mutate rate state;
- the server MUST NOT encode the mismatch as a rate denial;
- because the MVP has no general error PDU, the server drops the packet and the client observes request failure;
- implementations SHOULD count the event with a bounded diagnostic metric and SHOULD NOT emit an unbounded per-packet log.
This validation detects client errors and the extremely unlikely truncated-hash
collision while the entry is live. Once its rate state expires, the server may
forget the binding. There is no separate resource TTL field:
window_size_ms determines when inactive rate state expires.
Rate Request TLV Parameters
The rate request PDU supports an optional metrics label TLV for aggregating user-facing metrics.
Metrics Label TLV Parameter
The metrics label TLV parameter is used to specify a label for user-facing metrics aggregation.
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| tlv_type | 2 | Integer | 0x4C4D (“ML” when viewed in hexdump) |
| tlv_size | 2 | Integer |
Overall size of TLV parameter (TLV body length = tlv_size - 4) |
| str_length | 2 | Integer | Number of bytes in byte string |
| label | varies | bytes | UTF-8 string for metrics aggregation |
| padding | varies | bytes | Optional padding to 4-byte boundary for alignment |
rate_response PDU
The format of the Ratelimitly rate_response PDU is defined in the following table. The response is built by modifying the request PDU in-place to achieve zero-allocation performance.
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| pdu_type | 2 | Integer | 0x5252 (“RR” when viewed in hexdump) |
| pdu_size | 2 | Integer |
Defines the overall size of the rate_response PDU (PDU body length = pdu_size - 8) |
| <<reserved>> | 2 | Integer | MUST be set to zeroes (reserved for future use) |
| <<reserved>> | 2 | Integer | MUST be set to zeroes (reserved for future use) |
| guard_count | 2 | Integer | Number of guard blocks (copied from request) |
| resource_count | 2 | Integer | Number of resource blocks (copied from request) |
| Guard Blocks | Variable | Binary | Guard blocks with current_latency filled (40 bytes each) |
| Resource Blocks | Variable | Binary | Resource blocks with tokens_requested→deficit, rate_limit→actual_rate |
| TLV Parameters | Variable | TLV | Optional server data (may differ from or omit request TLVs) |
Response Semantics
The rate_response PDU uses payload-driven status determination with atomic token consumption:
-
Success: All guard blocks have
current_latency < latency_thresholdAND all resource blocks havedeficit = 0- Token Consumption: All requested tokens are atomically consumed by the server
-
Guard Failure: Any guard block has
current_latency >= latency_threshold- Token Consumption: No tokens are consumed from any resource
-
Rate Limited: Any resource block has
deficit > 0- Token Consumption: No tokens are consumed from any resource
- Partial Information: Response shows which specific buckets lack capacity
Atomic Semantics: Token consumption is all-or-nothing. If any guard fails or any resource has insufficient tokens, the entire request is rejected and no tokens are consumed from any resource. This prevents partial resource consumption in multi-resource requests.
High Availability (HA) Support
The dual-purpose key_id field enables High Availability deployments where multiple R-server instances run simultaneously:
-
Server Identification: Each server overwrites the
key_idfield with its unique server identifier in responses -
Age Extraction: Clients can decode the server startup time directly from the high bits of
server_id - Client Correlation: Clients can compare the response identity with server identities learned through discovery
- Policy Input: The full identity and decoded startup time are available to a client-side response-selection policy
- Zero-Allocation Compatibility: Reusing existing field preserves in-place modification for response generation
- Collision Resistance: Port and node identity reduce accidental collisions between near-simultaneous restarts or replicas
The dual-purpose field provides optimal efficiency while enabling deterministic HA server identification. It does not define a response-selection, stability, quorum, or reliability policy.
server_id Decoding:
Clients and operators can decode response server_id values as follows:
-
start_s_since_2025 = server_id >> 23 -
listener_id = (server_id >> 15) & 0xFF -
node_id = server_id & 0x7FFF -
server_start_s = 1735689600 + start_s_since_2025
Client-Policy Boundary:
The wire protocol requires correct response authentication, request correlation, and the response server identity described above. It does not require a client to broadcast, wait for a quorum, prefer a particular server, track reliability, or filter a recently restarted server.
The parameterized oldest-first replicated-delivery strategy in
r-client.md is
one client usage of these fields. It is intentionally specified outside the
wire protocol.
HA Commit Safety (Open Study)
This protocol does not define cross-server commit coordination, quorum semantics, or exactly-once mutation across independent r-servers. In particular, per-server deduplication does not deduplicate a request across different servers.
Some deployments may later study stronger HA modes based on a single commit authority, strongly consistent shared state, read-only replicas, quorum decisions, or another coordination mechanism. Those are potential deployment directions, not requirements or guarantees of this wire protocol.
The client-side replicated usage documented in r-client.md intentionally
provides a weaker eventual-convergence model: clients may send resource
requests to multiple independent r-servers, and temporary disagreement is
possible until state converges through new traffic and TTL expiry. Client
response-validation rules (authentication, request correlation, and trusted
server identity) do not by themselves provide commit safety.
Response TLV Parameters
The response may optionally include TLV parameters that differ from the request:
- Omitted entirely: Server can drop TLV section to save bandwidth
- Server metrics: Performance counters, debug information
- Future extensions: Reserved for protocol evolution
Clients must not assume response TLVs match request TLVs.
Guard Block Response
In the response, guard blocks are updated with current server-observed latency metrics:
-
current_latencyis filled with real-time latency data for thelatency_tracker_id -
Client behavior: Clients MUST set
current_latency = 0in requests (ignored by server) -
Server behavior: Server sets
current_latencyto observed value or 0 if no data available -
If
current_latency >= latency_threshold, the entire request is rejected
Resource Block Response
In the response, resource blocks are updated with token grant results and rate information:
-
tokens_requestedfield is reused asdeficit(tokens that could not be granted) -
rate_limitfield is reused asactual_rate(current tokens consumed in the window) -
Field reuse semantics:
-
Request:
tokens_requested= tokens needed,rate_limit= minimum required rate -
Response:
tokens_requested= deficit (unfulfilled tokens),rate_limit= current actual rate
-
Request:
-
deficit = 0means all requested tokens were granted -
deficit > 0means some or all tokens were refused due to rate limiting -
actual_rateshows current bucket utilization for client decision-making
Latency Report PDU
The latency report PDU is a fire-and-forget message used by clients to report observed latency metrics to the server for load shedding decisions.
Fire-and-Forget Behavior: Servers MUST NOT send responses to latency report PDUs. Clients send these reports and do not expect any acknowledgment.
Latency Report PDU:
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| pdu_type | 2 | Integer | 0x524C (“LR” when viewed in hexdump) |
| pdu_size | 2 | Integer |
Defines the overall size of the latency_report PDU (PDU body length = pdu_size - 8) |
reserved |
2 | Integer | MUST be set to zeroes (reserved for future use) |
reserved |
2 | Integer | MUST be set to zeroes (reserved for future use) |
| service_count | 2 | Integer | Number of service latency blocks that follow |
padding |
2 | Integer | Padding for 4-byte alignment |
| Service Blocks | Variable | Binary | service_count × 36-byte service latency blocks |
For latency reports, pdu_size MUST equal 12 + service_count * 36.
Service Latency Block (36 bytes each)
Each service latency block contains the complete service definition plus observation:
Service Latency Block (36 bytes):
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| latency_tracker_id | 16 | Binary | Canonical content-defined latency-tracker identifier |
| ttl_ms | 4 | Integer | Time-to-live for latency samples in milliseconds |
| max_samples | 4 | Integer | Maximum number of samples to keep for latency tracking |
| buffer_size | 4 | Integer | Circular buffer size for latency tracking |
| min_sample_threshold | 4 | Integer | Minimum insertion rate required for reliable minimum |
| observed_latency | 4 | Integer | Client-observed latency (units must be uniform) |
Administrative PDUs
Administrative PDUs use the same authenticated UDP channel as tenant mutation traffic, but are not limited to tenant lifecycle operations. This administrative channel is intended for control-plane and operator actions such as tenant configuration changes and metrics collection.
All administrative PDUs MUST be authenticated and encrypted using AES-256-GCM with a dedicated administrative key.
Administrative Authentication
All administrative PDUs must use AES-256-GCM authentication (TLV_AUTH_AES =
0x4541) with a dedicated administrative key that is separate from regular
tenant keys.
Admin Key Configuration:
tenant_management:
auth:
type: aes
key: "rl-secret1..."
Authentication Process:
- Encryption: Administrative interfaces encrypt administrative PDUs using the tenant management key
- Decryption: Server decrypts and authenticates operations using the same tenant management key
- Authorization: Only requests encrypted with the correct tenant management key are processed
-
Key Material: The tenant management key is provisioned as an
rl-secret...Bech32 string carrying exactly one raw 32-byte AES key
Key Usage Rules:
-
Administrative operations (
tenant_mgmt_flag=1): MUST use the dedicated tenant management key -
Regular operations (
tenant_mgmt_flag=0): Use tenant-specific keys from tenant configuration -
Key configuration: Tenant management key is provided at server startup as
rl-secret -
Runtime keys: Tenant-specific keys are delivered at runtime via tenant management requests as
rl-none,rl-cookie, orrl-aes
Security Considerations:
- Tenant management key must be kept separate from regular tenant keys
- Only authorized administrative interfaces should have access to this key
- Different servers can use different tenant management keys for isolation
- Key rotation should be coordinated between administrative interfaces and servers
Tenant Mutation PDU
The tenant_mutation PDU initiates tenant configuration operations:
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| pdu_type | 2 | Integer | 0x544D (“TM” when viewed in hexdump) |
| pdu_size | 2 | Integer |
Overall size of the tenant_mutation PDU (PDU body length = pdu_size - 8) |
| operation | 1 | Integer | Tenant operation type (see operation types below) |
| flags | 1 | Integer | Operation flags (reserved, MUST be zero) |
| dedup_ttl_ms | 4 | Integer | Required replay-window request in milliseconds |
| Mutation Data | Variable | Binary | Operation-specific payload |
dedup_ttl_ms (Tenant Mutation)
dedup_ttl_ms is a required replay-window request for tenant-management
mutations.
Its purpose is reply recovery rather than mutation correctness. Tenant-management operations in this proof-of-concept are idempotent enough that re-execution is acceptable if replay state has been lost.
Healthy-state behavior:
-
the server should cache the first
Tenant Ackfor the request key and replay it for duplicates received within the accepted dedup window
Degraded-state behavior:
-
if replay state has been lost, the server may execute the mutation again and return a fresh
Tenant Ack -
replay-state loss must be surfaced through
ERRORlogs and unhealthy deduplication metrics/state
Operation Types:
| Operation Code | Name | Description |
|---|---|---|
| 0x03 | TENANT_ADD | Add new tenant configuration |
| 0x05 | TENANT_REMOVE | Remove tenant configuration |
Thread Steering:
The key_id field in the Tenant Header contains the target tenant ID for the operation, ensuring:
-
ADD operations: New tenant assigned to appropriate thread via
hash(tenant_id) % num_threads - REMOVE operations: Deletions occur on the thread that owns the tenant
This maintains thread affinity and mutual exclusion for all tenant operations.
Mutation Data Formats:
ADD Tenant (Operation 0x03):
Behavior: Upsert operation - creates new tenant or overwrites existing tenant with the same ID.
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| name_len | 1 | Integer | Length of tenant name (0-255) |
| name | Variable | Binary | UTF-8 encoded tenant name |
| tenant_key_len | 1 | Integer | Length of tenant credential string (0-255) |
| tenant_key | Variable | Binary |
UTF-8 Bech32 tenant credential: rl-none, rl-cookie, or rl-aes |
The tenant_key embeds all tenant authentication and quota data:
- authentication method (via HRP)
-
key_id - secret material, if required by the auth method
-
rate_buckets_max -
latency_services_max -
metrics_labels_max -
latency_buffer_size_max -
dedup_ttl_ms_max -
rate_window_size_ms_max
Deterministic validation rules:
- Unknown tenant-key HRPs MUST cause the entire tenant mutation to be rejected.
-
tenant_keyMUST decode according to the payload shape mandated by its HRP. -
The decoded
key_idMUST equal the Tenant Headerkey_id. - Rejected tenant mutations MUST follow tenant mutation failure behavior (no acknowledgment response).
Before admitting an ADD, a server MUST calculate the runtime storage implied by
the decoded quotas and compare it with its current allocation budget and
available system/cgroup memory. If the estimate overflows or exceeds available
headroom, the server MUST reject the mutation before publishing any tenant
state. An operating-system allocation failure MUST likewise be handled as a
tenant mutation failure, not a process panic. Such failures increment the
server status field allocation_failures_total.
REMOVE Tenant (Operation 0x05):
No additional payload required. The tenant ID to remove is specified in the key_id field of the Tenant Header.
Simplified Configuration Data Format
Tenant mutation payloads use length-prefixed binary fields for optimal parsing. The tenant_key field is a UTF-8 Bech32 string whose decoded payload carries the tenant authentication material and quotas.
Quota configuration is embedded directly in the Bech32 tenant credential. This keeps tenant authentication material and tenant quotas in one canonical string.
Quota Enforcement Semantics
Quota enforcement uses request-level atomic behavior, with one exception for metrics label cardinality control:
-
Rate bucket quota exceeded (
rate_buckets_max): Request is rejected atomically (no tokens consumed). -
Latency service quota exceeded (
latency_services_max): Request/report is rejected atomically. -
Latency shape exceeded: Request/report is rejected atomically if
buffer_size > latency_buffer_size_max. -
Rate-window shape exceeded: A rate request is rejected atomically if any
resource has
window_size_ms > rate_window_size_ms_max. Conforming clients MUST validate the complete logical request before DNS resolution, serialization, or transmission. The server independently validates every resource before evaluating guards or creating/updating either rate-counter or latency-tracker state. An over-quota authenticated request receives no response. -
Metrics labels quota exceeded (
metrics_labels_max): Request is still processed, but label is rewritten to the fixed overflow labeloverflow.
This preserves strict all-or-nothing resource semantics while bounding metrics-label cardinality without turning otherwise valid requests into hard failures.
rate_buckets_max and latency_services_max bound live logical entries even
when an implementation uses a larger minimum physical table for efficient map
geometry. Physical spare slots MUST NOT increase either API-key quota.
Servers expose rejected request shapes through the bounded global Prometheus
counter ratelimitly_api_key_quota_rejections_total. It intentionally has no
API-key, resource, or service labels.
The server MUST use latency_buffer_size_max as the inline monotonic-point
capacity of every latency-service arena slot for the tenant. A service may
request a smaller active buffer_size, but that does not change the tenant’s
uniform slot stride. The arena backing is heap/mmap memory; the slot itself
MUST contain no reference or pointer to external sample storage.
Authentication Type Selection:
The tenant authentication method is selected by the tenant_key HRP:
-
rl-none= no tenant request authentication -
rl-cookie= Cookie authentication -
rl-aes= AES-256-GCM authentication
Upsert Semantics:
The ADD operation provides upsert behavior:
- CREATE: If tenant ID doesn’t exist, creates new tenant
- OVERWRITE: If tenant ID exists, completely replaces existing configuration
Tenant Acknowledgment PDU
The tenant_ack PDU provides acknowledgment responses only for successful tenant mutation operations:
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| pdu_type | 2 | Integer | 0x5441 (“TA” when viewed in hexdump) |
| pdu_size | 2 | Integer |
Overall size of the tenant_ack PDU (always 12; PDU body length = pdu_size - 8 = 4) |
| operation | 1 | Integer | Original operation type that was processed |
| status | 1 | Integer | Always SUCCESS (0x00) - only sent for successful operations |
reserved |
2 | Integer | MUST be set to zeroes (reserved for future use) |
reserved |
2 | Integer | MUST be set to zeroes (reserved for future use) |
padding |
2 | Integer | Padding for 4-byte alignment (MUST be zero) |
Status Codes:
| Status Code | Name | Description |
|---|---|---|
| 0x00 | SUCCESS | Operation completed successfully |
Acknowledgment Behavior:
- Success: Server sends tenant_ack PDU with status SUCCESS (0x00)
- Any Failure: Server sends no response (blackhole behavior for authentication failures, validation errors, etc.)
Tenant mutation failures include (non-exhaustive): authentication/tag failure, malformed payload, non-zero reserved fields, unknown operation, invalid Bech32 credential, mismatched key_id, and invalid quota values.
This design ensures that only successful operations generate network traffic, maintaining the fire-and-forget performance characteristics while providing confirmation of successful mutations.
Status Query PDU
The status_query PDU requests a single queue-local status snapshot from the
administrative channel. This PDU is read-only, does not mutate tenant/API-key
state, and does not reuse Tenant Ack.
This is the canonical UDP health/status probe for control-plane and operator tooling. Implementations MUST NOT use synthetic tenant mutations as a status probe when this PDU is available.
A Status Query is intentionally not scoped to an API key. The request Tenant
Header key_id field SHOULD be zero and MUST NOT be interpreted as an API key
identifier by the server. The response Tenant Header key_id is overwritten
with the responding server_id, allowing the client to identify which listener
produced the snapshot.
One Status Query produces at most one Status Response. The response is a
fixed-width binary PDU so the one-frame guarantee is mechanical: no strings,
interface names, labels, API-key lists, or other variable-length values are
included in V1.
Status queries are scoped to the listener queue that receives the UDP datagram. The server MUST NOT require cross-queue aggregation or hot-path synchronization to answer the query. Node-wide values included in the response, such as Linux memory snapshots, MUST be cheap point-in-time readings or cached snapshots.
Authentication failures remain blackhole/no-response behavior. Status codes in the response apply only after successful administrative authentication and PDU parsing.
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| pdu_type | 2 | Integer | 0x5153 (“SQ” when viewed in hexdump) |
| pdu_size | 2 | Integer | Overall size of the status_query PDU; MUST be 8 |
| schema_version | 1 | Integer | Status schema version; MUST be 1 for V1 |
| query_kind | 1 | Integer | Query variant; 0x00 = CURRENT_STATUS |
reserved |
2 | Integer | MUST be zero |
query_kind values:
| query_kind | Name | Query Data |
|---|---|---|
| 0x00 | CURRENT_STATUS | empty |
Status Response PDU
The status_response PDU returns one fixed-width V1 status record for an authenticated administrative status request.
The V1 PDU size is exactly 448 bytes. With the existing administrative response envelope this is approximately:
- Tenant TLV: 40 bytes
- AES Auth TLV: 32 bytes
- encrypted status_response PDU: 448 bytes
- total UDP payload: 520 bytes
This remains below the current 1200-byte packet budget.
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| pdu_type | 2 | Integer | 0x5253 (“SR” when viewed in hexdump) |
| pdu_size | 2 | Integer | Overall size of the status_response PDU; MUST be 448 |
| schema_version | 1 | Integer | Status schema version; MUST be 1 for V1 |
| status_code | 1 | Integer | 0x00 = OK; non-zero values reserved for future errors |
| status_flags | 2 | Bitset | Validity/status bitset for optional snapshot fields |
| wire_protocol_version | 2 | Integer | Wire protocol version understood by this server |
| record_len | 2 | Integer | Bytes after the first 8-byte status header; MUST be 440 |
| server_id | 8 | Integer | Structured response server id |
| node_id | 4 | Integer | Configured server node id |
| listener_id | 2 | Integer |
Listener id encoded into server_id |
| queue_index | 2 | Integer | Listener queue index that answered |
| udp_port | 2 | Integer | UDP port that answered |
| degraded_reason_code | 2 | Integer | 0 = none; non-zero values reserved for degraded reasons |
| started_at_ms | 8 | Integer | Unix epoch milliseconds for server/listener start |
| uptime_ms | 8 | Integer | Listener/server uptime in milliseconds |
| build_fingerprint | 8 | Integer | Fixed build id/hash prefix; zero if unavailable |
| health_flags | 4 | Bitset | Ready/datapath/accepting flags |
| backend | 2 | Integer | 0 = unknown, 1 = XDP, 2 = UDP fallback |
| xdp_mode | 2 | Integer | 0 = n/a, 1 = zero-copy, 2 = copy, 3 = disabled |
| ifindex | 4 | Integer |
Interface index, or u32::MAX if unavailable |
| rx_queue_id | 4 | Integer |
RX queue id, or u32::MAX if unavailable |
| worker_cpu | 4 | Integer |
Worker CPU id, or u32::MAX if unavailable |
reserved0 |
4 | Integer | MUST be zero |
| rx_packets | 8 | Integer | Queue-local packets received by this listener |
| tx_packets | 8 | Integer | Queue-local packets answered by this listener |
| rx_dropped | 8 | Integer | Queue-local receive/drop counter |
| tx_dropped | 8 | Integer | Queue-local transmit/drop counter |
| parse_errors | 8 | Integer | Queue-local protocol parse failures |
| auth_failures | 8 | Integer | Queue-local authentication failures |
| admin_requests_total | 8 | Integer | Queue-local administrative request attempts |
| system_memory_total_bytes | 8 | Integer |
Node memory total, or u64::MAX if unknown |
| system_memory_available_bytes | 8 | Integer |
Node memory available, or u64::MAX if unknown |
| cgroup_memory_limit_bytes | 8 | Integer |
Cgroup memory limit, or u64::MAX if unconstrained/unknown |
| process_rss_bytes | 8 | Integer |
Process resident set size, or u64::MAX if unknown |
| rl_memory_budget_bytes | 8 | Integer |
RateLimitly memory budget, or u64::MAX if unknown |
| rl_memory_allocated_bytes | 8 | Integer | RateLimitly allocated/estimated runtime state bytes |
| rl_memory_headroom_bytes | 8 | Integer |
Budget minus allocated bytes, or u64::MAX if unknown |
| dedup_cache_bytes | 8 | Integer | This listener’s dedup allocation/estimate |
| api_key_rate_map_bytes | 8 | Integer | Admitted API-key rate-map allocation/estimate |
| api_key_latency_map_bytes | 8 | Integer | Admitted API-key latency-map allocation/estimate |
| api_key_latency_buffer_bytes | 8 | Integer | Admitted latency-buffer allocation/estimate |
| api_key_metrics_label_bytes | 8 | Integer | Admitted metrics-label allocation/estimate |
| other_runtime_bytes | 8 | Integer | Remaining estimated RateLimitly runtime state |
| allocation_failures_total | 8 | Integer | Allocation/admission failures |
| api_keys_loaded | 8 | Integer | API keys loaded on this listener |
| api_key_capacity | 8 | Integer |
Admitted API-key capacity/budget, or u64::MAX if unknown |
| api_key_table_generation | 8 | Integer | Monotonic generation for API-key table changes |
| api_key_table_hash | 8 | Integer | Fixed hash of loaded API-key ids and quotas |
| rate_bucket_quota_sum | 8 | Integer | Sum across admitted API keys |
| latency_service_quota_sum | 8 | Integer | Sum across admitted API keys |
| metrics_label_quota_sum | 8 | Integer | Sum across admitted API keys |
| latency_buffer_quota_sum | 8 | Integer | Sum across admitted API keys |
| rate_requests_total | 8 | Integer | Queue-local rate request counter |
| rate_success_total | 8 | Integer | Queue-local successful rate request counter |
| rate_limited_total | 8 | Integer | Queue-local rate-limited request counter |
| guard_failed_total | 8 | Integer | Queue-local guard-failed request counter |
| latency_reports_total | 8 | Integer | Queue-local accepted latency-report service-block counter |
| dedup_hits_total | 8 | Integer | Queue-local dedup hit counter |
| dedup_misses_total | 8 | Integer | Queue-local dedup miss counter |
| tenant_mutations_total | 8 | Integer | Queue-local tenant/API-key mutation counter |
| tenant_mutation_failures_total | 8 | Integer | Queue-local tenant/API-key mutation failure counter |
| dedup_ttl_ms_max | 4 | Integer |
Server-side maximum accepted dedup_ttl_ms for this listener, or 0 if unknown |
reserved1 |
60 | Binary | MUST be zero in V1; reserved for fixed-width growth |
dedup_ttl_ms_max is the listener/server cap, not the API-key quota embedded in
an rl-* credential. Clients SHOULD bound request retry/replay windows by
min(api_key.dedup_ttl_ms_max, status.dedup_ttl_ms_max) when the
DEDUP_TTL_MS_MAX_VALID status flag is set. If that flag is not set, clients
MUST treat dedup_ttl_ms_max as unknown; this preserves compatibility with
older V1 servers that still return zero in the formerly reserved bytes.
status_flags validity bits:
| Bit | Name | Meaning |
|---|---|---|
| 0 | SYSTEM_MEMORY_TOTAL_VALID |
system_memory_total_bytes is known |
| 1 | SYSTEM_MEMORY_AVAILABLE_VALID |
system_memory_available_bytes is known |
| 2 | CGROUP_MEMORY_LIMIT_VALID |
cgroup_memory_limit_bytes is constrained and known |
| 3 | PROCESS_RSS_VALID |
process_rss_bytes is known |
| 4 | RL_MEMORY_BUDGET_VALID |
rl_memory_budget_bytes is known |
| 5 | RL_MEMORY_ALLOCATED_VALID |
rl_memory_allocated_bytes is known |
| 6 | RL_MEMORY_HEADROOM_VALID |
rl_memory_headroom_bytes is known |
| 7 | DEDUP_TTL_MS_MAX_VALID |
dedup_ttl_ms_max is known |
Fields whose validity bit is not set MUST be treated as unknown by clients,
regardless of the numeric value carried in the field. Clients SHOULD prefer
rl_memory_headroom_bytes over raw Linux MemAvailable for allocation
decisions when the headroom validity bit is set.
health_flags bits:
| Bit | Name | Meaning |
|---|---|---|
| 0 | READY | Listener is initialized enough to answer status |
| 1 | DATAPATH_RUNNING | Datapath/listener loop is running |
| 2 | ACCEPTING_RATE_REQUESTS | Listener accepts rate request PDUs |
| 3 | ACCEPTING_LATENCY_REPORTS | Listener accepts latency report PDUs |
| 4 | ACCEPTING_TENANT_MUTATIONS | Listener accepts tenant/API-key mutation PDUs |
For V1, CP may consider a queue healthy when all of the following are true:
- the response authenticates under the instance’s administrative key
-
status_code == 0 -
health_flagsincludes READY, DATAPATH_RUNNING, ACCEPTING_RATE_REQUESTS, ACCEPTING_LATENCY_REPORTS, and ACCEPTING_TENANT_MUTATIONS -
udp_portandqueue_indexmatch the probed queue endpoint
Metrics Query PDU
The metrics_query PDU requests a bounded metrics snapshot from the
administrative channel. This PDU is read-only. It does not participate in
replay guarantees and does not reuse Tenant Ack.
This administrative UDP path is the primary protocol interface for metrics retrieval. Implementations may also expose an optional HTTP/Prometheus endpoint, but that surface is secondary and mainly useful for testing, development, or local operator convenience.
Metrics queries are intentionally scoped to the responding listener thread. This matches the current Rust implementation, where metrics are collected and exposed per listener thread today. The protocol MUST NOT require cross-thread aggregation or synchronization for metrics retrieval.
Every Metrics Query is also scoped to exactly one tenant. The Tenant Header
key_id field in the request MUST contain the target tenant_id. This keeps
metrics requests on the same queue-local routing path as other tenant-scoped
operations. The response still overwrites the Tenant Header key_id with the
responding server_id, allowing the client to identify which listener produced
the page.
One Metrics Query produces at most one Metrics Response. Pagination is
therefore request-driven: the client issues another MQ after the last entry
from the previous MR.
Within one listener processing batch, an implementation MUST process at most one
Metrics Query for a given tenant. This preserves queue-local batching without
creating repeated per-tenant metrics work inside one batch.
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| pdu_type | 2 | Integer | 0x514D (“MQ” when viewed in hexdump) |
| pdu_size | 2 | Integer |
Overall size of the metrics_query PDU (PDU body length = pdu_size - 8) |
| metric_family | 1 | Integer | Requested metric family (see family codes below) |
| query_kind | 1 | Integer | Variant tag within the family (see below) |
reserved |
2 | Integer | MUST be zero |
| Query Data | Variable | Binary | Family-specific payload |
Metric Family Codes:
| Family Code | Name | Record Type Returned |
|---|---|---|
| 0x01 | TENANT_SUMMARY | One Tenant Summary record |
| 0x02 | TENANT_LABEL_COUNTERS | Tenant Label Counter records |
| 0x03 | TENANT_SERVICE_LATENCY_COUNTERS | Tenant Service Latency Counter records |
metric_family and query_kind form a tagged union.
Query Variants
TENANT_SUMMARY:
| query_kind | Name | Query Data |
|---|---|---|
| 0x00 | SUMMARY_CURRENT | empty |
TENANT_LABEL_COUNTERS:
| query_kind | Name | Query Data |
|---|---|---|
| 0x00 | LABELS_FROM_START | empty |
| 0x01 | LABELS_FROM_CURSOR |
cursor_len: u16, reserved: u16, cursor, cursor_padding |
TENANT_SERVICE_LATENCY_COUNTERS:
| query_kind | Name | Query Data |
|---|---|---|
| 0x00 | SERVICES_FROM_START | empty |
| 0x01 | SERVICES_FROM_CURSOR |
cursor_len: u16, reserved: u16, cursor, cursor_padding |
Pagination rules:
-
The server MUST fill each
MRpage with the maximal contiguous slice that fits in one datagram under the implementation packet-size budget. -
The server MUST return records in an implementation-defined order that is consistent enough for best-effort cursor continuation on that queue:
-
TENANT_SUMMARY: exactly one record when the tenant exists on the queue -
TENANT_LABEL_COUNTERS: implementation-defined label order -
TENANT_SERVICE_LATENCY_COUNTERS: implementation-defined service-record order
-
- Pagination is cursor-based and the cursor is opaque to the client.
-
For both
TENANT_LABEL_COUNTERSandTENANT_SERVICE_LATENCY_COUNTERS, the client resumes by taking the opaque cursor from*_PAGE_MOREand issuing the corresponding*_FROM_CURSORquery. - Clients MUST NOT interpret, modify, or construct cursor contents on their own. They MUST echo the cursor bytes exactly as received.
- The server MAY implement cursors statefully or statelessly.
- The server SHOULD make cursor continuation best-effort across storage mutation.
- A cursor MAY become invalid or expire under implementation-defined conditions. The response MUST then use the explicit cursor-invalid page kind for the requested family.
-
A
Metrics Querydoes not create a durable snapshot across pages. Each page is a best-effort point-in-time view when encoded. -
If the response
server_idchanges between pages, the client SHOULD restart pagination from the beginning because the listener thread likely restarted. - Global or cross-tenant counters are intentionally out of scope for the UDP administrative metrics protocol. They may remain available only through optional Prometheus export or logs.
Metrics Response PDU
The metrics_response PDU returns either a page of records or an explicit query error for an authenticated administrative metrics request.
Authentication failures remain blackhole/no-response behavior. Status codes below apply only after successful administrative authentication and PDU parsing.
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| pdu_type | 2 | Integer | 0x524D (“MR” when viewed in hexdump) |
| pdu_size | 2 | Integer |
Overall size of the metrics_response PDU (PDU body length = pdu_size - 8) |
| metric_family | 1 | Integer | Echo of the requested metric family |
| page_kind | 1 | Integer | Variant tag within the family (see below) |
reserved |
2 | Integer | MUST be zero |
| Page Data | Variable | Binary | Family-specific page payload |
metric_family and page_kind form a tagged union.
Response Variants
TENANT_SUMMARY:
| page_kind | Name | Page Data |
|---|---|---|
| 0x00 | SUMMARY_PAGE | one Tenant Summary record |
| 0x01 | METRICS_NOT_FOUND | empty |
| 0x02 | METRICS_INVALID_QUERY | empty |
| 0x03 | METRICS_UNSUPPORTED | empty |
| 0x04 | METRICS_INTERNAL_ERROR | empty |
TENANT_LABEL_COUNTERS:
| page_kind | Name | Page Data |
|---|---|---|
| 0x00 | LABELS_PAGE_MORE |
next_cursor_len: u16, reserved: u16, next_cursor, cursor_padding, one or more Tenant Label Counter records |
| 0x01 | LABELS_PAGE_LAST | one or more Tenant Label Counter records |
| 0x02 | LABELS_EMPTY | empty |
| 0x03 | METRICS_NOT_FOUND | empty |
| 0x04 | METRICS_INVALID_QUERY | empty |
| 0x05 | METRICS_UNSUPPORTED | empty |
| 0x06 | METRICS_INTERNAL_ERROR | empty |
| 0x07 | METRICS_RECORD_TOO_LARGE | empty |
| 0x08 | METRICS_CURSOR_INVALID | empty |
TENANT_SERVICE_LATENCY_COUNTERS:
| page_kind | Name | Page Data |
|---|---|---|
| 0x00 | SERVICES_PAGE_MORE |
next_cursor_len: u16, reserved: u16, next_cursor, cursor_padding, one or more Tenant Service Latency Counter records |
| 0x01 | SERVICES_PAGE_LAST | one or more Tenant Service Latency Counter records |
| 0x02 | SERVICES_EMPTY | empty |
| 0x03 | METRICS_NOT_FOUND | empty |
| 0x04 | METRICS_INVALID_QUERY | empty |
| 0x05 | METRICS_UNSUPPORTED | empty |
| 0x06 | METRICS_INTERNAL_ERROR | empty |
| 0x07 | METRICS_RECORD_TOO_LARGE | empty |
| 0x08 | METRICS_CURSOR_INVALID | empty |
Empty variants mean:
- the tenant exists on the queue
- the query is valid
- there are no records in the requested family at the requested start position or cursor position
METRICS_RECORD_TOO_LARGE means:
- the tenant exists on the queue
- the query is valid
- the next record in the requested family cannot fit into a single UDP response page under the implementation packet-size budget
- clients SHOULD treat this as a hard stop for that cursor position rather than retrying the same query unchanged
METRICS_CURSOR_INVALID means:
- the tenant exists on the queue
- the requested cursor is syntactically well-formed at the PDU level
- but the server cannot continue that pagination stream from the supplied cursor
Record counts are intentionally omitted from the wire format. Records are parsed until the end of the PDU body using the family-specific entry layout.
Metrics Record Formats
Tenant Summary record (family 0x01)
This record encodes the tenant-scoped summary counters currently exposed by the Rust collector for one tenant on one queue.
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| requests_success | 8 | Integer |
ratelimitly_tenant_requests_total{result="success"} |
| requests_rate_limited | 8 | Integer |
ratelimitly_tenant_requests_total{result="rate_limited"} |
| requests_guard_failed | 8 | Integer |
ratelimitly_tenant_requests_total{result="guard_failed"} |
| requests_auth_failed | 8 | Integer |
ratelimitly_tenant_requests_total{result="auth_failed"} |
| service_latency_reports_total | 8 | Integer | Total accepted latency-report service blocks for the tenant on this queue |
The counter semantics for this record are:
-
requests_success: increments once per successful rate-limit request accepted for the tenant -
requests_rate_limited: increments once per rate-limit request rejected for insufficient bucket capacity -
requests_guard_failed: increments once per rate-limit request rejected by latency guards -
requests_auth_failed: increments once per tenant request that fails authentication -
service_latency_reports_total: increments once for each accepted latency-report service block for the tenant
The UDP administrative metrics protocol intentionally excludes:
- global or cross-tenant counters
- any metric that would require cross-thread aggregation
-
Prometheus-derived gauges that are not primary counter state, including:
-
ratelimitly_batch_size_avg -
ratelimitly_batch_processing_time_avg_us -
ratelimitly_batch_processing_rate_items_per_second
-
These values are explicitly out of scope for the binary administrative metrics protocol. If operators still want them emitted directly by the server, they SHOULD be moved to informational or debug logging rather than encoded as protocol metrics.
Tenant Label Counter record (family 0x02)
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| requests_success | 8 | Integer | Label-scoped success count |
| requests_rate_limited | 8 | Integer | Label-scoped rate-limited count |
| requests_guard_failed | 8 | Integer | Label-scoped guard-failed count |
| requests_auth_failed | 8 | Integer | Label-scoped auth-failed count |
| label_len | 2 | Integer | Label length in bytes |
| padding | 2 | Integer | Padding to 4-byte alignment |
| label | Variable | Binary | UTF-8 label bytes |
| label_padding | Variable | Binary | Zero padding to 4-byte alignment |
This record maps to:
-
ratelimitly_tenant_label_requests_total{tenant_id=<request tenant>,label=...,result="success"} -
ratelimitly_tenant_label_requests_total{tenant_id=<request tenant>,label=...,result="rate_limited"} -
ratelimitly_tenant_label_requests_total{tenant_id=<request tenant>,label=...,result="guard_failed"} -
ratelimitly_tenant_label_requests_total{tenant_id=<request tenant>,label=...,result="auth_failed"}
Tenant Service Latency Counter record (family 0x03)
| Field name | Size (bytes) | Type | Description |
|---|---|---|---|
| latency_tracker_id | 16 | Binary | Canonical content-defined latency-tracker identifier |
| ttl_ms | 4 | Integer | Time-to-live for latency samples in milliseconds |
| max_samples | 4 | Integer | Maximum number of samples to keep for latency tracking |
| buffer_size | 4 | Integer | Circular buffer size for latency tracking |
| min_sample_threshold | 4 | Integer | Minimum insertion rate required for reliable minimum |
| report_count | 8 | Integer | Number of latency report blocks received for this service spec |
| checks_total | 8 | Integer | Number of latency guard evaluations performed for this service spec |
| checks_passed | 8 | Integer | Number of latency guard evaluations that passed for this service spec |
| checks_failed | 8 | Integer | Number of latency guard evaluations that failed for this service spec |
For this record family, latency_tracker_id is the uniqueness key. The
remaining tracker-definition fields are repeated as validated metadata for
diagnostics and interpretation.
Administrative metrics implementations MAY render the ID and definition for humans as:
-
<latency_tracker_id_hex>:<ttl_ms>:<max_samples>:<buffer_size>:<min_sample_threshold>
The counter semantics for this record are:
-
report_count: increments once for each latency-report block accepted for the service spec -
checks_total: increments once for each guard evaluation against the service spec in a rate-limit request -
checks_passed: increments when that guard evaluation does not reject the guard -
checks_failed: increments whencurrent_latency >= latency_thresholdandlatency_threshold > 0
Metrics PDU Examples
The following examples show plaintext MQ and MR PDUs before AES-GCM
encryption and before wrapping with the packet-level Tenant Header and Auth TLV.
In a real administrative packet:
-
the request Tenant Header
key_idis the targettenant_id -
the response Tenant Header
key_idis overwritten with the respondingserver_id - the full packet is carried on the administrative AES-authenticated channel
All integers below are little-endian.
Example 1: TENANT_SUMMARY current snapshot
Request meaning:
-
target tenant:
42 - ask for the current summary counters for that tenant on the addressed queue
Plaintext MQ bytes:
4d 51 08 00 01 00 00 00
^-----^---- ^--^ ^---^
type size fam/k reserved
Field decode:
-
pdu_type = 0x514D(MQ) -
pdu_size = 8 -
metric_family = 0x01(TENANT_SUMMARY) -
query_kind = 0x00(SUMMARY_CURRENT) -
reserved = 0x0000
Response meaning:
-
success =
100 -
rate_limited =
3 -
guard_failed =
1 -
auth_failed =
0 -
service_latency_reports_total =
9
Plaintext MR bytes:
4d 52 30 00 01 00 00 00
64 00 00 00 00 00 00 00
03 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
09 00 00 00 00 00 00 00
Field decode:
-
pdu_type = 0x524D(MR) -
pdu_size = 48 -
metric_family = 0x01(TENANT_SUMMARY) -
page_kind = 0x00(SUMMARY_PAGE) -
requests_success = 100 -
requests_rate_limited = 3 -
requests_guard_failed = 1 -
requests_auth_failed = 0 -
service_latency_reports_total = 9
Example 2: first page of TENANT_LABEL_COUNTERS
Request meaning:
-
target tenant:
42 - ask for the first label page in server order
Plaintext MQ bytes:
4d 51 08 00 02 00 00 00
Field decode:
-
metric_family = 0x02(TENANT_LABEL_COUNTERS) -
query_kind = 0x00(LABELS_FROM_START)
Response meaning:
- this page is not the last page
- the server provides an opaque cursor for the next page
-
it carries two records:
-
label
""with counters(8, 1, 0, 0) -
label
"api"with counters(50, 2, 1, 0)
-
label
Plaintext MR layout:
MR header:
pdu_type = 0x524D
pdu_size = 92
metric_family = 0x02
page_kind = 0x00 ; LABELS_PAGE_MORE
reserved = 0x0000
Cursor prefix:
next_cursor_len = 4
reserved = 0
next_cursor = de ad be ef
Record 1:
requests_success = 8
requests_rate_limited = 1
requests_guard_failed = 0
requests_auth_failed = 0
label_len = 0
padding = 0
label = ""
label_padding = ""
Record 2:
requests_success = 50
requests_rate_limited = 2
requests_guard_failed = 1
requests_auth_failed = 0
label_len = 3
padding = 0
label = "api"
label_padding = 00
Resume rule:
-
the client takes the opaque
next_cursorfrom the page -
the next request uses
LABELS_FROM_CURSOR
Example 3: resume TENANT_LABEL_COUNTERS from opaque cursor
Plaintext MQ bytes:
4d 51 10 00 02 01 00 00 04 00 00 00 de ad be ef
Field decode:
-
pdu_type = 0x514D(MQ) -
pdu_size = 16 -
metric_family = 0x02(TENANT_LABEL_COUNTERS) -
query_kind = 0x01(LABELS_FROM_CURSOR) -
cursor_len = 4 -
cursor = de ad be ef
Suppose the remaining label set begins with overflow and all remaining records
fit in one datagram. Then the response uses LABELS_PAGE_LAST.
Plaintext MR layout:
MR header:
pdu_type = 0x524D
pdu_size = 52
metric_family = 0x02
page_kind = 0x01 ; LABELS_PAGE_LAST
reserved = 0x0000
Record 1:
requests_success = 7
requests_rate_limited = 4
requests_guard_failed = 0
requests_auth_failed = 0
label_len = 8
padding = 0
label = "overflow"
Example 4: empty TENANT_SERVICE_LATENCY_COUNTERS from start
Request meaning:
-
target tenant:
42 - ask for the first service-counter page
- the tenant exists, but there are no recorded service counters yet
Plaintext MQ bytes:
4d 51 08 00 03 00 00 00
Plaintext MR bytes:
4d 52 08 00 03 02 00 00
Field decode:
-
metric_family = 0x03(TENANT_SERVICE_LATENCY_COUNTERS) -
page_kind = 0x02(SERVICES_EMPTY)