This is an automated email from the ASF dual-hosted git repository.

spetz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new 917612d27 feat(swift): add XXH3 and XXH32 with golden vectors from the 
Rust crates (#4306)
917612d27 is described below

commit 917612d27d591ed019ec9bcc3050d564b03637dc
Author: RustToMetal <[email protected]>
AuthorDate: Mon Sep 28 22:59:05 2026 -0500

    feat(swift): add XXH3 and XXH32 with golden vectors from the Rust crates 
(#4306)
    
    The wire protocol checksums every batch with XXH3-64 and routes keyed
    messages to partitions with XXH32, so the SDK needs both hashes before
    it can encode a single send request. Both are implemented natively in
    Swift rather than bound to a C library, and both are pinned to the Rust
    crates the server uses through a golden fixture generated from them.
    
    Second of nine stacked PRs for the Swift SDK; builds on #4190, relates to 
#4001
---
 .github/actions/swift/pre-merge/action.yml         |   35 +-
 .github/config/components.yml                      |    5 +-
 .gitignore                                         |    2 +
 .typos.toml                                        |    2 +
 core/binary_protocol/src/consensus/mod.rs          |    2 +
 core/binary_protocol/src/consensus/request.rs      |  143 ++
 core/sdk/src/vsr.rs                                |   71 +-
 foreign/swift/Package.swift                        |    3 +-
 foreign/swift/README.md                            |   13 +-
 .../swift/Sources/Iggy/Errors/IggyErrorCode.swift  |    4 +-
 .../Iggy/Hash/UnalignedLoads.swift}                |   35 +-
 foreign/swift/Sources/Iggy/Hash/XXH3.swift         |  273 ++++
 foreign/swift/Sources/Iggy/Hash/XXH32.swift        |   79 ++
 foreign/swift/Tests/IggyTests/ErrorCodeTests.swift |   18 +-
 foreign/swift/Tests/IggyTests/Fixtures/golden.json | 1004 ++++++++++++++
 foreign/swift/Tests/IggyTests/HashTests.swift      |   62 +
 .../Tests/IggyTests/Support/GoldenFixture.swift    |   73 +
 foreign/swift/Tools/golden-vectors/Cargo.toml      |   36 +
 foreign/swift/Tools/golden-vectors/src/main.rs     | 1435 ++++++++++++++++++++
 19 files changed, 3198 insertions(+), 97 deletions(-)

diff --git a/.github/actions/swift/pre-merge/action.yml 
b/.github/actions/swift/pre-merge/action.yml
index 02f383ada..f21a11903 100644
--- a/.github/actions/swift/pre-merge/action.yml
+++ b/.github/actions/swift/pre-merge/action.yml
@@ -20,15 +20,16 @@ description: Swift pre-merge testing github iggy actions
 
 inputs:
   task:
-    description: "Task to run (lint, test, build, build-macos)"
+    description: "Task to run (lint, test, build, build-macos, golden)"
     required: true
 
 runs:
   using: "composite"
   steps:
-    # The macOS lane uses the Swift that ships with Xcode on the runner.
+    # The macOS lane uses the Swift that ships with Xcode on the runner, and
+    # the golden lane only runs the Rust generator.
     - name: Setup Swift
-      if: inputs.task != 'build-macos'
+      if: inputs.task != 'build-macos' && inputs.task != 'golden'
       uses: ./.github/actions/utils/setup-swift
 
     # swift-format ships with the toolchain; the configuration lives in
@@ -73,3 +74,31 @@ runs:
         swift --version
         swift build -Xswiftc -warnings-as-errors
         swift test --parallel
+
+    # The generator is a small standalone workspace that needs no system
+    # packages, and the workspace cache never matches its dependency set, so
+    # the lane skips the disk cleanup, the apt install, and the cache restore.
+    - name: Setup Rust for the golden vectors
+      if: inputs.task == 'golden'
+      uses: ./.github/actions/utils/setup-rust-with-cache
+      with:
+        free-disk-space: "false"
+        read-cache: "false"
+        install-system-dependencies: "false"
+
+    # The generator sits outside the root workspace, so the repo-wide fmt and
+    # clippy runs never see it. The golden fixture is checked in, so this lane
+    # then regenerates it from the Rust crates and fails when the two differ;
+    # otherwise the Swift tests stay green against a stale copy of the 
protocol.
+    - name: Check the golden vectors match the Rust crates
+      shell: bash
+      if: inputs.task == 'golden'
+      run: |
+        cargo fmt --manifest-path 
foreign/swift/Tools/golden-vectors/Cargo.toml -- --check
+        cargo clippy --manifest-path 
foreign/swift/Tools/golden-vectors/Cargo.toml --all-targets -- -D warnings
+        cargo run --manifest-path 
foreign/swift/Tools/golden-vectors/Cargo.toml -- 
foreign/swift/Tests/IggyTests/Fixtures/golden.json
+        if ! git diff --exit-code -- 
foreign/swift/Tests/IggyTests/Fixtures/golden.json; then
+          echo "❌ golden.json is out of date; regenerate it with the command 
above and commit the result"
+          exit 1
+        fi
+        echo "✅ golden.json matches the Rust crates"
diff --git a/.github/config/components.yml b/.github/config/components.yml
index eb77a9764..b8c5dd036 100644
--- a/.github/config/components.yml
+++ b/.github/config/components.yml
@@ -345,10 +345,7 @@ components:
       - "foreign/swift/**"
       - "bdd/swift/**"
       - "examples/swift/**"
-      # The SDK compiles against the VSR wire contract, so a change to the
-      # protocol crate must rerun it.
-      - "core/binary_protocol/**"
-    tasks: ["lint", "test", "build", "build-macos"]
+    tasks: ["lint", "test", "build", "build-macos", "golden"]
 
   # Shared BDD infrastructure that affects all BDD tests
   bdd-infrastructure:
diff --git a/.gitignore b/.gitignore
index 4c5ecbe94..2b6bf9c31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,8 @@ examples/swift/**/.swiftpm
 bdd/swift/.build
 bdd/swift/**/.build
 bdd/swift/**/.swiftpm
+# A standalone dev tool with path dependencies on the workspace crates.
+foreign/swift/Tools/golden-vectors/Cargo.lock
 foreign/node/npm-debug.log*
 foreign/node/.npm
 **/node_modules
diff --git a/.typos.toml b/.typos.toml
index fd5c96870..88977d7eb 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -157,5 +157,7 @@ extend-exclude = [
     "core/bench/dashboard/frontend/dist",
     # WireMock test fixtures with base64-encoded JWT data
     "**/wiremock/__files/*.json",
+    # Swift SDK golden vectors dumped from the Rust crates (hex payloads)
+    "foreign/swift/Tests/IggyTests/Fixtures/golden.json",
     "**/.python-version",
 ]
diff --git a/core/binary_protocol/src/consensus/mod.rs 
b/core/binary_protocol/src/consensus/mod.rs
index 67525a14e..1b9a44470 100644
--- a/core/binary_protocol/src/consensus/mod.rs
+++ b/core/binary_protocol/src/consensus/mod.rs
@@ -42,6 +42,7 @@ mod error;
 mod header;
 mod operation;
 mod reply_result;
+mod request;
 
 pub use command::Command;
 pub use consumer_session::{
@@ -64,3 +65,4 @@ pub use reply_result::{
     REJECTION_SECTION_LEN, RESULT_COUNT_LEN, RESULT_ENTRY_LEN, result_code, 
result_section_len,
     write_rejection_section,
 };
+pub use request::{NON_REPLICATED_CODE_RANGE, operation_for_code};
diff --git a/core/binary_protocol/src/consensus/request.rs 
b/core/binary_protocol/src/consensus/request.rs
new file mode 100644
index 000000000..0e2d2f8ae
--- /dev/null
+++ b/core/binary_protocol/src/consensus/request.rs
@@ -0,0 +1,143 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::codes::{LOGIN_REGISTER_CODE, LOGIN_REGISTER_WITH_PAT_CODE, 
LOGOUT_USER_CODE};
+use crate::consensus::{Command, HEADER_SIZE, Operation, RequestHeader};
+use crate::error::WireError;
+use std::ops::Range;
+use twox_hash::XxHash3_64;
+
+/// Where `RequestHeader::reserved` carries the legacy command code of a
+/// `NonReplicated` request, as little-endian `u32`.
+pub const NON_REPLICATED_CODE_RANGE: Range<usize> = 0..4;
+
+/// Maps a command code onto the consensus operation a client sends it as.
+///
+/// `COMMAND_TABLE` is a protocol registry, not a per-server capability list,
+/// so a client build cannot know which codes a given server implements. The
+/// server is the authority: an unmapped code is forwarded as non-replicated
+/// (the code rides `RequestHeader::reserved`, which that path stamps) and the
+/// server answers with a proper error if it does not know it.
+#[must_use]
+pub fn operation_for_code(code: u32) -> Operation {
+    match code {
+        LOGIN_REGISTER_CODE | LOGIN_REGISTER_WITH_PAT_CODE => 
Operation::Register,
+        LOGOUT_USER_CODE => Operation::Logout,
+        _ => 
Operation::from_command_code(code).unwrap_or(Operation::NonReplicated),
+    }
+}
+
+impl RequestHeader {
+    /// Builds the header a client sends for `code` with `payload`, given the
+    /// identity the session assigned: `client`, `request` and `session`.
+    ///
+    /// Every rule the server checks lives here so that each client, and the
+    /// fixtures other SDKs verify against, agree on the bytes: metadata and
+    /// session operations stamp `request_checksum` with the payload hash,
+    /// partition and non-replicated operations leave it zero (send batches
+    /// carry their own checksum and non-replicated requests bypass dedup),
+    /// and a non-replicated request carries its command code in `reserved`.
+    ///
+    /// # Errors
+    ///
+    /// Returns `WireError::PayloadTooLarge` when the frame would not fit the
+    /// `u32` size field.
+    pub fn for_request(
+        code: u32,
+        client: u128,
+        request: u64,
+        session: u64,
+        payload: &[u8],
+    ) -> Result<Self, WireError> {
+        let operation = operation_for_code(code);
+        let total_size = HEADER_SIZE.saturating_add(payload.len());
+        let size = u32::try_from(total_size).map_err(|_| 
WireError::PayloadTooLarge {
+            size: payload.len(),
+            max: u32::MAX as usize - HEADER_SIZE,
+        })?;
+        let request_checksum = if operation.is_partition() || operation == 
Operation::NonReplicated
+        {
+            0
+        } else {
+            u128::from(XxHash3_64::oneshot(payload))
+        };
+        let mut reserved = [0; 60];
+        if operation == Operation::NonReplicated {
+            
reserved[NON_REPLICATED_CODE_RANGE].copy_from_slice(&code.to_le_bytes());
+        }
+        Ok(Self {
+            command: Command::Request,
+            operation,
+            size,
+            client,
+            request,
+            session,
+            request_checksum,
+            // Replicated prepares get a server timestamp; no consumer needs a
+            // client clock read here.
+            timestamp: 0,
+            reserved,
+            ..Default::default()
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::codes::{CREATE_STREAM_CODE, PING_CODE, SEND_MESSAGES_CODE};
+
+    #[test]
+    fn login_codes_register_and_logout_logs_out() {
+        assert_eq!(operation_for_code(LOGIN_REGISTER_CODE), 
Operation::Register);
+        assert_eq!(
+            operation_for_code(LOGIN_REGISTER_WITH_PAT_CODE),
+            Operation::Register
+        );
+        assert_eq!(operation_for_code(LOGOUT_USER_CODE), Operation::Logout);
+        assert_eq!(operation_for_code(u32::MAX), Operation::NonReplicated);
+    }
+
+    #[test]
+    fn metadata_requests_are_stamped_with_the_payload_hash() {
+        let payload = [1, 2, 3];
+        let header = RequestHeader::for_request(CREATE_STREAM_CODE, 7, 8, 9, 
&payload).unwrap();
+        assert_eq!(header.command, Command::Request);
+        assert_eq!(header.size as usize, HEADER_SIZE + payload.len());
+        assert_eq!((header.client, header.request, header.session), (7, 8, 9));
+        assert_eq!(
+            header.request_checksum,
+            u128::from(XxHash3_64::oneshot(&payload))
+        );
+        assert_eq!(header.reserved, [0; 60]);
+    }
+
+    #[test]
+    fn partition_and_non_replicated_requests_leave_the_stamp_zero() {
+        let send = RequestHeader::for_request(SEND_MESSAGES_CODE, 1, 2, 3, 
&[9; 16]).unwrap();
+        assert_eq!(send.request_checksum, 0);
+        assert!(send.operation.is_partition());
+
+        let ping = RequestHeader::for_request(PING_CODE, 1, 2, 3, 
&[]).unwrap();
+        assert_eq!(ping.operation, Operation::NonReplicated);
+        assert_eq!(ping.request_checksum, 0);
+        assert_eq!(
+            ping.reserved[NON_REPLICATED_CODE_RANGE],
+            PING_CODE.to_le_bytes()
+        );
+    }
+}
diff --git a/core/sdk/src/vsr.rs b/core/sdk/src/vsr.rs
index 782f9014d..ca5fa7eb9 100644
--- a/core/sdk/src/vsr.rs
+++ b/core/sdk/src/vsr.rs
@@ -17,18 +17,14 @@
 
 use crate::session::ConsensusSession;
 use bytes::{BufMut, Bytes, BytesMut};
-use iggy_binary_protocol::codes::{
-    LOGIN_REGISTER_CODE, LOGIN_REGISTER_WITH_PAT_CODE, LOGOUT_USER_CODE,
-};
+use iggy_binary_protocol::codes::{LOGIN_REGISTER_CODE, 
LOGIN_REGISTER_WITH_PAT_CODE};
 use iggy_binary_protocol::consensus::{
     Command, EvictionHeader, EvictionReason, GenericHeader, HEADER_SIZE, 
Operation, ReplyHeader,
-    RequestHeader, read_size_field, result_code, result_section_len,
+    RequestHeader, operation_for_code, read_size_field, result_code, 
result_section_len,
 };
-use iggy_common::{IggyError, calculate_checksum, eviction_reason_to_error};
+use iggy_common::{IggyError, eviction_reason_to_error};
 use std::sync::atomic::{AtomicU64, Ordering};
 
-const NON_REPLICATED_CODE_RANGE: std::ops::Range<usize> = 0..4;
-
 // A reconnect creates a fresh VSR client and session. A replicated request
 // retried there gets a new (client_id, request_id) tuple, so server-side
 // deduplication cannot match a mutation that may already have committed before
@@ -104,55 +100,14 @@ pub(crate) fn encode_request_header(
             }
         }
     };
-    // Metadata dedup compares this stamp with its cached replies. Partition
-    // dedup tracks request ids without retaining replies or comparing stamps;
-    // send batches carry their own checksum. NonReplicated ops bypass dedup.
-    let request_checksum = if operation.is_partition() || operation == 
Operation::NonReplicated {
-        0
-    } else {
-        u128::from(calculate_checksum(payload))
-    };
-    let total_size = HEADER_SIZE
-        .checked_add(payload.len())
-        .ok_or(IggyError::InvalidConfiguration)?;
-    let size = u32::try_from(total_size).map_err(|_| 
IggyError::InvalidConfiguration)?;
-    let mut reserved = [0; 60];
-    if operation == Operation::NonReplicated {
-        
reserved[NON_REPLICATED_CODE_RANGE].copy_from_slice(&code.to_le_bytes());
-    }
-    let header = RequestHeader {
-        command: Command::Request,
-        operation,
-        size,
-        client: session.client_id(),
-        request: request_id,
-        session: session_id,
-        // Lets the client table tell a genuine retry from a `request` number 
reused
-        // for different arguments. Zero means unstamped, which is what an SDK
-        // predating this sends. A server that rewrites the body (PAT, 
password)
-        // carries it through untouched, so it keeps describing what the 
client sent.
-        request_checksum,
-        // Replicated prepares get a server timestamp. Direct replies may echo
-        // this field, but no RTT consumer needs a client clock read here.
-        timestamp: 0,
-        reserved,
-        ..Default::default()
-    };
-
-    Ok((header, total_size))
-}
-
-/// `COMMAND_TABLE` is a protocol registry, not a per-server capability list, 
so
-/// an SDK build cannot know which codes a given server implements. The server 
is
-/// the authority: an unmapped code is forwarded as non-replicated (the code
-/// rides `RequestHeader.reserved`, which that path already stamps) and the
-/// server answers with a proper error if it does not know it.
-pub(crate) fn operation_for_code(code: u32) -> Operation {
-    if code == LOGOUT_USER_CODE {
-        return Operation::Logout;
-    }
-
-    Operation::from_command_code(code).unwrap_or(Operation::NonReplicated)
+    // The header rules (checksum stamp, size, reserved code) live in the
+    // protocol crate so every client and the cross-SDK fixtures share them;
+    // only the session identity is decided here.
+    let header =
+        RequestHeader::for_request(code, session.client_id(), request_id, 
session_id, payload)
+            .map_err(|_| IggyError::InvalidConfiguration)?;
+    debug_assert_eq!(header.operation, operation);
+    Ok((header, header.size as usize))
 }
 
 /// Whether replaying `code` after reconnecting with a new session cannot
@@ -363,12 +318,14 @@ mod tests {
     use super::*;
     use crate::session::ConsensusSession;
     use iggy_binary_protocol::codes::{
-        CREATE_STREAM_CODE, GET_STREAM_CODE, PING_CODE, SEND_MESSAGES_CODE,
+        CREATE_STREAM_CODE, GET_STREAM_CODE, LOGOUT_USER_CODE, PING_CODE, 
SEND_MESSAGES_CODE,
     };
+    use iggy_binary_protocol::consensus::NON_REPLICATED_CODE_RANGE;
     use iggy_binary_protocol::requests::streams::CreateStreamRequest;
     use iggy_binary_protocol::requests::users::LoginRegisterRequest;
     use iggy_binary_protocol::version::IGGY_PROTOCOL_VERSION;
     use iggy_binary_protocol::{ClientVersionInfo, WireEncode, WireName, 
WireOptions};
+    use iggy_common::calculate_checksum;
     use secrecy::SecretString;
 
     fn decode_request_header(bytes: &Bytes) -> RequestHeader {
diff --git a/foreign/swift/Package.swift b/foreign/swift/Package.swift
index c644c9a27..27af6f7ae 100644
--- a/foreign/swift/Package.swift
+++ b/foreign/swift/Package.swift
@@ -34,7 +34,8 @@ let package = Package(
         .target(name: "Iggy"),
         .testTarget(
             name: "IggyTests",
-            dependencies: ["Iggy"]
+            dependencies: ["Iggy"],
+            resources: [.copy("Fixtures")]
         ),
     ],
     swiftLanguageModes: [.v6]
diff --git a/foreign/swift/README.md b/foreign/swift/README.md
index 1b2d243d4..f6657e0e2 100644
--- a/foreign/swift/README.md
+++ b/foreign/swift/README.md
@@ -11,8 +11,9 @@
 Official Swift client SDK for [Apache Iggy](https://iggy.apache.org) message 
streaming.
 
 The SDK is built up in stages. This stage carries the package scaffold, the 
error table,
-and the byte codec the wire protocol is encoded with. The protocol layer, the 
TCP and TLS
-client, the producer and consumer, examples, and BDD scenarios follow in later 
changes.
+the byte codec the wire protocol is encoded with, and native XXH3-64 and XXH32 
hashes
+pinned to the Rust crates by golden vectors. The protocol layer, the TCP and 
TLS client,
+the producer and consumer, examples, and BDD scenarios follow in later changes.
 
 ## Requirements
 
@@ -75,6 +76,14 @@ cd foreign/swift
 swift test
 ```
 
+The hash and error-code tests compare against 
`Tests/IggyTests/Fixtures/golden.json`, a
+fixture dumped from the Rust protocol crates. Regenerate it after a protocol 
change, from
+`foreign/swift`, and commit the result; CI fails when the checked-in copy is 
stale:
+
+```bash
+cargo run --manifest-path Tools/golden-vectors/Cargo.toml -- 
Tests/IggyTests/Fixtures/golden.json
+```
+
 ## Contributing
 
 Format the sources with the toolchain's formatter before opening a pull 
request; CI runs it
diff --git a/foreign/swift/Sources/Iggy/Errors/IggyErrorCode.swift 
b/foreign/swift/Sources/Iggy/Errors/IggyErrorCode.swift
index 1b6940d1f..ffbac5d24 100644
--- a/foreign/swift/Sources/Iggy/Errors/IggyErrorCode.swift
+++ b/foreign/swift/Sources/Iggy/Errors/IggyErrorCode.swift
@@ -16,8 +16,8 @@
 // under the License.
 
 // Every discriminant the server can answer with, transcribed from
-// `core/common/src/error/iggy_error.rs`. The golden-vector generator that
-// follows in a later change dumps the same table and the tests compare.
+// `core/common/src/error/iggy_error.rs`. `Tools/golden-vectors` dumps the
+// same table from the crate and `ErrorCodeTests` compares the two.
 
 /// Numeric error code shared with every other Apache Iggy SDK and the server.
 ///
diff --git a/foreign/swift/Package.swift 
b/foreign/swift/Sources/Iggy/Hash/UnalignedLoads.swift
similarity index 61%
copy from foreign/swift/Package.swift
copy to foreign/swift/Sources/Iggy/Hash/UnalignedLoads.swift
index c644c9a27..41157f761 100644
--- a/foreign/swift/Package.swift
+++ b/foreign/swift/Sources/Iggy/Hash/UnalignedLoads.swift
@@ -1,4 +1,3 @@
-// swift-tools-version: 6.0
 // Licensed to the Apache Software Foundation (ASF) under one
 // or more contributor license agreements.  See the NOTICE file
 // distributed with this work for additional information
@@ -16,26 +15,16 @@
 // specific language governing permissions and limitations
 // under the License.
 
-import PackageDescription
+extension UnsafeRawBufferPointer {
+    /// The little-endian word at `offset`, which both hashes read from
+    /// arbitrary positions of the input and the secret.
+    @inline(__always)
+    func loadLittleEndianUInt32(at offset: Int) -> UInt32 {
+        UInt32(littleEndian: loadUnaligned(fromByteOffset: offset, as: 
UInt32.self))
+    }
 
-let package = Package(
-    name: "apache-iggy",
-    platforms: [
-        .macOS(.v13),
-        .iOS(.v16),
-        .tvOS(.v16),
-        .watchOS(.v9),
-        .visionOS(.v1),
-    ],
-    products: [
-        .library(name: "Iggy", targets: ["Iggy"])
-    ],
-    targets: [
-        .target(name: "Iggy"),
-        .testTarget(
-            name: "IggyTests",
-            dependencies: ["Iggy"]
-        ),
-    ],
-    swiftLanguageModes: [.v6]
-)
+    @inline(__always)
+    func loadLittleEndianUInt64(at offset: Int) -> UInt64 {
+        UInt64(littleEndian: loadUnaligned(fromByteOffset: offset, as: 
UInt64.self))
+    }
+}
diff --git a/foreign/swift/Sources/Iggy/Hash/XXH3.swift 
b/foreign/swift/Sources/Iggy/Hash/XXH3.swift
new file mode 100644
index 000000000..4eebc7417
--- /dev/null
+++ b/foreign/swift/Sources/Iggy/Hash/XXH3.swift
@@ -0,0 +1,273 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+/// XXH3 64-bit hash with the default secret and a zero seed.
+///
+/// Every message frame and batch on the wire is checksummed with this hash,
+/// and the server verifies the values, so the implementation has to match the
+/// reference bit for bit. It is checked against vectors produced by the Rust
+/// `twox-hash` crate in the test suite.
+enum XXH3 {
+    private static let prime32_1: UInt64 = 0x9E37_79B1
+    private static let prime32_2: UInt64 = 0x85EB_CA77
+    private static let prime32_3: UInt64 = 0xC2B2_AE3D
+    private static let prime64_1: UInt64 = 0x9E37_79B1_85EB_CA87
+    private static let prime64_2: UInt64 = 0xC2B2_AE3D_27D4_EB4F
+    private static let prime64_3: UInt64 = 0x1656_67B1_9E37_79F9
+    private static let prime64_4: UInt64 = 0x85EB_CA77_C2B2_AE63
+    private static let prime64_5: UInt64 = 0x27D4_EB2F_1656_67C5
+    private static let primeMX1: UInt64 = 0x1656_6791_9E37_79F9
+    private static let primeMX2: UInt64 = 0x9FB2_1C65_1E98_DF25
+
+    private static let stripeLength = 64
+    private static let secretConsumeRate = 8
+    private static let accumulatorCount = 8
+    private static let secretLength = 192
+    private static let stripesPerBlock = (secretLength - stripeLength) / 
secretConsumeRate
+    private static let blockLength = stripeLength * stripesPerBlock
+    private static let secretLastAccumulatorStart = 7
+    private static let secretMergeAccumulatorsStart = 11
+    private static let midsizeStartOffset = 3
+    private static let midsizeLastOffset = 17
+    private static let secretSizeMin = 136
+
+    private static let secret: [UInt8] = [
+        0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 
0x2c, 0xf7, 0x21, 0xad, 0x1c,
+        0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 
0xa4, 0xb7, 0xb3, 0x67, 0x1f,
+        0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 
0x7d, 0xcc, 0xff, 0x72, 0x21,
+        0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 
0xe6, 0x81, 0x3a, 0x26, 0x4c,
+        0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 
0x8b, 0x1b, 0x53, 0x2e, 0xa3,
+        0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 
0x46, 0xa9, 0xde, 0xac, 0xd8,
+        0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 
0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
+        0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 
0xc9, 0xd9, 0x78, 0x73, 0x64,
+        0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 
0xff, 0xfa, 0x13, 0x63, 0xeb,
+        0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 
0x26, 0x29, 0xd4, 0x68, 0x9e,
+        0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 
0xd1, 0x7a, 0xd0, 0x31, 0xce,
+        0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 
0xca, 0xbb, 0x4b, 0x40, 0x7e,
+    ]
+
+    static func hash64(_ bytes: [UInt8]) -> UInt64 {
+        bytes.withUnsafeBytes { hash64($0) }
+    }
+
+    static func hash64(_ bytes: ArraySlice<UInt8>) -> UInt64 {
+        bytes.withUnsafeBytes { hash64($0) }
+    }
+
+    static func hash64(_ input: UnsafeRawBufferPointer) -> UInt64 {
+        secret.withUnsafeBytes { secret in
+            let length = input.count
+            switch length {
+            case 0:
+                return avalanche64(secret.loadLittleEndianUInt64(at: 56) ^ 
secret.loadLittleEndianUInt64(at: 64))
+            case 1...3:
+                return hash1to3(input, secret)
+            case 4...8:
+                return hash4to8(input, secret)
+            case 9...16:
+                return hash9to16(input, secret)
+            case 17...128:
+                return hash17to128(input, secret)
+            case 129...240:
+                return hash129to240(input, secret)
+            default:
+                return hashLong(input, secret)
+            }
+        }
+    }
+
+    @inline(__always)
+    private static func rotl(_ value: UInt64, _ count: UInt64) -> UInt64 {
+        (value << count) | (value >> (64 - count))
+    }
+
+    @inline(__always)
+    private static func mul128Fold64(_ lhs: UInt64, _ rhs: UInt64) -> UInt64 {
+        let product = lhs.multipliedFullWidth(by: rhs)
+        return product.high ^ product.low
+    }
+
+    @inline(__always)
+    private static func avalanche64(_ input: UInt64) -> UInt64 {
+        var hash = input
+        hash ^= hash >> 33
+        hash &*= prime64_2
+        hash ^= hash >> 29
+        hash &*= prime64_3
+        hash ^= hash >> 32
+        return hash
+    }
+
+    @inline(__always)
+    private static func avalanche(_ input: UInt64) -> UInt64 {
+        var hash = input
+        hash ^= hash >> 37
+        hash &*= primeMX1
+        hash ^= hash >> 32
+        return hash
+    }
+
+    @inline(__always)
+    private static func rrmxmx(_ input: UInt64, _ length: Int) -> UInt64 {
+        var hash = input
+        hash ^= rotl(hash, 49) ^ rotl(hash, 24)
+        hash &*= primeMX2
+        hash ^= (hash >> 35) &+ UInt64(length)
+        hash &*= primeMX2
+        return hash ^ (hash >> 28)
+    }
+
+    private static func hash1to3(_ input: UnsafeRawBufferPointer, _ secret: 
UnsafeRawBufferPointer) -> UInt64 {
+        let length = input.count
+        let byte1 = UInt32(input[0])
+        let byte2 = UInt32(input[length >> 1])
+        let byte3 = UInt32(input[length - 1])
+        let combined = (byte1 << 16) | (byte2 << 24) | byte3 | (UInt32(length) 
<< 8)
+        let bitflip = UInt64(secret.loadLittleEndianUInt32(at: 0) ^ 
secret.loadLittleEndianUInt32(at: 4))
+        return avalanche64(UInt64(combined) ^ bitflip)
+    }
+
+    private static func hash4to8(_ input: UnsafeRawBufferPointer, _ secret: 
UnsafeRawBufferPointer) -> UInt64 {
+        let length = input.count
+        let input1 = input.loadLittleEndianUInt32(at: 0)
+        let input2 = input.loadLittleEndianUInt32(at: length - 4)
+        let bitflip = secret.loadLittleEndianUInt64(at: 8) ^ 
secret.loadLittleEndianUInt64(at: 16)
+        let input64 = UInt64(input2) &+ (UInt64(input1) << 32)
+        return rrmxmx(input64 ^ bitflip, length)
+    }
+
+    private static func hash9to16(_ input: UnsafeRawBufferPointer, _ secret: 
UnsafeRawBufferPointer) -> UInt64 {
+        let length = input.count
+        let bitflip1 = secret.loadLittleEndianUInt64(at: 24) ^ 
secret.loadLittleEndianUInt64(at: 32)
+        let bitflip2 = secret.loadLittleEndianUInt64(at: 40) ^ 
secret.loadLittleEndianUInt64(at: 48)
+        let inputLow = input.loadLittleEndianUInt64(at: 0) ^ bitflip1
+        let inputHigh = input.loadLittleEndianUInt64(at: length - 8) ^ bitflip2
+        let accumulator = UInt64(length) &+ inputLow.byteSwapped &+ inputHigh 
&+ mul128Fold64(inputLow, inputHigh)
+        return avalanche(accumulator)
+    }
+
+    @inline(__always)
+    private static func mix16(_ input: UnsafeRawBufferPointer, _ inputOffset: 
Int, _ secret: UnsafeRawBufferPointer, _ secretOffset: Int) -> UInt64 {
+        let inputLow = input.loadLittleEndianUInt64(at: inputOffset)
+        let inputHigh = input.loadLittleEndianUInt64(at: inputOffset + 8)
+        return mul128Fold64(inputLow ^ secret.loadLittleEndianUInt64(at: 
secretOffset), inputHigh ^ secret.loadLittleEndianUInt64(at: secretOffset + 8))
+    }
+
+    private static func hash17to128(_ input: UnsafeRawBufferPointer, _ secret: 
UnsafeRawBufferPointer) -> UInt64 {
+        let length = input.count
+        var accumulator = UInt64(length) &* prime64_1
+        if length > 32 {
+            if length > 64 {
+                if length > 96 {
+                    accumulator &+= mix16(input, 48, secret, 96)
+                    accumulator &+= mix16(input, length - 64, secret, 112)
+                }
+                accumulator &+= mix16(input, 32, secret, 64)
+                accumulator &+= mix16(input, length - 48, secret, 80)
+            }
+            accumulator &+= mix16(input, 16, secret, 32)
+            accumulator &+= mix16(input, length - 32, secret, 48)
+        }
+        accumulator &+= mix16(input, 0, secret, 0)
+        accumulator &+= mix16(input, length - 16, secret, 16)
+        return avalanche(accumulator)
+    }
+
+    private static func hash129to240(_ input: UnsafeRawBufferPointer, _ 
secret: UnsafeRawBufferPointer) -> UInt64 {
+        let length = input.count
+        var accumulator = UInt64(length) &* prime64_1
+        let rounds = length / 16
+        for round in 0..<8 {
+            accumulator &+= mix16(input, 16 * round, secret, 16 * round)
+        }
+        accumulator = avalanche(accumulator)
+        for round in 8..<rounds {
+            accumulator &+= mix16(input, 16 * round, secret, 16 * (round - 8) 
+ midsizeStartOffset)
+        }
+        accumulator &+= mix16(input, length - 16, secret, secretSizeMin - 
midsizeLastOffset)
+        return avalanche(accumulator)
+    }
+
+    private static let initialAccumulators: [UInt64] = [prime32_3, prime64_1, 
prime64_2, prime64_3, prime64_4, prime32_2, prime64_5, prime32_1]
+
+    /// The accumulators live in a stack allocation: this path runs for every
+    /// input over 240 bytes, so it must not hit the heap.
+    private static func hashLong(_ input: UnsafeRawBufferPointer, _ secret: 
UnsafeRawBufferPointer) -> UInt64 {
+        let length = input.count
+        let blocks = (length - 1) / blockLength
+        return withUnsafeTemporaryAllocation(of: UInt64.self, capacity: 
accumulatorCount) { accumulators in
+            for index in 0..<accumulatorCount {
+                accumulators.initializeElement(at: index, to: 
initialAccumulators[index])
+            }
+            for block in 0..<blocks {
+                accumulate(accumulators, input, block * blockLength, secret, 
stripes: stripesPerBlock)
+                scramble(accumulators, secret, secretLength - stripeLength)
+            }
+            let stripes = ((length - 1) - blockLength * blocks) / stripeLength
+            accumulate(accumulators, input, blocks * blockLength, secret, 
stripes: stripes)
+            accumulate512(accumulators, input, length - stripeLength, secret, 
secretLength - stripeLength - secretLastAccumulatorStart)
+            return mergeAccumulators(accumulators, secret, 
secretMergeAccumulatorsStart, start: UInt64(length) &* prime64_1)
+        }
+    }
+
+    @inline(__always)
+    private static func accumulate512(
+        _ accumulators: UnsafeMutableBufferPointer<UInt64>, _ input: 
UnsafeRawBufferPointer, _ inputOffset: Int, _ secret: UnsafeRawBufferPointer,
+        _ secretOffset: Int
+    ) {
+        for lane in 0..<accumulatorCount {
+            let dataValue = input.loadLittleEndianUInt64(at: inputOffset + 8 * 
lane)
+            let dataKey = dataValue ^ secret.loadLittleEndianUInt64(at: 
secretOffset + 8 * lane)
+            accumulators[lane ^ 1] &+= dataValue
+            accumulators[lane] &+= UInt64(UInt32(truncatingIfNeeded: dataKey)) 
&* (dataKey >> 32)
+        }
+    }
+
+    @inline(__always)
+    private static func accumulate(
+        _ accumulators: UnsafeMutableBufferPointer<UInt64>, _ input: 
UnsafeRawBufferPointer, _ inputOffset: Int, _ secret: UnsafeRawBufferPointer,
+        stripes: Int
+    ) {
+        for stripe in 0..<stripes {
+            accumulate512(accumulators, input, inputOffset + stripe * 
stripeLength, secret, stripe * secretConsumeRate)
+        }
+    }
+
+    @inline(__always)
+    private static func scramble(_ accumulators: 
UnsafeMutableBufferPointer<UInt64>, _ secret: UnsafeRawBufferPointer, _ 
secretOffset: Int) {
+        for lane in 0..<accumulatorCount {
+            let key = secret.loadLittleEndianUInt64(at: secretOffset + 8 * 
lane)
+            var value = accumulators[lane]
+            value ^= value >> 47
+            value ^= key
+            value &*= prime32_1
+            accumulators[lane] = value
+        }
+    }
+
+    private static func mergeAccumulators(
+        _ accumulators: UnsafeMutableBufferPointer<UInt64>, _ secret: 
UnsafeRawBufferPointer, _ secretOffset: Int, start: UInt64
+    ) -> UInt64 {
+        var result = start
+        for pair in 0..<4 {
+            let low = accumulators[2 * pair] ^ 
secret.loadLittleEndianUInt64(at: secretOffset + 16 * pair)
+            let high = accumulators[2 * pair + 1] ^ 
secret.loadLittleEndianUInt64(at: secretOffset + 16 * pair + 8)
+            result &+= mul128Fold64(low, high)
+        }
+        return avalanche(result)
+    }
+}
diff --git a/foreign/swift/Sources/Iggy/Hash/XXH32.swift 
b/foreign/swift/Sources/Iggy/Hash/XXH32.swift
new file mode 100644
index 000000000..1489bb511
--- /dev/null
+++ b/foreign/swift/Sources/Iggy/Hash/XXH32.swift
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+/// XXH32 with a zero seed: the hash every SDK uses to map a messages key to a
+/// partition, so a key lands on the same partition whichever SDK produced it.
+enum XXH32 {
+    private static let prime1: UInt32 = 0x9E37_79B1
+    private static let prime2: UInt32 = 0x85EB_CA77
+    private static let prime3: UInt32 = 0xC2B2_AE3D
+    private static let prime4: UInt32 = 0x27D4_EB2F
+    private static let prime5: UInt32 = 0x1656_67B1
+
+    static func hash(_ bytes: [UInt8]) -> UInt32 {
+        bytes.withUnsafeBytes { hash($0) }
+    }
+
+    static func hash(_ input: UnsafeRawBufferPointer) -> UInt32 {
+        let length = input.count
+        var offset = 0
+        var hash: UInt32
+        if length >= 16 {
+            var v1 = prime1 &+ prime2
+            var v2 = prime2
+            var v3: UInt32 = 0
+            var v4 = 0 &- prime1
+            while offset + 16 <= length {
+                v1 = round(v1, input.loadLittleEndianUInt32(at: offset))
+                v2 = round(v2, input.loadLittleEndianUInt32(at: offset + 4))
+                v3 = round(v3, input.loadLittleEndianUInt32(at: offset + 8))
+                v4 = round(v4, input.loadLittleEndianUInt32(at: offset + 12))
+                offset += 16
+            }
+            hash = rotl(v1, 1) &+ rotl(v2, 7) &+ rotl(v3, 12) &+ rotl(v4, 18)
+        } else {
+            hash = prime5
+        }
+        hash &+= UInt32(truncatingIfNeeded: length)
+        while offset + 4 <= length {
+            hash &+= input.loadLittleEndianUInt32(at: offset) &* prime3
+            hash = rotl(hash, 17) &* prime4
+            offset += 4
+        }
+        while offset < length {
+            hash &+= UInt32(input[offset]) &* prime5
+            hash = rotl(hash, 11) &* prime1
+            offset += 1
+        }
+        hash ^= hash >> 15
+        hash &*= prime2
+        hash ^= hash >> 13
+        hash &*= prime3
+        hash ^= hash >> 16
+        return hash
+    }
+
+    @inline(__always)
+    private static func rotl(_ value: UInt32, _ count: UInt32) -> UInt32 {
+        (value << count) | (value >> (32 - count))
+    }
+
+    @inline(__always)
+    private static func round(_ accumulator: UInt32, _ lane: UInt32) -> UInt32 
{
+        rotl(accumulator &+ lane &* prime2, 13) &* prime1
+    }
+}
diff --git a/foreign/swift/Tests/IggyTests/ErrorCodeTests.swift 
b/foreign/swift/Tests/IggyTests/ErrorCodeTests.swift
index 50750bf33..57410106c 100644
--- a/foreign/swift/Tests/IggyTests/ErrorCodeTests.swift
+++ b/foreign/swift/Tests/IggyTests/ErrorCodeTests.swift
@@ -21,13 +21,10 @@ import Testing
 
 @Suite("Error codes")
 struct ErrorCodeTests {
-    /// Pins the table to the server's `IggyError` discriminants: the count,
-    /// both ends, and a code from every band.
+    /// Spot checks a code from every band by name; the golden test below
+    /// covers the whole table.
     @Test func tableMatchesTheServerCodes() {
-        #expect(IggyErrorCode.allCases.count == 240)
         #expect(IggyErrorCode.error.rawValue == 1)
-        #expect(IggyErrorCode.allCases.map(\.rawValue).min() == 1)
-        #expect(IggyErrorCode.allCases.map(\.rawValue).max() == 14003)
         #expect(IggyErrorCode.incompatibleProtocolVersion.rawValue == 14003)
         #expect(IggyErrorCode(rawValue: 57) == .transientNotCommitted)
         #expect(IggyErrorCode(rawValue: 58) == .transientNotAccepted)
@@ -39,6 +36,17 @@ struct ErrorCodeTests {
         #expect(Set(IggyErrorCode.allCases.map(\.name)).count == 
IggyErrorCode.allCases.count)
     }
 
+    /// The full table, against the dump the Rust generator takes from the
+    /// server's error enum.
+    @Test func errorCodesMatchTheServerTable() {
+        let golden = GoldenFixture.shared
+        let known = Dictionary(uniqueKeysWithValues: 
IggyErrorCode.allCases.map { ($0.rawValue, $0.name) })
+        #expect(known.count == golden.errors.count)
+        for entry in golden.errors {
+            #expect(known[entry.code] == entry.name, "code \(entry.code)")
+        }
+    }
+
     @Test func unknownWireCodesKeepTheRawValue() {
         let error = IggyError(wireCode: 65_535)
         #expect(error.code == .error)
diff --git a/foreign/swift/Tests/IggyTests/Fixtures/golden.json 
b/foreign/swift/Tests/IggyTests/Fixtures/golden.json
new file mode 100644
index 000000000..5ce2ef942
--- /dev/null
+++ b/foreign/swift/Tests/IggyTests/Fixtures/golden.json
@@ -0,0 +1,1004 @@
+{
+  "protocol_version": 11264,
+  "protocol_version_min": 11264,
+  "xxh3_64": {
+    "0": "2d06800538d394c2",
+    "1": "4c5cca45d0f4811f",
+    "2": "a7e250c97710ff27",
+    "3": "15f7093b173d005c",
+    "4": "dca012f95811b6b9",
+    "5": "b290cafc7b254345",
+    "6": "99b2e675fba1e0b5",
+    "7": "7561869c23da3c1b",
+    "8": "dec6a9a43575982e",
+    "9": "cbe393399f17ffbd",
+    "10": "02ab330b44074ad1",
+    "11": "32f214df8eed29c2",
+    "12": "46aaf92c7550afa4",
+    "13": "9e314510b0dbd95c",
+    "14": "11c33b5547089feb",
+    "15": "545e19990471dc37",
+    "16": "7e484c18d74895d0",
+    "17": "208bde5ee2bed407",
+    "18": "0b2fc9efca72f11f",
+    "19": "4a8e9636611ef195",
+    "20": "b752b00444280fd5",
+    "21": "c0c251a7aaa88000",
+    "22": "00bec0f929fb1424",
+    "23": "f1ed4292dd803ac6",
+    "24": "48953b9e46ea32c5",
+    "25": "2cb743cc698a6b5b",
+    "26": "be451d7ce35fa496",
+    "27": "482ddc5d1c67b259",
+    "28": "711a5d15799c5096",
+    "29": "77faf0f24f702d64",
+    "30": "96a2eca234677819",
+    "31": "a937652b0119ca11",
+    "32": "03df0ac5255d1446",
+    "33": "199a362122d71f46",
+    "34": "502358051cdae375",
+    "35": "3979f3a31317573b",
+    "36": "362c6fed990fb45f",
+    "37": "e07bdcb266833293",
+    "38": "3ceb2f4c443330ce",
+    "39": "35590c4b1b716079",
+    "40": "80ef652ab3f1b4b5",
+    "41": "f88aad891ae21edc",
+    "42": "491e0741086bc8b1",
+    "43": "7faba0b013e5699b",
+    "44": "63cc939553a0f673",
+    "45": "fef1383ea53f9386",
+    "46": "ffaa39765685d4d8",
+    "47": "0b9d89b90ef6f825",
+    "48": "37fc8c692bc274e1",
+    "49": "600574020639d802",
+    "50": "98eac754de954c04",
+    "51": "4ef18bcbbbe5c7b4",
+    "52": "b618d180fb23c0e7",
+    "53": "a88e3182010cacd3",
+    "54": "e670fa5605d0bbcf",
+    "55": "1fc37b65eeb8ce92",
+    "56": "3c837cc3c15a01bd",
+    "57": "5298171a4b2c5766",
+    "58": "a4f0435a673b9650",
+    "59": "a12450aededa97c1",
+    "60": "5b53af902b44d30e",
+    "61": "104cc689e1c6359f",
+    "62": "c7415ebecf37be00",
+    "63": "76d4eec1f092847f",
+    "64": "dd30702ab46b3745",
+    "65": "fab36b851b94ce20",
+    "66": "7ed877eaeb7ab09e",
+    "67": "5679e93ebb6b4667",
+    "68": "48a7f2c0bb6660d6",
+    "69": "5b605745067f8f78",
+    "70": "d254549641ca7cda",
+    "71": "6b020d44d649cd18",
+    "72": "8a3f6fb70e7f3c5a",
+    "73": "d63ad17c03358856",
+    "74": "adb5d6b84bb0c7e6",
+    "75": "f38e445be5bb7774",
+    "76": "924ae4523fda2623",
+    "77": "80af1a82d31f26ed",
+    "78": "5bae338f3410811f",
+    "79": "2db5e7edf9911248",
+    "80": "bab4673ab248e1e2",
+    "81": "adbb20e609fbccf3",
+    "82": "914a22ae0735c26c",
+    "83": "d310fea8290adf1c",
+    "84": "ef3d3650aeba996d",
+    "85": "a6b8f2b76480c80f",
+    "86": "422f63ca8db1dc58",
+    "87": "578e0991378b0edb",
+    "88": "56b28c120f398e4e",
+    "89": "b1b9cd0d8836c6e5",
+    "90": "8bf0778b4af4060a",
+    "91": "6c1f0cdee38bbe57",
+    "92": "c8c7b7a3f485984c",
+    "93": "bb1e53010e1fe285",
+    "94": "a381f91d6391149b",
+    "95": "9857e7a5a42c6fe9",
+    "96": "d245cd2541582982",
+    "97": "60e3e1d0d43785b3",
+    "98": "5d08d66481f2ca1d",
+    "99": "5e2543d8ebdd1946",
+    "100": "8c97158042fbf926",
+    "101": "d2fdb1aa73b78a0a",
+    "102": "d9ef45a695356955",
+    "103": "22e5af597a44ac05",
+    "104": "d3dce276c955e780",
+    "105": "77b5b20585cb1848",
+    "106": "fa0d562d176e7144",
+    "107": "763f0966982e0c4e",
+    "108": "baa3a573cadfa833",
+    "109": "56f9c083f935b104",
+    "110": "60bca2d0a693e0ff",
+    "111": "22d06ad6c0216ec3",
+    "112": "dbae773438781980",
+    "113": "18f12e161c06865b",
+    "114": "c48fc7c4e67f0a55",
+    "115": "5394b3c6f33d6c02",
+    "116": "f9dd9e1211e25d2d",
+    "117": "7044bacb399d52ca",
+    "118": "a97b6c41a31ca72d",
+    "119": "d1fa6a6c33ed184c",
+    "120": "dbb9fc4818080eba",
+    "121": "5884cd0cf2cb9749",
+    "122": "866a55eee34e87b1",
+    "123": "08649efe402a8b29",
+    "124": "71ec3fce38b2ea36",
+    "125": "65e3707c889a12d0",
+    "126": "71e3999204f56048",
+    "127": "a915ed6396db8cc0",
+    "128": "f92b70eaa21a6288",
+    "129": "f8f76713f2bb60fa",
+    "130": "303a2c3823c3c34a",
+    "131": "9ddf75554d4ef43f",
+    "132": "ec1ecd2020110609",
+    "133": "8374b8d42931f8f1",
+    "134": "94dfabf82db326cf",
+    "135": "54659005622c9884",
+    "136": "a62bec58f8de6b28",
+    "137": "a1eccf34d87679ab",
+    "138": "e1a6abf613d0c713",
+    "139": "d4d888ea09ad8be2",
+    "140": "73f9bf9367a34a5a",
+    "141": "265cb82209b57673",
+    "142": "c56b3ae0803aa7f2",
+    "143": "c372f52c27156971",
+    "144": "d87f00765ee3d89e",
+    "145": "65d480417b4734f8",
+    "146": "86c2036ce5e060f8",
+    "147": "d07b5c2f0fefd8a8",
+    "148": "91dbbdae16d82b72",
+    "149": "7749c551163f6b04",
+    "150": "b87aa95cb5f54aa2",
+    "151": "01246a53c1e24e38",
+    "152": "665c91b034d599be",
+    "153": "ee6ddbe8d073c666",
+    "154": "2f8d3364d5dfccc8",
+    "155": "11f85d6cd9966443",
+    "156": "15f260ba4b1e0968",
+    "157": "063b1ecd55a72930",
+    "158": "25bb4a9fb16a2d48",
+    "159": "a327b7cae27c8e10",
+    "160": "c90911ffcef461e2",
+    "161": "f11ed84f70bade2e",
+    "162": "de69b26de1a9b3f2",
+    "163": "0ae8762e72422656",
+    "164": "4b00656e32461c31",
+    "165": "7ba0c08511bbe8b3",
+    "166": "27cda8258d9b1179",
+    "167": "87b3e083141f3409",
+    "168": "3a62b95662105e41",
+    "169": "01b490d2d915abad",
+    "170": "643721c9bf32781c",
+    "171": "8287c649ce43e397",
+    "172": "a3dae8f46d8fd330",
+    "173": "c1f47864f99336fc",
+    "174": "5c0072559c0dd67b",
+    "175": "bfdab869a2187b30",
+    "176": "67f10aaf46d79a44",
+    "177": "a8ece1c90e74efbf",
+    "178": "50887a99d15a2cfa",
+    "179": "32da48475d1b8ad5",
+    "180": "bb150b04dbb71027",
+    "181": "ce006955e8ea3d6a",
+    "182": "3d8cc59b34a1d734",
+    "183": "8807c2bf48aadebc",
+    "184": "530061af42546bfa",
+    "185": "955b87dacb465bb7",
+    "186": "82f6fee48cc701c4",
+    "187": "338ed6d7173ffe5d",
+    "188": "e29ffc4b065c0c89",
+    "189": "0fe5c2e35a759ae3",
+    "190": "2f77a9373bb6e149",
+    "191": "f0025e0992b308fb",
+    "192": "084028fa06f01d30",
+    "193": "f0eb08bf31ca42b6",
+    "194": "88510eba2c60efe4",
+    "195": "5ea76d72c792b501",
+    "196": "69a29b581ae42494",
+    "197": "7b7dcee881e9ea8c",
+    "198": "3622e29db96318fc",
+    "199": "8124966e9386ec04",
+    "200": "12fdb864685f344d",
+    "201": "16101b47f8644472",
+    "202": "4a2dec05a37c91a0",
+    "203": "0fac1fabb0a5293a",
+    "204": "0e1f9008494d4fd7",
+    "205": "dc66165d9b20e180",
+    "206": "1718ea095c340389",
+    "207": "5f43962e18eca50e",
+    "208": "80826d7a65be5e69",
+    "209": "6a4f9f953de8f7e4",
+    "210": "afcc79d2b952690b",
+    "211": "b95f2c918d8fa761",
+    "212": "de10988270de16b4",
+    "213": "7f939839dd0460dd",
+    "214": "71c112f5c1a6c2ec",
+    "215": "2b3d2c4e3dca7a16",
+    "216": "7de819be3e17b031",
+    "217": "24667ff21981291c",
+    "218": "403cec6b6dde22fd",
+    "219": "a21db8d0bfda5b12",
+    "220": "72dc002165c95f3c",
+    "221": "5efc45722724a76f",
+    "222": "beff6aef7d2c8900",
+    "223": "6e8f4c03accab5a4",
+    "224": "e69e5c66a23443bc",
+    "225": "d01a4f7215895c93",
+    "226": "33cd79ebc1406b8e",
+    "227": "d16389c3956e82db",
+    "228": "8ba30402067714e9",
+    "229": "efb75e81f42711c2",
+    "230": "b73ba8942ee8ec15",
+    "231": "90b17ddb0b598b06",
+    "232": "1eb54c570c6806ff",
+    "233": "409daebb5cf1cbe0",
+    "234": "7f230cb54edc2575",
+    "235": "89aa4d3171c051ba",
+    "236": "a2c90d7776c670eb",
+    "237": "c80c97d6950b23f5",
+    "238": "7f477f909665b010",
+    "239": "caa9b7a588464745",
+    "240": "ccc7375172c41f03",
+    "241": "0b3b630948ce4a00",
+    "242": "64ac2c6712669e05",
+    "243": "cffaa40f5965922d",
+    "244": "79217054e33ac2a4",
+    "245": "bbedbcb1301bb022",
+    "246": "39b4015e13725a91",
+    "247": "7241d3e647366786",
+    "248": "81d90928ac0eeb66",
+    "249": "2f4f47dd7c3957d9",
+    "250": "359fe5d526851d06",
+    "251": "0ea0f7ed02748029",
+    "252": "a9ccd8780a626691",
+    "253": "aea766e710bb3aeb",
+    "254": "51b4770c44653ef6",
+    "255": "89932170686cdd9a",
+    "256": "ec85b75bafe6ca74",
+    "257": "12ef0ff633841459",
+    "258": "a4e6408d09e952e3",
+    "259": "954bb7c819d7c692",
+    "260": "4150449c54ae2818",
+    "261": "490bafd842c62536",
+    "262": "93c74114185bb9f1",
+    "263": "8c32f72ee30ed67b",
+    "264": "cfec02e4c65bd842",
+    "265": "cba945b79bf66219",
+    "266": "73cc985d4262f1bd",
+    "267": "ea08024b75cee207",
+    "268": "a34a41f5b9290997",
+    "269": "9849db6a8d8a039d",
+    "270": "2d3f19fb47538bfd",
+    "271": "1462787cc436c2ac",
+    "272": "7818444dc3fc7377",
+    "273": "be33f0cc03efec53",
+    "274": "40209d9b28a3090c",
+    "275": "1f2190eae6926c89",
+    "276": "08a98724b9d4be39",
+    "277": "073cc2461c21a8c2",
+    "278": "6573d2625ad5453b",
+    "279": "238e1629b7d8a8dd",
+    "280": "9020cab4d10a7dd4",
+    "281": "3f18233e9a6fefb2",
+    "282": "8e61883aeb841cf6",
+    "283": "ac3e9f2639b847d1",
+    "284": "231ca645b3d6d9d7",
+    "285": "b547f515e1870e8a",
+    "286": "afcdf1eda6591016",
+    "287": "141879f6e2da2d26",
+    "288": "cd3525433788a0c1",
+    "289": "4b4b552404655f01",
+    "290": "c2857d0990b83292",
+    "291": "e5e33ec16832605a",
+    "292": "772c8707770ae6c7",
+    "293": "268b0d6bf2a299b3",
+    "294": "b04d39c78d01f277",
+    "295": "e087beb0838fa21b",
+    "296": "e3759db580b6b195",
+    "297": "43c31cb1bcd727ac",
+    "298": "c2ec247efd191d2e",
+    "299": "c218b3a960ab59ac",
+    "300": "baad8e6a5f186ce6",
+    "511": "0fec8fb6eae1df8b",
+    "512": "efc2e52d1c2ffbaa",
+    "513": "eb39b64b9df98485",
+    "1000": "989765d0ea7a5ecd",
+    "1023": "f0d330ce2b3300fb",
+    "1024": "23bc880ebf0d29c6",
+    "1025": "c09fdfbc398c7d82",
+    "1279": "4e4a4f22808ca125",
+    "1280": "12b5908a8421697e",
+    "1281": "77f822b489e658c9",
+    "2047": "c49f1b36d955611e",
+    "2048": "19f6f9c987331373",
+    "2049": "5045460f5d85c275",
+    "4096": "a3c19f8174cde0bb",
+    "10000": "441f01d9711bebed",
+    "65536": "cf188822048798b0",
+    "100003": "e0950e66cf107fe8"
+  },
+  "xxh32": {
+    "0": "02cc5d05",
+    "1": "002e0d32",
+    "2": "ceb77c10",
+    "3": "aca17380",
+    "4": "073faa82",
+    "5": "16ac9537",
+    "6": "28e6b74b",
+    "7": "a9a7eebf",
+    "8": "b9893c6c",
+    "9": "ef682a43",
+    "10": "df648e86",
+    "11": "b79882ee",
+    "12": "9a1b075b",
+    "13": "84981cca",
+    "14": "f0a519a1",
+    "15": "9f29f87b",
+    "16": "3f6c9665",
+    "17": "e048ecdb",
+    "18": "e735aa1b",
+    "19": "342f1b7a",
+    "20": "cd1d4cf6",
+    "21": "dcb4db13",
+    "22": "4a959fa8",
+    "23": "0e236d9c",
+    "24": "40e1bf6c",
+    "25": "2cd48e6f",
+    "26": "9ab2fa79",
+    "27": "d658b557",
+    "28": "932ee011",
+    "29": "0da6ba78",
+    "30": "70f7a448",
+    "31": "00f1525c",
+    "32": "ee42e668",
+    "33": "770b5126",
+    "34": "d06f9029",
+    "35": "d0b8e260",
+    "36": "2322d463",
+    "37": "ba3f318f",
+    "38": "c0b27692",
+    "39": "06f7aac6",
+    "40": "404d20b6",
+    "41": "bd6bfc29",
+    "42": "18ac587c",
+    "43": "011b4a4a",
+    "44": "4de820bc",
+    "45": "811826a2",
+    "46": "ddb70dd9",
+    "47": "9b8b41ae",
+    "48": "cddaf19f",
+    "49": "9ff7eb17",
+    "50": "7d6b4ad7",
+    "51": "92a09016",
+    "52": "3a5fb3b9",
+    "53": "cc8f1214",
+    "54": "7fdfbfec",
+    "55": "9a4510e6",
+    "56": "10a4653e",
+    "57": "938ea8cb",
+    "58": "6e19f2da",
+    "59": "9b35c6a0",
+    "60": "f108d254",
+    "61": "9b46dfdb",
+    "62": "5b34f093",
+    "63": "471925bb",
+    "64": "f2a797fa",
+    "65": "832b0571",
+    "66": "83479242",
+    "67": "43e06d37",
+    "68": "1a59277c",
+    "69": "0e2caae7",
+    "70": "b1de93c7",
+    "71": "f5d3d88b",
+    "72": "ebd57645",
+    "73": "df41e4c0",
+    "74": "9d04e365",
+    "75": "f1f7f28b",
+    "76": "fe438835",
+    "77": "b9706d77",
+    "78": "79da63fd",
+    "79": "4798b7da",
+    "80": "5941d56e",
+    "81": "317dd0a0",
+    "82": "be2781b0",
+    "83": "82271a1e",
+    "84": "aec7c13c",
+    "85": "8affc730",
+    "86": "a367d4f7",
+    "87": "55b4e6ec",
+    "88": "810f5925",
+    "89": "e0e8454f",
+    "90": "4dce1e40",
+    "91": "df7e00e1",
+    "92": "9a87dcaa",
+    "93": "0a9a1189",
+    "94": "10500aa8",
+    "95": "e6f121e9",
+    "96": "d5a45742",
+    "97": "329fc2fd",
+    "98": "05b76f9c",
+    "99": "b9134f58",
+    "100": "75936eb8",
+    "101": "a876209d",
+    "102": "40aec185",
+    "103": "23de2ca4",
+    "104": "5d029390",
+    "105": "ff3aaa59",
+    "106": "16017ee3",
+    "107": "92c34c0b",
+    "108": "31684422",
+    "109": "158f3945",
+    "110": "414b5aa7",
+    "111": "5181fd0c",
+    "112": "1a8fe9e8",
+    "113": "4978c428",
+    "114": "f8c41f86",
+    "115": "bd0885e5",
+    "116": "0077cb85",
+    "117": "a91c56e4",
+    "118": "1cff50ac",
+    "119": "b3110bfc",
+    "120": "9ac4fe4f",
+    "121": "537ca2c9",
+    "122": "46d63e82",
+    "123": "1c85a07c",
+    "124": "7be6c5bd",
+    "125": "99b14eb0",
+    "126": "3a91dc40",
+    "127": "86025592",
+    "128": "0bf98264",
+    "129": "f2d10021",
+    "130": "1392a7bd",
+    "131": "30cb6f4d",
+    "132": "a9c3af8c",
+    "133": "ec2be087",
+    "134": "b199db99",
+    "135": "616568b5",
+    "136": "3d7d5662",
+    "137": "5d758663",
+    "138": "3a83b054",
+    "139": "95588c70",
+    "140": "2aa29cfd",
+    "141": "6d7eef4e",
+    "142": "19df68de",
+    "143": "6facb331",
+    "144": "db5579cb",
+    "145": "545163cb",
+    "146": "b355146e",
+    "147": "39c7b330",
+    "148": "55690afb",
+    "149": "a4d7b73e",
+    "150": "e72476e7",
+    "151": "d789523c",
+    "152": "bfe4811e",
+    "153": "405f132b",
+    "154": "6a5ef718",
+    "155": "5954ade0",
+    "156": "a69ac10d",
+    "157": "f38ddccb",
+    "158": "0548ae32",
+    "159": "0046cd9f",
+    "160": "c95973c8",
+    "161": "fbe49a53",
+    "162": "7a1832c0",
+    "163": "272ef877",
+    "164": "61e783b4",
+    "165": "d4ea4da7",
+    "166": "5a15bd89",
+    "167": "873824f4",
+    "168": "fc1163ea",
+    "169": "30f344e8",
+    "170": "610f4a57",
+    "171": "6c627d2e",
+    "172": "6779419f",
+    "173": "fb172823",
+    "174": "203816c7",
+    "175": "f9654ce0",
+    "176": "905716fd",
+    "177": "d065830e",
+    "178": "ab7b5244",
+    "179": "d2211f0e",
+    "180": "f83e2a14",
+    "181": "7ebb5d63",
+    "182": "738b1196",
+    "183": "62ce0895",
+    "184": "65382b10",
+    "185": "2299631e",
+    "186": "b5f9edf6",
+    "187": "85b83c4b",
+    "188": "dd03425e",
+    "189": "8b7a00c7",
+    "190": "3c6cd6c2",
+    "191": "bdcacc4d",
+    "192": "a730353b",
+    "193": "2f29e535",
+    "194": "2de42d25",
+    "195": "4fd0d7c1",
+    "196": "b979af56",
+    "197": "a94d4089",
+    "198": "fb2b6758",
+    "199": "5fc42e0a",
+    "200": "bf02ae18",
+    "201": "d446c8c4",
+    "202": "2f323967",
+    "203": "bfa0b579",
+    "204": "164800e9",
+    "205": "38623a71",
+    "206": "95c3dbb7",
+    "207": "708e6dfe",
+    "208": "64bf4c2e",
+    "209": "7fc2ad5d",
+    "210": "88df9963",
+    "211": "8f0f0954",
+    "212": "88733897",
+    "213": "b988b775",
+    "214": "b0eb0103",
+    "215": "d9e76484",
+    "216": "a087b647",
+    "217": "5e12429f",
+    "218": "5f855316",
+    "219": "7126b180",
+    "220": "08d9bbf5",
+    "221": "5029c90f",
+    "222": "87221984",
+    "223": "572b4a70",
+    "224": "216e6caa",
+    "225": "9d7a7a8a",
+    "226": "e5a620d4",
+    "227": "7132cd26",
+    "228": "921f3837",
+    "229": "95d40682",
+    "230": "f62fb72f",
+    "231": "2aca433d",
+    "232": "39c767d1",
+    "233": "25c7401c",
+    "234": "69d3a768",
+    "235": "f78f2430",
+    "236": "3c380b37",
+    "237": "19a5ead9",
+    "238": "0df92aef",
+    "239": "f28a7330",
+    "240": "6be3ca8b",
+    "241": "8c1402a0",
+    "242": "061599eb",
+    "243": "d79e0848",
+    "244": "d52430aa",
+    "245": "3894c6c7",
+    "246": "4a7b19f3",
+    "247": "44e2dfb5",
+    "248": "fbe16171",
+    "249": "e2d44691",
+    "250": "5dafd373",
+    "251": "b4c89dd3",
+    "252": "023ea9ca",
+    "253": "68dcd4a4",
+    "254": "ce927921",
+    "255": "dbe7dd0a",
+    "256": "d40ce0c6",
+    "257": "c1aba8c3",
+    "258": "40f4d5e7",
+    "259": "cb28799e",
+    "260": "a01039df",
+    "261": "fc02b393",
+    "262": "7bb829e3",
+    "263": "72f1ebe2",
+    "264": "efe97ef0",
+    "265": "e842dc00",
+    "266": "10a1bb8a",
+    "267": "2c360d7a",
+    "268": "12b0a3f8",
+    "269": "31214a23",
+    "270": "e00e6457",
+    "271": "ecf57d22",
+    "272": "7478f037",
+    "273": "31d7156c",
+    "274": "7e0dd53e",
+    "275": "68b430b6",
+    "276": "32506bad",
+    "277": "f0e91c11",
+    "278": "8d25c0ef",
+    "279": "cfbd0c21",
+    "280": "dd1fefc9",
+    "281": "ef012cdb",
+    "282": "ba3cd529",
+    "283": "bc283309",
+    "284": "18acf6e9",
+    "285": "6b131ce8",
+    "286": "8d41c940",
+    "287": "a82b85a4",
+    "288": "887dff95",
+    "289": "de705416",
+    "290": "04a48f97",
+    "291": "88bf730d",
+    "292": "56c4edbd",
+    "293": "95e86815",
+    "294": "7e55cbf4",
+    "295": "3f540617",
+    "296": "59a4c515",
+    "297": "1982239e",
+    "298": "1b21f991",
+    "299": "49cced94",
+    "300": "ec3c8ce2",
+    "511": "da196d23",
+    "512": "91fc3706",
+    "513": "13c6bd04",
+    "1000": "a793e7c7",
+    "1023": "2f81efc2",
+    "1024": "ead2ce3c",
+    "1025": "de7d7475",
+    "1279": "768b3053",
+    "1280": "b606805a",
+    "1281": "7b3f1b44",
+    "2047": "7a5fe81e",
+    "2048": "62bf26bf",
+    "2049": "ce1d9edf",
+    "4096": "f11917ba",
+    "10000": "80691c39",
+    "65536": "38f0653c",
+    "100003": "20796e40"
+  },
+  "errors": {
+    "1": "error",
+    "2": "invalid_configuration",
+    "3": "invalid_command",
+    "4": "invalid_format",
+    "5": "feature_unavailable",
+    "6": "invalid_identifier",
+    "7": "invalid_version",
+    "8": "disconnected",
+    "9": "cannot_establish_connection",
+    "10": "cannot_create_base_directory",
+    "11": "cannot_create_runtime_directory",
+    "12": "cannot_remove_runtime_directory",
+    "13": "cannot_create_state_directory",
+    "14": "state_file_not_found",
+    "15": "state_file_corrupted",
+    "16": "invalid_state_entry_checksum",
+    "19": "cannot_open_database",
+    "20": "resource_not_found",
+    "21": "cannot_close_web_socket_connection",
+    "30": "stale_client",
+    "31": "tcp_error",
+    "32": "quic_error",
+    "33": "invalid_server_address",
+    "34": "invalid_client_address",
+    "35": "invalid_ip_address",
+    "36": "http_error",
+    "37": "invalid_api_url",
+    "40": "unauthenticated",
+    "41": "unauthorized",
+    "42": "invalid_credentials",
+    "43": "invalid_username",
+    "44": "invalid_password",
+    "45": "invalid_user_status",
+    "46": "user_already_exists",
+    "47": "user_inactive",
+    "48": "cannot_delete_user",
+    "49": "cannot_change_permissions",
+    "50": "invalid_personal_access_token_name",
+    "51": "personal_access_token_already_exists",
+    "52": "personal_access_tokens_limit_reached",
+    "53": "invalid_personal_access_token",
+    "54": "personal_access_token_expired",
+    "55": "users_limit_reached",
+    "56": "invalid_personal_access_token_expiry",
+    "57": "transient_not_committed",
+    "58": "transient_not_accepted",
+    "59": "request_already_applied",
+    "61": "not_connected",
+    "63": "client_shutdown",
+    "64": "invalid_tls_domain",
+    "65": "invalid_tls_certificate_path",
+    "66": "invalid_tls_certificate",
+    "67": "failed_to_add_certificate",
+    "70": "invalid_encryption_key",
+    "71": "cannot_encrypt_data",
+    "72": "cannot_decrypt_data",
+    "73": "invalid_jwt_algorithm",
+    "74": "invalid_jwt_secret",
+    "75": "jwt_missing",
+    "76": "cannot_generate_jwt",
+    "77": "access_token_missing",
+    "78": "invalid_access_token",
+    "79": "cannot_fetch_jwks",
+    "80": "invalid_size_bytes",
+    "81": "invalid_utf8",
+    "82": "invalid_number_encoding",
+    "83": "invalid_boolean_value",
+    "84": "invalid_number_value",
+    "100": "client_not_found",
+    "101": "invalid_client_id",
+    "206": "connection_closed",
+    "209": "cannot_parse_header_kind",
+    "300": "http_response_error",
+    "301": "invalid_http_request",
+    "302": "invalid_json_response",
+    "303": "invalid_bytes_response",
+    "304": "empty_response",
+    "305": "cannot_create_endpoint",
+    "306": "cannot_parse_url",
+    "400": "web_socket_error",
+    "401": "web_socket_connection_error",
+    "402": "web_socket_close_error",
+    "403": "web_socket_receive_error",
+    "404": "web_socket_send_error",
+    "1000": "cannot_create_streams_directory",
+    "1001": "cannot_create_stream_directory",
+    "1002": "cannot_create_stream_info",
+    "1003": "cannot_update_stream_info",
+    "1004": "cannot_open_stream_info",
+    "1005": "cannot_read_stream_info",
+    "1006": "cannot_create_stream",
+    "1007": "cannot_delete_stream",
+    "1008": "cannot_delete_stream_directory",
+    "1009": "stream_id_not_found",
+    "1010": "stream_name_not_found",
+    "1011": "stream_directory_not_found",
+    "1012": "stream_name_already_exists",
+    "1013": "invalid_stream_name",
+    "1014": "invalid_stream_id",
+    "1015": "cannot_read_streams",
+    "1019": "invalid_topic_size",
+    "1020": "too_many_streams",
+    "2000": "cannot_create_topics_directory",
+    "2001": "cannot_create_topic_directory",
+    "2002": "cannot_create_topic_info",
+    "2003": "cannot_update_topic_info",
+    "2004": "cannot_open_topic_info",
+    "2005": "cannot_read_topic_info",
+    "2006": "cannot_create_topic",
+    "2007": "cannot_delete_topic",
+    "2008": "cannot_delete_topic_directory",
+    "2009": "cannot_poll_topic",
+    "2010": "topic_id_not_found",
+    "2011": "topic_name_not_found",
+    "2013": "topic_name_already_exists",
+    "2014": "invalid_topic_name",
+    "2015": "too_many_partitions",
+    "2016": "invalid_topic_id",
+    "2017": "cannot_read_topics",
+    "2018": "invalid_replication_factor",
+    "2019": "invalid_partitions_count",
+    "2020": "topic_directory_not_found",
+    "2021": "too_many_topics",
+    "3000": "cannot_create_partition",
+    "3001": "cannot_create_partitions_directory",
+    "3002": "cannot_create_partition_directory",
+    "3003": "cannot_open_partition_log_file",
+    "3004": "cannot_read_partitions",
+    "3005": "cannot_delete_partition",
+    "3006": "cannot_delete_partition_directory",
+    "3007": "partition_not_found",
+    "3008": "no_partitions",
+    "3009": "topic_full",
+    "3010": "cannot_delete_consumer_offsets_directory",
+    "3011": "cannot_delete_consumer_offset_file",
+    "3012": "cannot_create_consumer_offsets_directory",
+    "3013": "partition_id_space_exhausted",
+    "3020": "cannot_read_consumer_offsets",
+    "3021": "consumer_offset_not_found",
+    "3022": "not_resolved_consumer",
+    "3023": "cannot_open_consumer_offsets_file",
+    "3024": "too_many_consumer_offsets",
+    "4000": "segment_not_found",
+    "4001": "segment_closed",
+    "4002": "invalid_segment_size",
+    "4003": "cannot_create_segment_log_file",
+    "4004": "cannot_create_segment_index_file",
+    "4005": "cannot_create_segment_time_index_file",
+    "4006": "cannot_save_messages_to_segment",
+    "4007": "cannot_save_index_to_segment",
+    "4008": "cannot_save_time_index_to_segment",
+    "4009": "invalid_messages_count",
+    "4010": "cannot_append_message",
+    "4011": "cannot_read_message",
+    "4012": "cannot_read_message_id",
+    "4013": "cannot_read_message_state",
+    "4014": "cannot_read_message_timestamp",
+    "4015": "cannot_read_headers_length",
+    "4016": "cannot_read_headers_payload",
+    "4017": "too_big_user_headers",
+    "4018": "invalid_header_key",
+    "4019": "invalid_header_value",
+    "4020": "cannot_read_message_length",
+    "4021": "cannot_read_message_payload",
+    "4022": "too_big_message_payload",
+    "4023": "too_many_messages",
+    "4024": "empty_message_payload",
+    "4025": "invalid_message_payload_length",
+    "4026": "cannot_read_message_checksum",
+    "4027": "invalid_message_checksum",
+    "4028": "invalid_key_value_length",
+    "4029": "command_length_error",
+    "4030": "invalid_segments_count",
+    "4031": "non_zero_offset",
+    "4032": "non_zero_timestamp",
+    "4033": "missing_index",
+    "4034": "invalid_indexes_byte_size",
+    "4035": "invalid_indexes_count",
+    "4036": "invalid_messages_size",
+    "4037": "too_small_message",
+    "4038": "invalid_message_timestamp_delta",
+    "4039": "invalid_batch_checksum",
+    "4040": "invalid_header_kind",
+    "4041": "unsupported_option_key",
+    "4042": "invalid_option_value",
+    "4043": "options_block_too_large",
+    "4050": "cannot_send_messages_due_to_client_disconnection",
+    "4051": "background_send_error",
+    "4052": "background_send_timeout",
+    "4053": "background_send_buffer_full",
+    "4054": "background_worker_disconnected",
+    "4055": "background_send_buffer_overflow",
+    "4056": "producer_send_failed",
+    "4057": "producer_closed",
+    "4100": "invalid_offset",
+    "4101": "invalid_reserved_field",
+    "4102": "segment_size_mismatch_at_open",
+    "5000": "consumer_group_id_not_found",
+    "5002": "invalid_consumer_group_id",
+    "5003": "consumer_group_name_not_found",
+    "5004": "consumer_group_name_already_exists",
+    "5005": "invalid_consumer_group_name",
+    "5006": "consumer_group_member_not_found",
+    "5007": "cannot_create_consumer_group_info",
+    "5008": "cannot_delete_consumer_group_info",
+    "5009": "consumer_group_partition_not_owned",
+    "6000": "missing_base_offset_retained_message_batch",
+    "6001": "missing_last_offset_delta_retained_message_batch",
+    "6002": "missing_max_timestamp_retained_message_batch",
+    "6003": "missing_length_retained_message_batch",
+    "6004": "missing_payload_retained_message_batch",
+    "7000": "cannot_read_batch_base_offset",
+    "7001": "cannot_read_batch_length",
+    "7002": "cannot_read_last_offset_delta",
+    "7003": "cannot_read_max_timestamp",
+    "7004": "cannot_read_batch_payload",
+    "8000": "invalid_connection_string",
+    "9000": "snapshot_file_completion_failed",
+    "10000": "cannot_serialize_resource",
+    "10001": "cannot_deserialize_resource",
+    "10002": "cannot_read_file",
+    "10003": "cannot_read_file_metadata",
+    "10004": "cannot_seek_file",
+    "10005": "cannot_append_to_file",
+    "10006": "cannot_write_to_file",
+    "10007": "cannot_overwrite_file",
+    "10008": "cannot_delete_file",
+    "10009": "cannot_sync_file",
+    "10010": "cannot_read_index_offset",
+    "10011": "cannot_read_index_position",
+    "10012": "cannot_read_index_timestamp",
+    "10013": "timestamp_out_of_range",
+    "11000": "shard_not_found",
+    "11001": "shard_communication_error",
+    "12000": "cannot_bind_to_socket",
+    "12001": "task_timeout",
+    "13000": "io_error",
+    "14000": "already_authenticated",
+    "14001": "invalid_session",
+    "14003": "incompatible_protocol_version"
+  },
+  "vectors": {
+    "consumer.numeric.42": "0101042a000000",
+    "consumer_group.named.my-group": "0202086d792d67726f7570",
+    "identifier.named.my-stream": "02096d792d73747265616d",
+    "identifier.numeric.1": "010401000000",
+    "options.golden": 
"020a0000006475726162696c6974790209000000706572736973746564020c0000007365676d656e745f73697a650c080000000000004000000000",
+    "options.topic_create.full": 
"0215000000636f6d7072657373696f6e5f616c676f726974686d0204000000677a6970021a000000636f6e73756d65725f6f66667365745f6475726162696c697479020a0000007265706c696361746564020a0000006475726162696c6974790209000000706572736973746564020e0000006d61785f746f7069635f73697a650c080000000000004000000000020e0000006d6573736167655f6578706972790c0800000000a0e3d08c00000002190000006d657373616765735f72657175697265645f746f5f736176650b04000000070000000214000000707265616c6c6f63617465
 [...]
+    "options.topic_create.raw": 
"021a000000636f6e73756d65725f6f66667365745f6475726162696c697479020a0000007265706c696361746564020a0000006475726162696c6974790209000000706572736973746564",
+    "options.topic_update.full": 
"0215000000636f6d7072657373696f6e5f616c676f726974686d02040000006e6f6e65020e0000006d61785f746f7069635f73697a650c08000000ffffffffffffffff020e0000006d6573736167655f6578706972790c08000000ffffffffffffffff",
+    "partitioning.balanced": "0100",
+    "partitioning.messages_key.user-123": "0308757365722d313233",
+    "partitioning.partition_id.7": "020407000000",
+    "payload.create_stream": "0673747265616d",
+    "permissions.sample": 
"010100010001000101000101000000010100010100010a000000010100010114000000000101010001020000000001010000010000",
+    "polling.first": "030000000000000000",
+    "polling.last": "040000000000000000",
+    "polling.next": "050000000000000000",
+    "polling.offset.100": "016400000000000000",
+    "polling.timestamp.1700000000000": "020068e5cf8b010000",
+    "request.change_password": 
"010401000000086f6c642d706173730c6e65772d706173732d313233",
+    "request.create_consumer_group": "01040100000001040200000003677270",
+    "request.create_partitions": "01040100000001040200000005000000",
+    "request.create_personal_access_token": 
"086d792d746f6b656e100e000000000000",
+    "request.create_stream": "0b746573742d73747265616d",
+    "request.create_stream.options": 
"0b746573742d73747265616d020a0000006475726162696c6974790209000000706572736973746564020c0000007365676d656e745f73697a650c080000000000004000000000",
+    "request.create_topic": 
"01040100000003000000066f7264657273020a0000006475726162696c6974790209000000706572736973746564020c0000007365676d656e745f73697a650c080000000000004000000000",
+    "request.create_user": 
"0561646d696e087040737377307264010135000000010100010001000101000101000000010100010100010a000000010100010114000000000101010001020000000001010000010000",
+    "request.create_user.no_permissions": "0475736572097365637265743132330200",
+    "request.delete_consumer_group": "010401000000010402000000010403000000",
+    "request.delete_consumer_offset": 
"0101040100000001040a000000010414000000010500000001",
+    "request.delete_partitions": "020673747265616d0205746f70696302000000",
+    "request.delete_personal_access_token": "086d792d746f6b656e",
+    "request.delete_segments": "010401000000010402000000030000000a000000",
+    "request.delete_stream": "010405000000",
+    "request.delete_topic": "010401000000010405000000",
+    "request.delete_user": "02086f6c642d75736572",
+    "request.describe_options": "01",
+    "request.get_client": "2a000000",
+    "request.get_clients": "",
+    "request.get_cluster_metadata": "",
+    "request.get_consumer_group": 
"020873747265616d2d310207746f7069632d31020767726f75702d31",
+    "request.get_consumer_groups": "010401000000010402000000",
+    "request.get_consumer_offset": 
"01020b6d792d636f6e73756d6572020873747265616d2d310207746f7069632d310100000000",
+    "request.get_me": "",
+    "request.get_personal_access_tokens": "",
+    "request.get_snapshot": "02020105",
+    "request.get_stats": "",
+    "request.get_stream": "02096d792d73747265616d",
+    "request.get_streams": "",
+    "request.get_topic": "02096d792d73747265616d02086d792d746f706963",
+    "request.get_topics": "01042a000000",
+    "request.get_user": "01042a000000",
+    "request.get_users": "",
+    "request.join_consumer_group": "010401000000010402000000010403000000",
+    "request.leave_consumer_group": "010401000000010402000000020167",
+    "request.login_register": 
"002c00000973776966742d73646b05302e312e300469676779046967677900000000",
+    "request.login_register_with_pat": 
"002c00000973776966742d73646b05302e312e30107061742d61626331323364656634353600000000",
+    "request.logout_user": "",
+    "request.ping": "",
+    "request.poll_messages": 
"0101040100000001040a00000001041400000001050000000164000000000000003200000001",
+    "request.poll_messages.group": 
"0201040300000001040100000001040100000000000000000300000000000000000a00000000",
+    "request.purge_stream": "010401000000",
+    "request.purge_topic": "010401000000010403000000",
+    "request.send_messages": 
"1600000001040100000001040200000002040000000003000000000000000000000000000000000000000000000000000000e803000000000000c501000000000000dcffef596f23a1cb03000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "request.store_consumer_offset": 
"0101040100000001040a0000000104140000000105000000393000000000000001",
+    "request.store_consumer_offset.no_partition": 
"020104030000000104010000000104010000000000000000ffffffffffffffff00",
+    "request.sync_consumer_group": 
"020873747265616d2d310207746f7069632d31020767726f75702d31",
+    "request.update_permissions": 
"0104010000000135000000010100010001000101000101000000010100010100010a000000010100010114000000000101010001020000000001010000010000",
+    "request.update_permissions.none": "020561646d696e00",
+    "request.update_stream": "02086f6c642d6e616d65086e65772d6e616d65",
+    "request.update_topic": 
"0104010000000104020000000d757064617465642d746f706963020e0000006d6573736167655f6578706972790c0800000000a0e3d08c000000",
+    "request.update_user": "01040100000001086e65772d6e616d650102",
+    "request.update_user.none": "0104050000000000",
+    "response.client": 
"010000000a000000010e0000003132372e302e302e313a3830383002000000",
+    "response.client_details": 
"010000000a000000010e0000003132372e302e302e313a3830383002000000010000000200000003000000040000000500000006000000",
+    "response.client_details.no_user": 
"02000000ffffffff010d00000031302e302e302e313a3630303000000000",
+    "response.cluster_metadata": 
"0c00000070726f642d636c757374657202000000060000006e6f64652d310800000031302e302e302e319a1f901fb80b9c1f0000060000006e6f64652d3207000000666430303a3a329b1f0000b90b00000103",
+    "response.consumer_group": "010000000400000002000000086d792d67726f7570",
+    "response.consumer_group_details": 
"010000000600000002000000086d792d67726f757001000000030000000000000001000000020000000200000003000000030000000400000005000000",
+    "response.consumer_offset": "01000000e803000000000000f401000000000000",
+    "response.describe_options": 
"020000000c7365676d656e745f73697a650c0800000000000040000000000c0000005365676d656e742073697a650a6475726162696c697479020a0000007265706c69636174656400000000",
+    "response.get_clients": 
"010000000a000000010e0000003132372e302e302e313a38303830020000000200000014000000020d00000031302e302e302e313a3630303003000000",
+    "response.get_consumer_groups": 
"0100000004000000020000000767726f75702d610200000004000000000000000767726f75702d62",
+    "response.get_personal_access_tokens": 
"07746f6b656e2d6100e090663c13060007746f6b656e2d620000000000000000",
+    "response.get_stream": 
"0100000000e090663c130600020000000008000000000000c800000000000000096d792d73747265616d000000000100000000e090663c1306000300000000a0e3d08c0000000100000040000000000010000000000000640000000000000007746f7069632d613b000000020a0000006475726162696c6974790209000000706572736973746564020c0000007365676d656e745f73697a650c08000000000000400000000020000000020e0000006d61785f746f7069635f73697a650c08000000ffffffffffffffff0200000000e090663c1306000300000000a0e3d08c000000010000004000
 [...]
+    "response.get_streams": 
"0100000000e090663c130600020000000008000000000000c800000000000000096d792d73747265616d000000000200000000e090663c130600000000000008000000000000c800000000000000096d792d73747265616d00000000",
+    "response.get_topic": 
"0100000000e090663c1306000300000000a0e3d08c00000001000000400000000000100000000000006400000000000000086d792d746f7069633b000000020a0000006475726162696c6974790209000000706572736973746564020c0000007365676d656e745f73697a650c08000000000000400000000020000000020e0000006d61785f746f7069635f73697a650c08000000ffffffffffffffff0000000000e090663c130600020000006300000000000000000400000000000064000000000000000100000001e090663c13060001000000000000000000000000000000000000000000000
 [...]
+    "response.get_topics": 
"020000000100000000e090663c1306000300000000a0e3d08c00000001000000400000000000100000000000006400000000000000066576656e74733b000000020a0000006475726162696c6974790209000000706572736973746564020c0000007365676d656e745f73697a650c08000000000000400000000020000000020e0000006d61785f746f7069635f73697a650c08000000ffffffffffffffff0200000000e090663c1306000300000000a0e3d08c00000001000000400000000000100000000000006400000000000000046c6f67733b000000020a0000006475726162696c697479
 [...]
+    "response.get_topics.empty": "00000000",
+    "response.get_users": 
"0100000000e090663c130600010561646d696e0000000002000000c8000000000000000205616c69636500000000",
+    "response.identity": "2a000000",
+    "response.login_register": 
"2a0000006400000000000000002c000006302e31312e30",
+    "response.poll_messages": 
"07000000660000000000000003000000070000000000000064000000000000008813000000000000e8030000000000006e01000000000000f576a2492e1328b3020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "response.poll_messages.empty": "00000000000000000000000000000000",
+    "response.raw_personal_access_token": 
"167261772d7365637265742d746f6b656e2d76616c7565",
+    "response.send_messages": 
"0200000001000000020000000300000004000000000000000100000002000000000000002a00000000000000",
+    "response.send_messages.empty": "00000000",
+    "response.stats": 
"d20400000000cc4100004842000000400000000000000000020000000000000001000000100e00000000000000e090663c13060040420f000000000020a107000000000080841e0000000000030000000a0000001e0000005a00000050c30000000000000500000002000000060000006e6f64652d31050000004c696e757803000000362e3105000000362e312e3006000000302e31312e30002c000001000000010000000100000000000000e80300000000000032000000000000003dcf733f1000000000000000190000000060253c77000000",
+    "response.stream": 
"0100000000e090663c130600020000000008000000000000c800000000000000096d792d73747265616d00000000",
+    "response.sync_consumer_group": 
"070000000000000003000000000000000200000004000000",
+    "response.topic_header": 
"0500000000e090663c1306000300000000a0e3d08c00000001000000400000000000100000000000006400000000000000066576656e74733b000000020a0000006475726162696c6974790209000000706572736973746564020c0000007365676d656e745f73697a650c08000000000000400000000020000000020e0000006d61785f746f7069635f73697a650c08000000ffffffffffffffff",
+    "response.user": "0100000000e090663c130600010561646d696e00000000",
+    "response.user_details": 
"0100000000e090663c130600010561646d696e000000000135000000010100010001000101000101000000010100010100010a000000010100010114000000000101010001020000000001010000010000",
+    "response.user_details.no_permissions": 
"0100000000e090663c130600010561646d696e0000000000000000",
+    "user_headers.sample": 
"0207000000617474656d70740b0400000003000000020800000074726163652d69640203000000616263",
+    "version_info": "002c00000973776966742d73646b05302e312e30",
+    "vsr.eviction.incompatible_protocol": 
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab8967452301002c0000002c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "vsr.eviction.stale_client": 
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab896745230100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "vsr.reply.denied": 
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000020000000000000029000000000000000000000000000000000
 [...]
+    "vsr.reply.ok": 
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0100000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab896745230100000000000000000000000000000000000000000000000007000000000000008000000000000000000000000000000000000000000000000000000
 [...]
+    "vsr.request.create_stream": 
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007010000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab89674523013a9e6b43adf27bf20000000000000000000000000000000001000000000000008000000000000000630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "vsr.request.logout": 
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab8967452301c294d3380580062d00000000000000000000000000000000040000000000000003000000000000006300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "vsr.request.ping": 
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab896745230100000000000000000000000000000000000000000000000001000000000000000200000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "vsr.request.ping.bound": 
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab896745230100000000000000000000000000000000000000000000000003000000000000000200000000000000630000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "vsr.request.register": 
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003010000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab89674523013b73ae32769bceeb000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "vsr.request.send_messages": 
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004010000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab89674523010000000000000000000000000000000000000000000000000200000000000000a000000000000000630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
+    "vsr.request.unknown_code": 
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab89674523010000000000000000000000000000000000000000000000000500000000000000020000000000000063000000000000000000000060ea000000000000000000000000000000000000000000000000000000000000000
 [...]
+  }
+}
diff --git a/foreign/swift/Tests/IggyTests/HashTests.swift 
b/foreign/swift/Tests/IggyTests/HashTests.swift
new file mode 100644
index 000000000..6fc254026
--- /dev/null
+++ b/foreign/swift/Tests/IggyTests/HashTests.swift
@@ -0,0 +1,62 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import Testing
+
+@testable import Iggy
+
+@Suite("XXH3-64")
+struct XXH3Tests {
+    @Test("matches the reference implementation for every golden length")
+    func matchesGoldenVectors() {
+        let fixture = GoldenFixture.shared
+        #expect(fixture.xxh3_64.count > 300)
+        for vector in fixture.xxh3_64 {
+            let input = GoldenFixture.pattern(vector.len)
+            #expect(UInt64(vector.hash, radix: 16) == XXH3.hash64(input), 
"length \(vector.len)")
+        }
+    }
+
+    @Test("hashes slices by their own bounds")
+    func hashesSlices() {
+        let input = GoldenFixture.pattern(600)
+        let slice = input[100..<400]
+        #expect(XXH3.hash64(slice) == XXH3.hash64(Array(slice)))
+    }
+
+    @Test("empty input has the well-known value")
+    func emptyInput() {
+        #expect(XXH3.hash64([]) == 0x2D06_8005_38D3_94C2)
+    }
+}
+
+@Suite("XXH32")
+struct XXH32Tests {
+    @Test("matches the reference implementation for every golden length")
+    func matchesGoldenVectors() {
+        let fixture = GoldenFixture.shared
+        #expect(fixture.xxh32.count > 300)
+        for vector in fixture.xxh32 {
+            #expect(UInt32(vector.hash, radix: 16) == 
XXH32.hash(GoldenFixture.pattern(vector.len)), "length \(vector.len)")
+        }
+    }
+
+    @Test("empty input has the well-known value")
+    func emptyInput() {
+        #expect(XXH32.hash([]) == 0x02CC_5D05)
+    }
+}
diff --git a/foreign/swift/Tests/IggyTests/Support/GoldenFixture.swift 
b/foreign/swift/Tests/IggyTests/Support/GoldenFixture.swift
new file mode 100644
index 000000000..a68de70eb
--- /dev/null
+++ b/foreign/swift/Tests/IggyTests/Support/GoldenFixture.swift
@@ -0,0 +1,73 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import Foundation
+import Testing
+
+/// Byte-exact fixtures produced by `Tools/golden-vectors` from the Rust
+/// protocol crates. Regenerate with:
+///
+/// ```text
+/// cargo run --manifest-path Tools/golden-vectors/Cargo.toml -- 
Tests/IggyTests/Fixtures/golden.json
+/// ```
+struct GoldenFixture: Decodable {
+    struct HashVector {
+        let len: Int
+        let hash: String
+    }
+
+    struct ErrorEntry {
+        let code: UInt32
+        let name: String
+    }
+
+    private let xxh3_64Table: [String: String]
+    private let xxh32Table: [String: String]
+    private let errorTable: [String: String]
+
+    /// The XXH3-64 vectors, by input length.
+    var xxh3_64: [HashVector] { Self.hashVectors(xxh3_64Table) }
+
+    /// The XXH32 vectors, by input length.
+    var xxh32: [HashVector] { Self.hashVectors(xxh32Table) }
+
+    /// Every error code the server defines, with its snake_case name.
+    var errors: [ErrorEntry] {
+        errorTable.map { ErrorEntry(code: UInt32($0.key)!, name: $0.value) 
}.sorted { $0.code < $1.code }
+    }
+
+    private static func hashVectors(_ table: [String: String]) -> [HashVector] 
{
+        table.map { HashVector(len: Int($0.key)!, hash: $0.value) }.sorted { 
$0.len < $1.len }
+    }
+
+    enum CodingKeys: String, CodingKey {
+        case xxh3_64Table = "xxh3_64"
+        case xxh32Table = "xxh32"
+        case errorTable = "errors"
+    }
+
+    static let shared: GoldenFixture = {
+        let url = Bundle.module.url(forResource: "golden", withExtension: 
"json", subdirectory: "Fixtures")!
+        let data = try! Data(contentsOf: url)
+        return try! JSONDecoder().decode(GoldenFixture.self, from: data)
+    }()
+
+    /// The deterministic input the hash vectors were computed over.
+    static func pattern(_ length: Int) -> [UInt8] {
+        (0..<length).map { UInt8(truncatingIfNeeded: $0) &* 31 &+ 7 }
+    }
+}
diff --git a/foreign/swift/Tools/golden-vectors/Cargo.toml 
b/foreign/swift/Tools/golden-vectors/Cargo.toml
new file mode 100644
index 000000000..a1a685e11
--- /dev/null
+++ b/foreign/swift/Tools/golden-vectors/Cargo.toml
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+[package]
+name = "iggy-swift-golden-vectors"
+version = "0.1.0"
+edition = "2024"
+publish = false
+description = "Dumps byte-exact wire fixtures from the Rust protocol crates 
for the Swift SDK tests"
+
+# Standalone: the root workspace must not pick this tool up.
+[workspace]
+
+[dependencies]
+bytemuck = "1"
+bytes = "1"
+iggy_binary_protocol = { path = "../../../../core/binary_protocol" }
+iggy_common = { path = "../../../../core/common" }
+secrecy = "0.10"
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+twox-hash = { version = "2", features = ["xxhash32", "xxhash3_64"] }
diff --git a/foreign/swift/Tools/golden-vectors/src/main.rs 
b/foreign/swift/Tools/golden-vectors/src/main.rs
new file mode 100644
index 000000000..b599ae9d4
--- /dev/null
+++ b/foreign/swift/Tools/golden-vectors/src/main.rs
@@ -0,0 +1,1435 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Dumps byte-exact wire fixtures from the Rust protocol crates.
+//!
+//! The Swift SDK re-implements the binary protocol from scratch, so every
+//! encoder and decoder it ships is checked against bytes produced by the
+//! reference implementation rather than against itself. Run from
+//! `foreign/swift`:
+//!
+//! ```text
+//! cargo run --manifest-path Tools/golden-vectors/Cargo.toml -- 
Tests/IggyTests/Fixtures/golden.json
+//! ```
+
+use bytes::{BufMut, Bytes, BytesMut};
+use iggy_binary_protocol::batch::{
+    BATCH_HEADER_SIZE, BATCH_MESSAGE_HEADER_SIZE, BatchHeader, 
calculate_batch_checksum,
+};
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::codes::*;
+use iggy_binary_protocol::consensus::{
+    Command, EvictionHeader, EvictionReason, HEADER_SIZE, Operation, 
ReplyHeader, RequestHeader,
+};
+use iggy_binary_protocol::primitives::options::WireOptions;
+use iggy_binary_protocol::primitives::permissions::{
+    WireGlobalPermissions, WirePermissions, WireStreamPermissions, 
WireTopicPermissions,
+};
+use iggy_binary_protocol::primitives::user_headers::encode_user_headers;
+use iggy_binary_protocol::requests::consumer_groups::*;
+use iggy_binary_protocol::requests::consumer_offsets::*;
+use iggy_binary_protocol::requests::messages::{
+    PollMessagesRequest, RawMessage, SendMessagesEncoder,
+};
+use iggy_binary_protocol::requests::partitions::{
+    CreatePartitionsRequest, DeletePartitionsRequest,
+};
+use iggy_binary_protocol::requests::personal_access_tokens::*;
+use iggy_binary_protocol::requests::segments::DeleteSegmentsRequest;
+use iggy_binary_protocol::requests::streams::*;
+use iggy_binary_protocol::requests::system::*;
+use iggy_binary_protocol::requests::topics::{
+    CreateTopicRequest, DeleteTopicRequest, GetTopicRequest, GetTopicsRequest, 
PurgeTopicRequest,
+    UpdateTopicRequest,
+};
+use iggy_binary_protocol::requests::users::*;
+use iggy_binary_protocol::responses::clients::{
+    ClientDetailsResponse, ClientResponse, ConsumerGroupInfoResponse, 
GetClientsResponse,
+};
+use iggy_binary_protocol::responses::consumer_groups::{
+    ConsumerGroupDetailsResponse, ConsumerGroupMemberResponse, 
ConsumerGroupResponse,
+    GetConsumerGroupsResponse, SyncConsumerGroupResponse,
+};
+use iggy_binary_protocol::responses::consumer_offsets::ConsumerOffsetResponse;
+use iggy_binary_protocol::responses::messages::{
+    PollMessagesResponseHeader, SendMessagesConfirmationResponse, 
SendMessagesResponse,
+};
+use iggy_binary_protocol::responses::personal_access_tokens::{
+    GetPersonalAccessTokensResponse, PersonalAccessTokenResponse, 
RawPersonalAccessTokenResponse,
+};
+use iggy_binary_protocol::responses::streams::{
+    GetStreamResponse, GetStreamsResponse, StreamResponse, TopicHeader,
+};
+use iggy_binary_protocol::responses::system::get_stats::{CacheMetricEntry, 
StatsResponse};
+use iggy_binary_protocol::responses::system::{
+    ClusterMetadataResponse, ClusterNodeResponse, DescribeOptionsResponse, 
OptionDescriptor,
+};
+use iggy_binary_protocol::responses::topics::{
+    GetTopicResponse, GetTopicsResponse, PartitionResponse,
+};
+use iggy_binary_protocol::responses::users::{
+    GetUsersResponse, IdentityResponse, LoginRegisterResponse, 
UserDetailsResponse, UserResponse,
+};
+use iggy_binary_protocol::{
+    AckLevel, ClientVersionInfo, IGGY_PROTOCOL_VERSION, 
IGGY_PROTOCOL_VERSION_MIN, WireConsumer,
+    WireIdentifier, WireName, WirePartitioning, WirePollingStrategy,
+};
+use iggy_common::{
+    CompressionAlgorithm, Durability, IggyByteSize, IggyDuration, IggyError, 
IggyExpiry,
+    MaxTopicSize, TopicCreateOptions, TopicUpdateOptions,
+};
+use secrecy::SecretString;
+use serde::Serialize;
+use std::collections::BTreeMap;
+use twox_hash::{XxHash3_64, XxHash32};
+
+/// Hash and error tables are maps keyed by length and code, so each entry is
+/// one line of the pretty-printed fixture and the file stays reviewable.
+#[derive(Serialize)]
+struct Golden {
+    protocol_version: u32,
+    protocol_version_min: u32,
+    xxh3_64: BTreeMap<usize, String>,
+    xxh32: BTreeMap<usize, String>,
+    errors: BTreeMap<u32, String>,
+    vectors: BTreeMap<String, String>,
+}
+
+fn pattern(len: usize) -> Vec<u8> {
+    (0..len)
+        .map(|i| (i as u8).wrapping_mul(31).wrapping_add(7))
+        .collect()
+}
+
+fn hex(bytes: &[u8]) -> String {
+    bytes.iter().map(|b| format!("{b:02x}")).collect()
+}
+
+fn numeric(id: u32) -> WireIdentifier {
+    WireIdentifier::numeric(id)
+}
+
+fn named(name: &str) -> WireIdentifier {
+    WireIdentifier::named(name).expect("valid name")
+}
+
+fn name(value: &str) -> WireName {
+    WireName::new(value).expect("valid name")
+}
+
+fn options(entries: &[(u8, &[u8], u8, &[u8])]) -> WireOptions {
+    let mut buf = BytesMut::new();
+    encode_user_headers(entries, &mut buf);
+    WireOptions::from_bytes(buf.freeze()).expect("valid options")
+}
+
+fn sample_options() -> WireOptions {
+    options(&[
+        (2, b"durability", 2, b"persisted"),
+        (2, b"segment_size", 12, &1_073_741_824u64.to_le_bytes()),
+    ])
+}
+
+fn version_info() -> ClientVersionInfo {
+    ClientVersionInfo {
+        protocol_version: IGGY_PROTOCOL_VERSION,
+        sdk_name: name("swift-sdk"),
+        sdk_version: name("0.1.0"),
+    }
+}
+
+fn sample_permissions() -> WirePermissions {
+    WirePermissions {
+        global: WireGlobalPermissions {
+            manage_servers: true,
+            read_servers: true,
+            manage_users: false,
+            read_users: true,
+            manage_streams: false,
+            read_streams: true,
+            manage_topics: false,
+            read_topics: true,
+            poll_messages: true,
+            send_messages: false,
+        },
+        streams: vec![
+            WireStreamPermissions {
+                stream_id: 1,
+                manage_stream: true,
+                read_stream: true,
+                manage_topics: false,
+                read_topics: true,
+                poll_messages: true,
+                send_messages: false,
+                topics: vec![
+                    WireTopicPermissions {
+                        topic_id: 10,
+                        manage_topic: true,
+                        read_topic: true,
+                        poll_messages: false,
+                        send_messages: true,
+                    },
+                    WireTopicPermissions {
+                        topic_id: 20,
+                        manage_topic: false,
+                        read_topic: true,
+                        poll_messages: true,
+                        send_messages: true,
+                    },
+                ],
+            },
+            WireStreamPermissions {
+                stream_id: 2,
+                manage_stream: false,
+                read_stream: true,
+                manage_topics: true,
+                read_topics: false,
+                poll_messages: false,
+                send_messages: true,
+                topics: vec![],
+            },
+        ],
+    }
+}
+
+fn frame(
+    id: u128,
+    offset_delta: u32,
+    timestamp_delta: u32,
+    payload: &[u8],
+    user_headers: &[u8],
+) -> Vec<u8> {
+    let mut bytes = vec![0u8; BATCH_MESSAGE_HEADER_SIZE];
+    bytes[8..24].copy_from_slice(&id.to_le_bytes());
+    bytes[24..28].copy_from_slice(&offset_delta.to_le_bytes());
+    bytes[28..32].copy_from_slice(&timestamp_delta.to_le_bytes());
+    bytes[32..36].copy_from_slice(&(user_headers.len() as u32).to_le_bytes());
+    bytes[36..40].copy_from_slice(&(payload.len() as u32).to_le_bytes());
+    bytes.extend_from_slice(payload);
+    bytes.extend_from_slice(user_headers);
+    let checksum = XxHash3_64::oneshot(&bytes[8..]);
+    bytes[0..8].copy_from_slice(&checksum.to_le_bytes());
+    bytes
+}
+
+fn batch_record(
+    partition_id: u64,
+    base_offset: u64,
+    base_timestamp: u64,
+    origin_timestamp: u64,
+    frames: &[Vec<u8>],
+) -> Vec<u8> {
+    let blob: Vec<u8> = frames.concat();
+    let mut header = BatchHeader::new(
+        partition_id,
+        origin_timestamp,
+        (BATCH_HEADER_SIZE + blob.len()) as u64,
+        frames.len() as u32,
+    );
+    header.base_offset = base_offset;
+    header.base_timestamp = base_timestamp;
+    header.batch_checksum = calculate_batch_checksum(&header, &blob);
+    let mut bytes = vec![0u8; BATCH_HEADER_SIZE];
+    header.encode_into(&mut bytes);
+    bytes.extend_from_slice(&blob);
+    bytes
+}
+
+fn topic_header(id: u32, name_value: &str) -> TopicHeader {
+    TopicHeader {
+        id,
+        created_at: 1_710_000_000_000_000,
+        partitions_count: 3,
+        message_expiry: 604_800_000_000,
+        compression_algorithm: 1,
+        max_topic_size: 1_073_741_824,
+        size_bytes: 4096,
+        messages_count: 100,
+        name: name(name_value),
+        options: sample_options(),
+        derived_options: options(&[(2, b"max_topic_size", 12, 
&u64::MAX.to_le_bytes())]),
+    }
+}
+
+fn stream_response(id: u32, topics_count: u32) -> StreamResponse {
+    StreamResponse {
+        id,
+        created_at: 1_710_000_000_000_000,
+        topics_count,
+        size_bytes: 2048,
+        messages_count: 200,
+        name: name("my-stream"),
+        options: WireOptions::empty(),
+    }
+}
+
+fn add<T: WireEncode>(vectors: &mut BTreeMap<String, String>, key: &str, 
value: &T) {
+    vectors.insert(key.to_owned(), hex(&value.to_bytes()));
+}
+
+/// The header a client sends, built by the same rules the Rust SDK uses.
+fn request_header(
+    code: u32,
+    client: u128,
+    request: u64,
+    session: u64,
+    payload: &[u8],
+) -> RequestHeader {
+    RequestHeader::for_request(code, client, request, session, payload)
+        .expect("fixture payloads fit a request frame")
+}
+
+fn main() {
+    let output = std::env::args().nth(1).expect("output path argument");
+    let mut vectors = BTreeMap::new();
+
+    let mut lengths: Vec<usize> = (0..=300).collect();
+    lengths.extend([
+        511, 512, 513, 1000, 1023, 1024, 1025, 1279, 1280, 1281, 2047, 2048, 
2049, 4096, 10_000,
+        65_536, 100_003,
+    ]);
+    let xxh3_64 = lengths
+        .iter()
+        .map(|&len| (len, format!("{:016x}", 
XxHash3_64::oneshot(&pattern(len)))))
+        .collect();
+    let xxh32 = lengths
+        .iter()
+        .map(|&len| (len, format!("{:08x}", XxHash32::oneshot(0, 
&pattern(len)))))
+        .collect();
+
+    let errors = (1u32..=20_000)
+        .filter_map(|code| {
+            let error = IggyError::from_code(code);
+            (error.as_code() == code).then(|| (code, 
error.as_string().to_owned()))
+        })
+        .collect();
+
+    // Primitives.
+    add(&mut vectors, "identifier.numeric.1", &numeric(1));
+    add(
+        &mut vectors,
+        "identifier.named.my-stream",
+        &named("my-stream"),
+    );
+    add(
+        &mut vectors,
+        "consumer.numeric.42",
+        &WireConsumer::consumer(numeric(42)),
+    );
+    add(
+        &mut vectors,
+        "consumer_group.named.my-group",
+        &WireConsumer::consumer_group(named("my-group")),
+    );
+    add(
+        &mut vectors,
+        "partitioning.balanced",
+        &WirePartitioning::Balanced,
+    );
+    add(
+        &mut vectors,
+        "partitioning.partition_id.7",
+        &WirePartitioning::PartitionId(7),
+    );
+    add(
+        &mut vectors,
+        "partitioning.messages_key.user-123",
+        &WirePartitioning::MessagesKey(b"user-123".to_vec()),
+    );
+    add(
+        &mut vectors,
+        "polling.offset.100",
+        &WirePollingStrategy::offset(100),
+    );
+    add(
+        &mut vectors,
+        "polling.timestamp.1700000000000",
+        &WirePollingStrategy::timestamp(1_700_000_000_000),
+    );
+    add(&mut vectors, "polling.first", &WirePollingStrategy::first());
+    add(&mut vectors, "polling.last", &WirePollingStrategy::last());
+    add(&mut vectors, "polling.next", &WirePollingStrategy::next());
+    add(&mut vectors, "options.golden", &sample_options());
+    add(&mut vectors, "permissions.sample", &sample_permissions());
+    add(&mut vectors, "version_info", &version_info());
+
+    // Requests.
+    add(
+        &mut vectors,
+        "request.create_stream",
+        &CreateStreamRequest {
+            name: name("test-stream"),
+            options: WireOptions::empty(),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.create_stream.options",
+        &CreateStreamRequest {
+            name: name("test-stream"),
+            options: sample_options(),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.delete_stream",
+        &DeleteStreamRequest {
+            stream_id: numeric(5),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.get_stream",
+        &GetStreamRequest {
+            stream_id: named("my-stream"),
+        },
+    );
+    add(&mut vectors, "request.get_streams", &GetStreamsRequest);
+    add(
+        &mut vectors,
+        "request.purge_stream",
+        &PurgeStreamRequest {
+            stream_id: numeric(1),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.update_stream",
+        &UpdateStreamRequest {
+            stream_id: named("old-name"),
+            name: name("new-name"),
+            options: WireOptions::empty(),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.create_topic",
+        &CreateTopicRequest {
+            stream_id: numeric(1),
+            partitions_count: 3,
+            name: name("orders"),
+            options: sample_options(),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.delete_topic",
+        &DeleteTopicRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(5),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.get_topic",
+        &GetTopicRequest {
+            stream_id: named("my-stream"),
+            topic_id: named("my-topic"),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.get_topics",
+        &GetTopicsRequest {
+            stream_id: numeric(42),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.purge_topic",
+        &PurgeTopicRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(3),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.update_topic",
+        &UpdateTopicRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(2),
+            name: name("updated-topic"),
+            options: options(&[(2, b"message_expiry", 12, 
&604_800_000_000u64.to_le_bytes())]),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.create_partitions",
+        &CreatePartitionsRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(2),
+            partitions_count: 5,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.delete_partitions",
+        &DeletePartitionsRequest {
+            stream_id: named("stream"),
+            topic_id: named("topic"),
+            partitions_count: 2,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.delete_segments",
+        &DeleteSegmentsRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(2),
+            partition_id: 3,
+            segments_count: 10,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.create_consumer_group",
+        &CreateConsumerGroupRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(2),
+            name: name("grp"),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.delete_consumer_group",
+        &DeleteConsumerGroupRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(2),
+            group_id: numeric(3),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.get_consumer_group",
+        &GetConsumerGroupRequest {
+            stream_id: named("stream-1"),
+            topic_id: named("topic-1"),
+            group_id: named("group-1"),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.get_consumer_groups",
+        &GetConsumerGroupsRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(2),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.join_consumer_group",
+        &JoinConsumerGroupRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(2),
+            group_id: numeric(3),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.leave_consumer_group",
+        &LeaveConsumerGroupRequest {
+            stream_id: numeric(1),
+            topic_id: numeric(2),
+            group_id: named("g"),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.sync_consumer_group",
+        &SyncConsumerGroupRequest {
+            stream_id: named("stream-1"),
+            topic_id: named("topic-1"),
+            group_id: named("group-1"),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.store_consumer_offset",
+        &StoreConsumerOffsetRequest {
+            consumer: WireConsumer::consumer(numeric(1)),
+            stream_id: numeric(10),
+            topic_id: numeric(20),
+            partition_id: Some(5),
+            offset: 12_345,
+            ack: AckLevel::Quorum,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.store_consumer_offset.no_partition",
+        &StoreConsumerOffsetRequest {
+            consumer: WireConsumer::consumer_group(numeric(3)),
+            stream_id: numeric(1),
+            topic_id: numeric(1),
+            partition_id: None,
+            offset: u64::MAX,
+            ack: AckLevel::NoAck,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.get_consumer_offset",
+        &GetConsumerOffsetRequest {
+            consumer: WireConsumer::consumer(named("my-consumer")),
+            stream_id: named("stream-1"),
+            topic_id: named("topic-1"),
+            partition_id: Some(0),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.delete_consumer_offset",
+        &DeleteConsumerOffsetRequest {
+            consumer: WireConsumer::consumer(numeric(1)),
+            stream_id: numeric(10),
+            topic_id: numeric(20),
+            partition_id: Some(5),
+            ack: AckLevel::Quorum,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.poll_messages",
+        &PollMessagesRequest {
+            consumer: WireConsumer::consumer(numeric(1)),
+            stream_id: numeric(10),
+            topic_id: numeric(20),
+            partition_id: Some(5),
+            strategy: WirePollingStrategy::offset(100),
+            count: 50,
+            auto_commit: true,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.poll_messages.group",
+        &PollMessagesRequest {
+            consumer: WireConsumer::consumer_group(numeric(3)),
+            stream_id: numeric(1),
+            topic_id: numeric(1),
+            partition_id: None,
+            strategy: WirePollingStrategy::first(),
+            count: 10,
+            auto_commit: false,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.create_user",
+        &CreateUserRequest {
+            username: name("admin"),
+            password: "p@ssw0rd".to_owned(),
+            status: 1,
+            permissions: Some(sample_permissions()),
+            options: WireOptions::empty(),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.create_user.no_permissions",
+        &CreateUserRequest {
+            username: name("user"),
+            password: "secret123".to_owned(),
+            status: 2,
+            permissions: None,
+            options: WireOptions::empty(),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.delete_user",
+        &DeleteUserRequest {
+            user_id: named("old-user"),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.get_user",
+        &GetUserRequest {
+            user_id: numeric(42),
+        },
+    );
+    add(&mut vectors, "request.get_users", &GetUsersRequest);
+    add(
+        &mut vectors,
+        "request.update_user",
+        &UpdateUserRequest {
+            user_id: numeric(1),
+            username: Some(name("new-name")),
+            status: Some(2),
+            options: WireOptions::empty(),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.update_user.none",
+        &UpdateUserRequest {
+            user_id: numeric(5),
+            username: None,
+            status: None,
+            options: WireOptions::empty(),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.update_permissions",
+        &UpdatePermissionsRequest {
+            user_id: numeric(1),
+            permissions: Some(sample_permissions()),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.update_permissions.none",
+        &UpdatePermissionsRequest {
+            user_id: named("admin"),
+            permissions: None,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.change_password",
+        &ChangePasswordRequest {
+            user_id: numeric(1),
+            current_password: "old-pass".to_owned(),
+            new_password: "new-pass-123".to_owned(),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.login_register",
+        &LoginRegisterRequest {
+            version_info: version_info(),
+            username: name("iggy"),
+            password: SecretString::from("iggy"),
+            client_context: None,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.login_register_with_pat",
+        &LoginRegisterWithPatRequest {
+            version_info: version_info(),
+            token: SecretString::from("pat-abc123def456"),
+            client_context: None,
+        },
+    );
+    add(&mut vectors, "request.logout_user", &LogoutUserRequest);
+    add(
+        &mut vectors,
+        "request.create_personal_access_token",
+        &CreatePersonalAccessTokenRequest {
+            name: name("my-token"),
+            expiry: 3600,
+        },
+    );
+    add(
+        &mut vectors,
+        "request.delete_personal_access_token",
+        &DeletePersonalAccessTokenRequest {
+            name: name("my-token"),
+        },
+    );
+    add(
+        &mut vectors,
+        "request.get_personal_access_tokens",
+        &GetPersonalAccessTokensRequest,
+    );
+    add(&mut vectors, "request.ping", &PingRequest);
+    add(&mut vectors, "request.get_stats", &GetStatsRequest);
+    add(&mut vectors, "request.get_me", &GetMeRequest);
+    add(
+        &mut vectors,
+        "request.get_client",
+        &GetClientRequest { client_id: 42 },
+    );
+    add(&mut vectors, "request.get_clients", &GetClientsRequest);
+    add(
+        &mut vectors,
+        "request.get_cluster_metadata",
+        &GetClusterMetadataRequest,
+    );
+    add(
+        &mut vectors,
+        "request.get_snapshot",
+        &GetSnapshotRequest {
+            compression: 2,
+            snapshot_types: vec![1, 5],
+        },
+    );
+    add(
+        &mut vectors,
+        "request.describe_options",
+        &DescribeOptionsRequest { scope: 1 },
+    );
+
+    // Send messages: the full body a producer writes.
+    {
+        let stream_id = numeric(1);
+        let topic_id = numeric(2);
+        let partitioning = WirePartitioning::PartitionId(0);
+        let mut headers = BytesMut::new();
+        // Key order matters for the bytes: the reference SDK keeps headers in 
a
+        // `BTreeMap`, so entries are written sorted by key.
+        encode_user_headers(
+            &[
+                (2, b"attempt", 11, &3u32.to_le_bytes()),
+                (2, b"trace-id", 2, b"abc"),
+            ],
+            &mut headers,
+        );
+        let headers = headers.freeze();
+        let messages = [
+            RawMessage {
+                id: 1,
+                origin_timestamp: 1_000,
+                headers: None,
+                payload: b"first",
+            },
+            RawMessage {
+                id: 2,
+                origin_timestamp: 1_500,
+                headers: Some(&headers),
+                payload: b"second",
+            },
+            RawMessage {
+                id: 0xDEAD_BEEF_0000_0000_0000_0000_0000_0003,
+                origin_timestamp: 1_000,
+                headers: None,
+                payload: b"",
+            },
+        ];
+        let mut buf = BytesMut::new();
+        SendMessagesEncoder::encode(&mut buf, &stream_id, &topic_id, 
&partitioning, &messages)
+            .expect("encodes");
+        vectors.insert("request.send_messages".to_owned(), hex(&buf));
+        vectors.insert("user_headers.sample".to_owned(), hex(&headers));
+    }
+
+    // Consensus headers.
+    let create_stream_payload = CreateStreamRequest {
+        name: name("stream"),
+        options: WireOptions::empty(),
+    }
+    .to_bytes();
+    vectors.insert(
+        "payload.create_stream".to_owned(),
+        hex(&create_stream_payload),
+    );
+    let client_id: u128 = 0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210;
+    vectors.insert(
+        "vsr.request.register".to_owned(),
+        hex(bytemuck::bytes_of(&request_header(
+            LOGIN_REGISTER_CODE,
+            client_id,
+            0,
+            0,
+            &[1, 2, 3],
+        ))),
+    );
+    vectors.insert(
+        "vsr.request.ping".to_owned(),
+        hex(bytemuck::bytes_of(&request_header(
+            PING_CODE,
+            client_id,
+            1,
+            0,
+            &[],
+        ))),
+    );
+    vectors.insert(
+        "vsr.request.ping.bound".to_owned(),
+        hex(bytemuck::bytes_of(&request_header(
+            PING_CODE,
+            client_id,
+            3,
+            99,
+            &[],
+        ))),
+    );
+    vectors.insert(
+        "vsr.request.create_stream".to_owned(),
+        hex(bytemuck::bytes_of(&request_header(
+            CREATE_STREAM_CODE,
+            client_id,
+            1,
+            99,
+            &create_stream_payload,
+        ))),
+    );
+    vectors.insert(
+        "vsr.request.send_messages".to_owned(),
+        hex(bytemuck::bytes_of(&request_header(
+            SEND_MESSAGES_CODE,
+            client_id,
+            2,
+            99,
+            &[9, 9, 9, 9],
+        ))),
+    );
+    vectors.insert(
+        "vsr.request.logout".to_owned(),
+        hex(bytemuck::bytes_of(&request_header(
+            LOGOUT_USER_CODE,
+            client_id,
+            4,
+            99,
+            &[],
+        ))),
+    );
+    vectors.insert(
+        "vsr.request.unknown_code".to_owned(),
+        hex(bytemuck::bytes_of(&request_header(
+            60_000,
+            client_id,
+            5,
+            99,
+            &[],
+        ))),
+    );
+
+    let reply = ReplyHeader {
+        command: Command::Reply,
+        operation: Operation::CreateStream,
+        size: (HEADER_SIZE + 12) as u32,
+        request: 7,
+        status: 0,
+        client: client_id,
+        ..Default::default()
+    };
+    vectors.insert("vsr.reply.ok".to_owned(), hex(bytemuck::bytes_of(&reply)));
+    let denied = ReplyHeader {
+        command: Command::Reply,
+        operation: Operation::NonReplicated,
+        size: HEADER_SIZE as u32,
+        request: 8,
+        status: IggyError::Unauthorized.as_code(),
+        ..Default::default()
+    };
+    vectors.insert(
+        "vsr.reply.denied".to_owned(),
+        hex(bytemuck::bytes_of(&denied)),
+    );
+    let eviction = EvictionHeader::new(0, 0, 0, client_id, 
EvictionReason::StaleClient);
+    vectors.insert(
+        "vsr.eviction.stale_client".to_owned(),
+        hex(bytemuck::bytes_of(&eviction)),
+    );
+    let incompatible = EvictionHeader::incompatible_protocol(
+        0,
+        0,
+        0,
+        client_id,
+        IGGY_PROTOCOL_VERSION,
+        IGGY_PROTOCOL_VERSION_MIN,
+    );
+    vectors.insert(
+        "vsr.eviction.incompatible_protocol".to_owned(),
+        hex(bytemuck::bytes_of(&incompatible)),
+    );
+
+    // Responses.
+    add(&mut vectors, "response.stream", &stream_response(1, 2));
+    add(
+        &mut vectors,
+        "response.get_stream",
+        &GetStreamResponse {
+            stream: stream_response(1, 2),
+            topics: vec![topic_header(1, "topic-a"), topic_header(2, 
"topic-b")],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.get_streams",
+        &GetStreamsResponse {
+            streams: vec![stream_response(1, 2), stream_response(2, 0)],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.topic_header",
+        &topic_header(5, "events"),
+    );
+    let partitions = vec![
+        PartitionResponse {
+            id: 0,
+            created_at: 1_710_000_000_000_000,
+            segments_count: 2,
+            current_offset: 99,
+            size_bytes: 1024,
+            messages_count: 100,
+        },
+        PartitionResponse {
+            id: 1,
+            created_at: 1_710_000_000_000_001,
+            segments_count: 1,
+            current_offset: 0,
+            size_bytes: 0,
+            messages_count: 0,
+        },
+        PartitionResponse {
+            id: 2,
+            created_at: 1_710_000_000_000_002,
+            segments_count: 3,
+            current_offset: 7,
+            size_bytes: 512,
+            messages_count: 8,
+        },
+    ];
+    add(
+        &mut vectors,
+        "response.get_topic",
+        &GetTopicResponse {
+            topic: topic_header(1, "my-topic"),
+            partitions,
+        },
+    );
+    add(
+        &mut vectors,
+        "response.get_topics",
+        &GetTopicsResponse {
+            topics: vec![topic_header(1, "events"), topic_header(2, "logs")],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.get_topics.empty",
+        &GetTopicsResponse { topics: vec![] },
+    );
+    add(
+        &mut vectors,
+        "response.consumer_group",
+        &ConsumerGroupResponse {
+            id: 1,
+            partitions_count: 4,
+            members_count: 2,
+            name: name("my-group"),
+        },
+    );
+    add(
+        &mut vectors,
+        "response.consumer_group_details",
+        &ConsumerGroupDetailsResponse {
+            group: ConsumerGroupResponse {
+                id: 1,
+                partitions_count: 6,
+                members_count: 2,
+                name: name("my-group"),
+            },
+            members: vec![
+                ConsumerGroupMemberResponse {
+                    id: 1,
+                    partitions_count: 3,
+                    partitions: vec![0, 1, 2],
+                },
+                ConsumerGroupMemberResponse {
+                    id: 2,
+                    partitions_count: 3,
+                    partitions: vec![3, 4, 5],
+                },
+            ],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.get_consumer_groups",
+        &GetConsumerGroupsResponse {
+            groups: vec![
+                ConsumerGroupResponse {
+                    id: 1,
+                    partitions_count: 4,
+                    members_count: 2,
+                    name: name("group-a"),
+                },
+                ConsumerGroupResponse {
+                    id: 2,
+                    partitions_count: 4,
+                    members_count: 0,
+                    name: name("group-b"),
+                },
+            ],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.sync_consumer_group",
+        &SyncConsumerGroupResponse {
+            generation: 7,
+            partitions: vec![0, 2, 4],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.consumer_offset",
+        &ConsumerOffsetResponse {
+            partition_id: 1,
+            current_offset: 1000,
+            stored_offset: 500,
+        },
+    );
+    let client = ClientResponse {
+        client_id: 1,
+        user_id: 10,
+        transport: 1,
+        address: "127.0.0.1:8080".to_owned(),
+        consumer_groups_count: 2,
+    };
+    add(&mut vectors, "response.client", &client);
+    add(
+        &mut vectors,
+        "response.client_details",
+        &ClientDetailsResponse {
+            client: client.clone(),
+            consumer_groups: vec![
+                ConsumerGroupInfoResponse {
+                    stream_id: 1,
+                    topic_id: 2,
+                    group_id: 3,
+                },
+                ConsumerGroupInfoResponse {
+                    stream_id: 4,
+                    topic_id: 5,
+                    group_id: 6,
+                },
+            ],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.client_details.no_user",
+        &ClientDetailsResponse {
+            client: ClientResponse {
+                client_id: 2,
+                user_id: u32::MAX,
+                transport: 1,
+                address: "10.0.0.1:6000".to_owned(),
+                consumer_groups_count: 0,
+            },
+            consumer_groups: vec![],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.get_clients",
+        &GetClientsResponse {
+            clients: vec![
+                client,
+                ClientResponse {
+                    client_id: 2,
+                    user_id: 20,
+                    transport: 2,
+                    address: "10.0.0.1:6000".to_owned(),
+                    consumer_groups_count: 3,
+                },
+            ],
+        },
+    );
+    let user = UserResponse {
+        id: 1,
+        created_at: 1_710_000_000_000_000,
+        status: 1,
+        username: name("admin"),
+        options: WireOptions::empty(),
+    };
+    add(&mut vectors, "response.user", &user);
+    add(
+        &mut vectors,
+        "response.user_details",
+        &UserDetailsResponse {
+            user: user.clone(),
+            permissions: Some(sample_permissions()),
+        },
+    );
+    add(
+        &mut vectors,
+        "response.user_details.no_permissions",
+        &UserDetailsResponse {
+            user: user.clone(),
+            permissions: None,
+        },
+    );
+    add(
+        &mut vectors,
+        "response.get_users",
+        &GetUsersResponse {
+            users: vec![
+                user,
+                UserResponse {
+                    id: 2,
+                    created_at: 200,
+                    status: 2,
+                    username: name("alice"),
+                    options: WireOptions::empty(),
+                },
+            ],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.login_register",
+        &LoginRegisterResponse {
+            user_id: 42,
+            session: 100,
+            server_protocol_version: IGGY_PROTOCOL_VERSION,
+            server_version: name("0.11.0"),
+        },
+    );
+    add(
+        &mut vectors,
+        "response.identity",
+        &IdentityResponse { user_id: 42 },
+    );
+    add(
+        &mut vectors,
+        "response.raw_personal_access_token",
+        &RawPersonalAccessTokenResponse {
+            token: name("raw-secret-token-value"),
+        },
+    );
+    add(
+        &mut vectors,
+        "response.get_personal_access_tokens",
+        &GetPersonalAccessTokensResponse {
+            tokens: vec![
+                PersonalAccessTokenResponse {
+                    name: name("token-a"),
+                    expiry_at: 1_710_000_000_000_000,
+                },
+                PersonalAccessTokenResponse {
+                    name: name("token-b"),
+                    expiry_at: 0,
+                },
+            ],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.stats",
+        &StatsResponse {
+            process_id: 1234,
+            cpu_usage: 25.5,
+            total_cpu_usage: 50.0,
+            memory_usage: 1_073_741_824,
+            total_memory: 8_589_934_592,
+            available_memory: 4_294_967_296,
+            run_time: 3600,
+            start_time: 1_710_000_000_000_000,
+            read_bytes: 1_000_000,
+            written_bytes: 500_000,
+            messages_size_bytes: 2_000_000,
+            streams_count: 3,
+            topics_count: 10,
+            partitions_count: 30,
+            segments_count: 90,
+            messages_count: 50_000,
+            clients_count: 5,
+            consumer_groups_count: 2,
+            hostname: "node-1".to_owned(),
+            os_name: "Linux".to_owned(),
+            os_version: "6.1".to_owned(),
+            kernel_version: "6.1.0".to_owned(),
+            iggy_server_version: "0.11.0".to_owned(),
+            iggy_server_semver: Some(11_264),
+            cache_metrics: vec![CacheMetricEntry {
+                stream_id: 1,
+                topic_id: 1,
+                partition_id: 0,
+                hits: 1000,
+                misses: 50,
+                hit_ratio: 0.952_380_95,
+            }],
+            threads_count: 16,
+            free_disk_space: 107_374_182_400,
+            total_disk_space: 512_110_190_592,
+        },
+    );
+    add(
+        &mut vectors,
+        "response.cluster_metadata",
+        &ClusterMetadataResponse {
+            name: "prod-cluster".to_owned(),
+            nodes: vec![
+                ClusterNodeResponse {
+                    name: "node-1".to_owned(),
+                    ip: "10.0.0.1".to_owned(),
+                    tcp_port: 8090,
+                    quic_port: 8080,
+                    http_port: 3000,
+                    websocket_port: 8092,
+                    role: 0,
+                    status: 0,
+                },
+                ClusterNodeResponse {
+                    name: "node-2".to_owned(),
+                    ip: "fd00::2".to_owned(),
+                    tcp_port: 8091,
+                    quic_port: 0,
+                    http_port: 3001,
+                    websocket_port: 0,
+                    role: 1,
+                    status: 3,
+                },
+            ],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.describe_options",
+        &DescribeOptionsResponse {
+            entries: vec![
+                OptionDescriptor {
+                    key: name("segment_size"),
+                    kind: 12,
+                    default_value: 
Bytes::copy_from_slice(&1_073_741_824u64.to_le_bytes()),
+                    description: "Segment size".to_owned(),
+                },
+                OptionDescriptor {
+                    key: name("durability"),
+                    kind: 2,
+                    default_value: Bytes::from_static(b"replicated"),
+                    description: String::new(),
+                },
+            ],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.send_messages",
+        &SendMessagesResponse {
+            confirmations: vec![
+                SendMessagesConfirmationResponse {
+                    stream_id: 1,
+                    topic_id: 2,
+                    partition_id: 3,
+                    base_offset: 4,
+                },
+                SendMessagesConfirmationResponse {
+                    stream_id: 1,
+                    topic_id: 2,
+                    partition_id: 0,
+                    base_offset: 42,
+                },
+            ],
+        },
+    );
+    add(
+        &mut vectors,
+        "response.send_messages.empty",
+        &SendMessagesResponse {
+            confirmations: vec![],
+        },
+    );
+
+    // Poll response: header + two batch records.
+    {
+        let first = batch_record(
+            7,
+            100,
+            5_000,
+            1_000,
+            &[
+                frame(11, 0, 0, b"a", &[]),
+                frame(12, 1, 10, b"b", &vectors_user_headers()),
+            ],
+        );
+        let second = batch_record(7, 102, 6_000, 2_000, &[frame(13, 0, 0, 
b"c", &[])]);
+        let mut body = BytesMut::new();
+        PollMessagesResponseHeader {
+            partition_id: 7,
+            current_offset: 102,
+            messages_count: 3,
+        }
+        .encode(&mut body);
+        body.put_slice(&first);
+        body.put_slice(&second);
+        vectors.insert("response.poll_messages".to_owned(), hex(&body));
+        let mut empty = BytesMut::new();
+        PollMessagesResponseHeader {
+            partition_id: 0,
+            current_offset: 0,
+            messages_count: 0,
+        }
+        .encode(&mut empty);
+        vectors.insert("response.poll_messages.empty".to_owned(), hex(&empty));
+    }
+
+    // Domain option blocks.
+    let create_options = TopicCreateOptions {
+        partitions_count: Some(3),
+        compression_algorithm: Some(CompressionAlgorithm::Gzip),
+        message_expiry: 
Some(IggyExpiry::ExpireDuration(IggyDuration::new_from_secs(
+            604_800,
+        ))),
+        max_topic_size: 
Some(MaxTopicSize::Custom(IggyByteSize::from(1_073_741_824))),
+        segment_size: Some(IggyByteSize::from(1_048_576)),
+        durability: Durability::Persisted,
+        consumer_offset_durability: Durability::Replicated,
+        messages_required_to_save: Some(7),
+        size_of_messages_required_to_save: Some(IggyByteSize::from(4096)),
+        preallocate_segments: Some(false),
+        raw: BTreeMap::new(),
+    };
+    add(
+        &mut vectors,
+        "options.topic_create.full",
+        &create_options.to_wire().expect("encodes"),
+    );
+    let update_options = TopicUpdateOptions {
+        compression_algorithm: Some(CompressionAlgorithm::None),
+        message_expiry: Some(IggyExpiry::NeverExpire),
+        max_topic_size: Some(MaxTopicSize::Unlimited),
+        raw: BTreeMap::new(),
+    };
+    add(
+        &mut vectors,
+        "options.topic_update.full",
+        &update_options.to_wire().expect("encodes"),
+    );
+    let mut raw = BTreeMap::new();
+    raw.insert("durability".to_owned(), "persisted".to_owned());
+    let raw_options = TopicCreateOptions {
+        raw,
+        ..Default::default()
+    };
+    add(
+        &mut vectors,
+        "options.topic_create.raw",
+        &raw_options.to_wire().expect("encodes"),
+    );
+
+    let golden = Golden {
+        protocol_version: IGGY_PROTOCOL_VERSION,
+        protocol_version_min: IGGY_PROTOCOL_VERSION_MIN,
+        xxh3_64,
+        xxh32,
+        errors,
+        vectors,
+    };
+    let mut json = serde_json::to_string_pretty(&golden).expect("serializes");
+    json.push('\n');
+    if let Some(parent) = std::path::Path::new(&output).parent() {
+        std::fs::create_dir_all(parent).expect("creates the output directory");
+    }
+    std::fs::write(&output, json).expect("writes output");
+    println!("wrote {output}");
+}
+
+fn vectors_user_headers() -> Vec<u8> {
+    let mut headers = BytesMut::new();
+    encode_user_headers(&[(2, b"k", 2, b"v")], &mut headers);
+    headers.to_vec()
+}

Reply via email to