Skip to content

feat(swift): add the VSR protocol layer with request and reply codecs - #4355

Open
RustToMetal wants to merge 3 commits into
apache:masterfrom
RustToMetal:swift/04-protocol
Open

RustToMetal wants to merge 3 commits into
apache:masterfrom
RustToMetal:swift/04-protocol

Conversation

@RustToMetal

@RustToMetal RustToMetal commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

The protocol layer: the consensus frame every request and reply travels in, the session state that hands out request ids, one request encoder per command code, one reply decoder per reply shape, and the message batch format that send bodies and poll replies share. Nothing here opens a socket; the TCP and TLS client that drives this layer is the next PR.

Fourth of nine stacked PRs for the Swift SDK; builds on #4190, #4306, and #4335.

Which issue does this PR address?

Relates to #4001

Rationale

The client, producer, and consumer are thin over this layer, and the wire rules are the part most likely to diverge from the server. Landing them on their own lets every header, encoder, and decoder be reviewed next to the golden vector the Rust crates produced for it, before any networking is involved.

What changed?

VSRFrame ports core/binary_protocol/src/consensus/header.rs: the 256-byte header offsets, the command and eviction discriminants, encodeRequest (which stamps the request checksum for the operations the server's client table deduplicates and leaves it zero for partition and non-replicated operations, like the Rust SDK), and decodeReply, which applies the fixed order of frame discriminant, declared size, pre-commit denial status, then the committed result section. Eviction reasons map to the same errors as eviction_reason_to_error in core/common.

ConsensusSession ports core/sdk/src/session.rs: an ephemeral client id, the session fence, and monotonic request ids for replicated operations. ConsensusOperation and CommandCode mirror the Rust enums, with the metadata, partition, and result-framed bands.

Requests has one encoder per command, with the username, password, and name bounds applied before encoding so a length prefix can never desync. Responses has one decoder per reply shape, mapping codec failures to invalidCommand like the Rust client; the stats decoder applies the crate's rule for the optional open-files tail (absent on older servers, a partial tail fails). Batch ports batch.rs: the 256-byte batch header, the 48-byte message frame, and the XXH3 checksums over frames and batch.

Every length read from the wire is bounded before it is converted or added (frame size, result-section count, prefixed blocks, batch length, message payload and header lengths), so a hostile frame cannot trap or overflow on any platform, and the raw header accessors answer nil for a short buffer instead of indexing past it. The frames of a batch must tile message_count on the layout-only poll path as well as the verifying one, as in the Rust LayoutOnly decode. The byte codec gains the helpers this layer needs: readPrefixedBytes, skip, rest, writeZeros, overwrite, and the checksum error cases with their IggyError mapping.

Tests: WireVectorTests compares every request encoder and consensus header against the Rust bytes in golden.json, ResponseVectorTests does the same for every reply decoder, and ProtocolTests covers the session rules, result-section handling, denial ordering, duplicate sends, and eviction mapping.

Local Execution

  • Passed: swift build -Xswiftc -warnings-as-errors, swift test (106 tests), swift format lint --strict, hawkeye license check
  • Pre-commit: prek run --from-ref upstream/master --to-ref HEAD passed

AI Usage

  1. Which tools? Claude Code (Claude).
  2. Scope of usage? The port and the tests were drafted with it from core/binary_protocol, core/sdk/src/vsr.rs, and core/sdk/src/session.rs as the reference, reviewed and directed by me.
  3. How did you verify the generated code works correctly? Every header, request, and reply is checked byte for byte against vectors the Rust crates produced, and the full chain was run end to end against a real iggy-server (35 end-to-end tests) plus the 9 BDD scenarios.
  4. Can you explain every line of the code if asked? Yes.

@github-actions

Copy link
Copy Markdown

Thanks for the PR. It is labeled S-waiting-on-review and queued for review.

Slash commands (own line, regular comment) move it around the queue:

  • /ready - back to S-waiting-on-review after addressing feedback
  • /author - flip to S-waiting-on-author while you finish changes
  • /request-review @user-or-team - request a reviewer
  • /pin - exempt the PR from the stale bot, /unpin to undo

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Sep 29, 2026
@RustToMetal

Copy link
Copy Markdown
Contributor Author

/ready

@hubcio

hubcio commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

/skill team-review-slim

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary: The Swift VSR protocol layer matches the Rust wire formats, but its batch decoder adds an unbounded batch length before it checks the sum, so a hostile poll reply aborts the client, and the default poll path skips the frame-count tiling check that the Rust decoder enforces. The remaining notes cover trapping reply-header accessors, missing error context in the user validators, and three unused header offsets.

Counts: critical 1, warning 1, nit 2, simplification 1


This review was generated by Claude Code 2.1.284 on deepseek-flash[1m]. Review the output before you act on it.

Comment thread foreign/swift/Sources/Iggy/Protocol/Batch.swift Outdated
Comment thread foreign/swift/Sources/Iggy/Protocol/Batch.swift Outdated
Comment thread foreign/swift/Sources/Iggy/Protocol/VSRFrame.swift Outdated
Comment thread foreign/swift/Sources/Iggy/Protocol/Requests.swift Outdated
Comment thread foreign/swift/Sources/Iggy/Protocol/VSRFrame.swift Outdated
@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Oct 1, 2026
The consensus frame every request and reply travels in: the 256-byte
header with its command, operation, request id, session fence, and
checksum stamp, the session state that hands out request ids the way the
Rust SDK does, and the eviction and result-section rules that turn a
reply into a typed error or a body. On top of it, one request encoder per
command code, one reply decoder per reply shape, and the message batch
format shared by send bodies and poll replies, checksummed with XXH3.

Every encoder, decoder, and header is compared against bytes produced by
the Rust crates through the golden fixture. Every length read from the
wire is bounded before it becomes an Int, so a hostile frame cannot trap
a 32-bit platform.

Relates to apache#4001
@hubcio

hubcio commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

purge is getting removed from the server and every sdk, and i'll open the removal pr today. so you can drop purgeStream / purgeTopic from CommandCode.swift and ConsensusOperation.swift, plus the two purge golden vector tests. if this merges first that's fine too, they'll go away in the removal pr.

@hubcio

hubcio commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

@RustToMetal could you please fix CI? it now fails after rebase

…w error codes

Master added a 32-byte bind_secret field (for session binding) to
LoginRegisterRequest and LoginRegisterWithPatRequest, and introduced
BindSessionRequest (command 15). Three new server error codes arrived
in the same wave: partition_history_changed (3014), session_mismatch
(14004), request_id_exhausted (14005). The eviction window min was
pinned to 0.11.1 (equal to current).

  - Requests.loginRegister and loginRegisterWithPersonalAccessToken
    accept bindSecret: [UInt8] (default zeros) and write the 32 bytes
    after the ClientVersionInfo prefix, matching the Rust wire shape.
  - Requests.bindSession encodes the new re-auth request.
  - CommandCode gains bindSession = 15 / "session.bind".
  - IggyErrorCode gains the three new codes with their snake_case names.
  - golden.json is rebased on the master fixture (protocol_version_min
    11265, updated login + eviction vectors, bind_session vector) while
    keeping the Swift-only purge_stream and purge_topic vectors.
  - WireVectorTests passes the 0x5A test bind_secret so the encoded
    bytes match the reference fixture; adds the bindSession case.
  - The golden-vectors generator is updated to compile with the new
    Rust structs and emit the same vectors CI checks against.

Relates to apache#4001

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author PR is waiting on author response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants