Repository navigation
Transport v3, phase 3a: the iPad's v3 client core in Swift - #250
Merged
Merged
Conversation
- client_golden.txt: the TooNew and PairAgain answers, the largest Accept with records under it, message 2s that authenticate but don't parse (the attempt is then over), name cuts at character boundaries, slot status encodings, payloads every parser rejects, and a long replay script. - parser_differential.txt: a seeded generator mutates valid encodings into 4,000 inputs per parser; the file pins the inputs' digest, every accept/reject verdict and the accepted outputs' digest, for another implementation to match. - v3_interop_host example: the reference session table on a loopback UDP socket, with one key that commits and one that is always refused.
A Swift package, ios/EternalTransportV3, on CryptoKit. Nothing in the app uses it yet; only the unit tests link it. - Noise_NNpsk0_25519_ChaChaPoly_SHA256 as the Rust side runs it, with the session prologue and K_dev as the PSK. A failed message 2 read changes nothing; once one authenticates, the attempt is over. - Sealed records with per-direction counters, and the 4,096-wide replay window, marked only after authentication. - The reconnect state machine, ported branch for branch from the Rust reference client: retries, confirms, abandoned attempts kept receive-only, the drain, the liveness rule, age and counter limits. Only sealed v2 messages count as application records. - The key rule with slot status, and a Keychain store by host_id (one item per host, after first unlock, this device only) with an in-memory store for tests. Keys live in memory that is wiped on release. - Tests: every Rust golden transcript byte for byte, the parser differential against the Rust verdicts, the state machine row by row, fuzz-like mutation of every datagram in each client state, and a live run against the Rust host over loopback (scripts/v3_swift_interop.sh, on macOS or an iOS simulator, and in CI).
…eference Only inner types below 0x40 are application data; an unknown type at 0x40 or above never promotes an attempt and is never delivered, and on a live session still counts for liveness. The test now covers confirming, live, draining and abandoned attempts.
- The interop script builds the tests before starting the host, so a slow build can't outlast the host's idle limit (now 120 s). - Tests for three intent rules: an abandoned attempt is promoted only for the key the app wants; PairAgain keeps abandoned attempts; a failed attempt falls back to the live key only while a session is live. - A full key ring refuses a new key with `.full` instead of a bare false; nothing is evicted. - The in-memory key store moves to the tests, and the stored-form codec is internal.
…base - SessionMessage.busy (0x53, empty body) and ClientOutput.busy, as the reference client handles them: a busy answer ends a confirming attempt, and removes an abandoned one; on a live session it changes nothing. - parser_differential.txt regenerated: the session parser now returns Busy for 0x53. client_golden.txt adds a sealed Busy record. - KeyRing.add no longer discards its outcome; every caller checks it.
…#252) into the iPad core
…ifferential The session parser's canonical output now tells a non-session inner type that is application data ([2], below 0x40) from a protocol type the client drops ([3]), using wire::is_application. Swift matches every case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 3a of transport v3: the iPad's side of the reconnect handshake and sealed records, as a Swift package on CryptoKit (
ios/EternalTransportV3). There's no UI and no connection wiring. The app target doesn't link the package; onlyEternalMonitorTestsdoes, for the Keychain round trip. v2 behaviour is unchanged.What's in the package
Noise_NNpsk0_25519_ChaChaPoly_SHA256, asproto/src/v3/handshake.rsruns it: the session prologue,K_devas the PSK in position 0, CryptoKit's X25519, ChaCha20-Poly1305 and HMAC-SHA-256, and Noise's own HKDF. A message 2 that fails framing or authentication changes nothing. Once one authenticates, the attempt is over, even if its payload then fails to parse.Client), ported branch for branch fromproto/src/v3/client.rs:StaleorTooNew, andPairAgain, which stops new attempts under that key without retiring it;SessionBusy(0x53): it ends a confirming attempt and drops an abandoned one, and leaves a live session as it is;KeyRing). A key is deleted only on an authenticated tombstone, a refused pairing, or Forget. A tombstoned key is never used again, even if saving fails. Connect order is pending, then active, then unlisted, newest first. A key the host lists as pending gets an immediate reconnect, and after three silent cycles the client suggests pairing again. The ring holds at most 16 keys; a pairing past that gets.full, with nothing evicted, and leads to Forget.KeychainKeyStorekeeps one item perhost_id: after first unlock, this device only, not synchronized. Each save is one atomic write, and a damaged item is reported, never overwritten. The in-memory store the tests use lives in the test target.memset_son release, and buffers that held secrets are wiped after use.Interop with the Rust reference
proto/testdata/v3/client_golden.txt(new,v3::golden_client) holds the other message 2 answers, the largest Accept with records sealed under it, message 2s that authenticate but don't parse, name truncation, slot status encodings, 29 payloads every parser must reject, and a 3,000-counter replay script.proto/testdata/v3/parser_differential.txt(new,v3::golden_differential): a seeded generator mutates valid encodings into 4,000 inputs for each of 8 parsers. The file pins the inputs' digest, every Rust accept/reject verdict and the accepted outputs' digest, and Swift reproduces all of them. The session parser's output also says whether a non-session type is application data, so the application-type rule is pinned too.session_golden.txt,noise_vectors.txt(cacophony NNpsk0, RFC 8439, RFC 7748), both replay scripts and the two new files byte for byte. Each test checks that it read every entry of its file.proto/examples/v3_interop_host.rsruns the reference session table on a loopback UDP socket, andscripts/v3_swift_interop.shruns the package tests against it, on macOS or (--simulator <udid>) on an iOS simulator. One run covers:StaleandTooNewanswers and their retries;Checks
feat/transport-v3(with Transport v3, phase 2b-ii: QR pairing and the busy answer #243 and Transport v3 pairing: per-source limits only, and unknown inner types never prove a commit #252 merged in):cargo fmt --check,cargo clippy -p eternal-wire --all-targets -D warnings(with and withouttest-hooks),cargo test -p eternal-wire: all pass.scripts/v3_swift_interop.shon macOS (Xcode 16.4): 76 of 76 tests, live interop included.scripts/v3_swift_interop.sh --simulatoron iOS 18.6 (Xcode 16.4): 76 of 76.scripts/test_ios.sh -only-testing:EternalMonitorTestson iOS 18.6: 397 of 397, including the three Keychain tests.PairAgainkeeping abandoned attempts, the fallback to the live key only while a session is live) each catch their mutation.