hermes-android/docs/pairing-protocol-v1.md

4.7 KiB

Hermes Pairing Protocol v1 (hermes-pair)

1. URI Format

The pairing URI is used to transmit connection information securely to the Hermes client, typically via QR codes or deep links.

  • Canonical Format: hermes://pair?data=<base64url_payload>
  • Tolerated Formats: Clients MUST also tolerate single slashes (e.g., hermes:/pair?data=...) to accommodate aggressive URL normalization applied by certain OS deep link handlers or third-party QR scanners.

2. Payload Fields & Types

The data query parameter contains a Base64 encoded JSON string representing the payload.

Field Type Description
v u32 Protocol version. Must be exactly 1.
type string Payload type. Must be exactly "hermes-pair".
host_id string A valid UUID (canonical string representation, 8-4-4-4-12) identifying the host server.
name string Human-readable string containing the name of the server/host.
host string The IP address (IPv4, or IPv6 enclosed in brackets e.g. [::1]) or a valid hostname of the server.
port u16 TCP port number (1-65535). Cannot be 0.
scheme string The HTTP scheme to use. Must be either "http" or "https".
expires_at u64 Expiration time as a Unix timestamp in seconds since epoch.
nonce string 16 bytes of random data, encoded as a Base64URL string (unpadded). Ensures QR code uniqueness and helps prevent replay attacks.

3. Decisions on the 4 Divergences

  1. UUID Version (host_id): The host_id MUST be validated as a legitimate UUID according to RFC 4122. Any valid UUID version (such as v4, v7, etc.) is accepted, provided it parses correctly as a standard 128-bit UUID. Justification: Limiting strict validation to only v4 limits future upgrades (e.g., migrating to v7 for time-sorting), while enforcing any valid UUID check provides the required uniqueness and collision resistance.

  2. Base64 Encoding (data): The canonical encoding for the data parameter (and the nonce inside) is URL-safe Base64 without padding (RFC 4648 Section 5). Tolerance: Parsers MUST be robust and accept URL-safe Base64 with padding, as well as standard Base64 encoding. Justification: This maximizes interoperability with various generation libraries and ecosystem tools that may apply padding or default standard Base64 despite requests to be URL-safe.

  3. Data Extraction (URL query parsing): Parsers MUST use robust, standard URL query parameter extraction mechanisms (e.g., standard URLDecoder in Java/Kotlin, or query_pairs() in Rust). They MUST handle extraneous query parameters gracefully by ignoring them, and strictly tolerate anomalous empty segments such as double ampersands &&.

  4. URI Scheme: The hermes://pair schema/host structure is strictly canonical.

4. Validation Rules & Error Classification

During parsing and validation, clients MUST return distinct typed errors corresponding to the following failure modes to allow for proper UX messaging or fallback behavior:

Error Code Description
InvalidUriFormat The URI does not match hermes://pair or hermes:/pair.
MissingDataParam The data query parameter is missing from the URI.
Base64DecodeError The data string cannot be decoded as Base64 (corrupt).
JsonSyntaxError The decoded string is not valid JSON.
InvalidPayloadType The type field is missing or not "hermes-pair".
UnsupportedProtocolVersion The v field is not 1.
InvalidHostId The host_id is missing or not a valid UUID string.
InvalidPort The port is missing, 0, or out of valid u16 range.
InvalidScheme The scheme is missing, or not "http" or "https".
InvalidNonce The nonce is missing, incorrectly sized, or malformed.
ExpiredPayload The expires_at timestamp is historically in the past.
ClockSkewError The expires_at timestamp is excessively in the future or rejected due to extreme local clock skew anomalies.

5. Nonce and Replay Prevention

The nonce field serves to ensure that every generated QR code payload string is unique, preventing predictable QR patterns. To prevent replay attacks (where an intercepted QR code is reused maliciously by a third party), clients and servers SHOULD implement a single-use tracking mechanism:

  1. Client-side cache: Clients should record the nonce of successfully parsed and used pairing payloads.
  2. Rejection: If a payload is presented containing an already-seen nonce, it must be rejected.
  3. Cache Eviction: The cache entries only need to be retained until the expires_at timestamp of the payload, after which the basic ExpiredPayload check will naturally reject it, saving memory.