Cosmos: Implement binary round trip fuzzer - #4872
Closed
Debdatta Kunda (kundadebdatta) wants to merge 118 commits into
Closed
Cosmos: Implement binary round trip fuzzer#4872Debdatta Kunda (kundadebdatta) wants to merge 118 commits into
Debdatta Kunda (kundadebdatta) wants to merge 118 commits into
Conversation
added 30 commits
June 22, 2026 12:19
First phase of Cosmos binary JSON encoding support (see docs/BINARY_ENCODING_SPEC.md). Pure additive scaffolding with no behavior change - nothing is wired into the request/response path yet. Adds a schema-agnostic binary_json module: markers.rs (the full type-marker byte constant set transcribed from the .NET JsonBinaryEncoding.TypeMarker.cs reference, with range-contiguity and byte-value tests); error.rs (the BinaryError vocabulary the decoder will return - EOF, invalid marker, bad length, invalid UTF-8, unresolved reference, depth limit, missing preamble, trailing bytes); and mod.rs (the 0x80 PREAMBLE constant plus is_binary first-byte auto-detection). The system-string dictionary (P0b) and the decoder/encoder (P1/P2) follow. build/clippy/fmt clean; 9 unit tests + 1 doctest pass; the existing 1931 lib tests are unaffected.
Adds the fixed system-string dictionary the binary JSON decoder uses to resolve 1-byte system-string markers (marker - SYSTEM_STRING_1BYTE_MIN indexes the table). Transcribed verbatim from the .NET JsonBinaryEncoding.SystemStrings.cs SystemStrings.Strings array.
The authoritative table is exactly 32 entries (the spec estimated ~128); there is no 2-byte system-string marker range today. Each entry order is significant and matches the service. Cross-checked every entry against the byte-length buckets the .NET GetSystemStringIdLength{N} reverse-lookup functions sort them into, plus spot checks at well-known indices (id=12, _rid=5, _etag=4, _id=31), uniqueness, and marker-range mapping.
Exposes SYSTEM_STRINGS, SYSTEM_STRING_COUNT, system_string(index), and system_string_for_marker(marker). Still no behavior change - nothing wired into the request/response path. build/clippy/fmt/cspell clean; 15 binary_json tests (6 new) pass.
Adds a small test-only golden-vector corpus: minimal-valid Cosmos binary JSON buffers paired with the text JSON they encode (null/false/true, literal-int min/max, a system-string lookup, and an encoded-length string). The buffers are hand-encoded from the P0a marker constants so they are well-defined byte-for-byte. At P0 the corpus drives a trivial structural decoder built only from the foundation (preamble + marker ranges + system-string table), proving those primitives are wired correctly and already dispatch every scalar form. The same vectors become the P1 decoder decode-parity bar and the P2 encoder reproduction target. The module is cfg(test) only. No behavior change. build/clippy/fmt/cspell clean; 18 binary_json tests (3 new) pass.
Adds reader.rs implementing the public decode() entry point and a bounds-checked Reader that decodes scalar binary-JSON values into serde_json::Value: null, bool, literal ints, fixed-width numbers (UInt8/Int16/Int32/Int64/UInt64/Double, little-endian), system strings, encoded-length strings, and StrL1/L2/L4 strings. Adds the InvalidNumber error variant for non-finite doubles. Containers, user/reference strings, and exotic forms return InvalidMarker for now (P1b-P1d). Wires the reader module and re-exports decode from mod.rs.
Extends the decoder to arrays (Arr0/Arr1/ArrL1-4/ArrLC1-4, 0xE0-0xE7) and objects (Obj0/Obj1/ObjL1-4/ObjLC1-4, 0xE8-0xEF). Length prefixes bound each payload to a sub-region; elements are parsed until the region is exhausted, with declared item/member counts validated against what was actually decoded. Object names must decode to strings (non-strings report InvalidMarker at the name position). Adds a MAX_DEPTH=256 nesting guard mirroring .NET JsonObjectState.JsonMaxNestingDepth to prevent stack exhaustion. User/reference strings and exotic forms still return InvalidMarker (P1c-P1d). Also drops two redundant explicit doc-link targets in system_strings.rs so cargo doc is warning-free.
Reference strings (StrR1-StrR4, 0xC3-0xC6) are now resolved: each carries a 1/2/3/4-byte little-endian absolute byte offset (same frame as the cursor, preamble = offset 0) pointing back to an earlier string. The target is validated to lie within the buffer and to hold a string that is not itself a reference, mirroring .NET IsValidReferenceStringTarget; this makes reference chains and cycles impossible, so resolution needs no recursion guard. Invalid targets return UnresolvedReference. User strings (0x40-0x67) are recognized and their dictionary id decoded (1-byte and 2-byte forms per .NET TryGetUserStringId), but report the new UnsupportedUserString error because the data plane does not supply the external string dictionary they reference. Adds a read_u24_le primitive for the StrR3 offset width. Exotic string/number forms still return InvalidMarker (P1d).
Decodes the extended fixed-width number markers Int8 (0xD8), Int16 (0xD9), Int32 (0xDA), Int64 (0xDB), UInt32 (0xDC), Float32 (0xCD), and Float64 (0xCE). Each carries its little-endian value immediately after the marker (no length prefix), per .NET TryGetFixedWidthValue. Float values reuse the non-finite rejection from the NumberDouble path. Float16 (0xCF) and the extended UInt8 (0xD7) have no JSON node type in the service (NodeTypes maps them to Unknown), so they remain InvalidMarker. Adds read_i8 and read_f32_le primitives. Compact strings (base64/GUID/compressed), binary blobs, and uniform number arrays are still deferred (P1d-2..P1d-4).
Decodes the GUID string markers LowercaseGuidString (0x75), UppercaseGuidString (0x76), and DoubleQuotedLowercaseGuidString (0x77). Each carries a 16-byte encoded form (following the marker) that expands to the canonical 8-4-4-4-12 hex text via a straight sequential hex dump (not the .NET Guid mixed-endian layout), mirroring .NET DecodeGuidStringValue. The uppercase variant differs only in hex case; the double-quoted variant re-adds the literal quote characters the original JSON string carried. Adds the read_guid_string helper. Base64 strings, compressed strings, the GUID value (0xD3), binary blobs, and uniform number arrays remain deferred (P1d-3..P1d-5).
Decodes the base64 string markers Base64StringLength1/2 (0x71/0x72) and Base64UrlStringLength1/2 (0x73/0x74). The inline payload is the raw (already base64-decoded) bytes; decoding re-encodes them to the original text using the standard or URL-safe alphabet. The group-count prefix (1 or 2 byte LE) times four is the padded length; the padding byte gives the literal '=' count (0..=2) or, when greater than 2, signals omitted padding via its bitwise complement, shrinking the final text length. Raw byte count and final length follow .NET GetBase64ByteCount/ComputeBase64StringLength. Adds the read_base64_string helper (uses the already-vendored base64 crate). Compressed strings, the GUID value, binary blobs, and uniform number arrays remain deferred (P1d-4, P1d-5).
Decodes all compressed string forms (0x78-0x7F). The 4-bit table forms (CompressedLowercaseHexString 0x78, CompressedUppercaseHexString 0x79, CompressedDateTimeString 0x7A) map each nibble (low-then-high per byte) through a 16-entry character table, transcribed from .NET StringCompressionLookupTables. The packed forms unpack little-endian N-bit values: Packed4/5/6BitString (0x7B-0x7D) read a 1-byte base character added to every value; Packed7BitStringLength1/2 (0x7E/0x7F) use a 1- or 2-byte length and no base. Lengths are decoded character counts; payload size is ceil(len*bits/8). All decode to ASCII text. Adds read_table_string, read_packed_string, and a private compression submodule with the hex/datetime tables. The GUID value, binary blobs, and uniform number arrays remain deferred (P1d-5).
Decodes the GUID value (0xD3) and binary blobs (Binary1/2/4ByteLength, 0xDD-0xDF). The GUID value is 16 bytes interpreted as a .NET Guid (mixed-endian: the first three groups are little-endian, the final eight bytes sequential) and rendered as canonical lowercase text; this differs from the GUID string forms, which dump the bytes sequentially. Since JSON has no GUID type it maps to a string. Binary blobs carry a 1/2/4-byte little-endian length and raw bytes, mapped to a standard base64 string (the conventional JSON byte encoding). Adds read_guid_value and read_binary. Uniform number arrays remain deferred (P1d-5b).
Decodes the uniform number array forms: ArrNumC1/ArrNumC2 (0xF0/0xF1) are a shared-type number array with a 1- or 2-byte item count, and ArrArrNumC1C1/ArrArrNumC2C2 (0xF2/0xF3) are an array of such arrays. Inside a uniform array the item-type marker is shared, so each element is a bare little-endian number (Int8/UInt8/Int16/Int32/Int64/UInt32/Float32/Float64) with no per-item marker; a non-number item type reports InvalidMarker. Adds read_bare_number, read_uniform_number_array, and read_uniform_array_of_number_arrays. This completes the binary JSON decoder: every value form the service can emit now decodes into a serde_json::Value. Updates the module status docs accordingly.
Wires the binary JSON decoder into the response path. ResponseBody::into_single and into_items now route each buffer through a shared deserialize_response helper that inspects the first byte: a 0x80 preamble (binary_json::is_binary) is decoded via binary_json::decode into a serde_json::Value and then deserialized into T; any other buffer is parsed directly as text JSON exactly as before. Because no UTF-8 text JSON can begin with 0x80 (a continuation byte), detection is unambiguous and the text path is byte-for-byte unchanged. This single choke point covers reads, write responses, and query (the SDK decodes the whole Documents envelope via into_single as a typed feed body), so all three response shapes become binary-aware at once. Adds tests for binary point reads, both into_items variants, a binary feed envelope (the query path), unchanged text decoding, and malformed-binary errors. The branch stays inert until the service negotiates binary responses (request-side header and encoder land in a later phase).
Adds writer.rs implementing the public encode() entry point: serde_json::Value to a complete binary buffer (preamble prefixed). The encoder is minimal-but-valid, using the smallest set of forms that represents any value: null/false/true; numbers as literal int (0-31), Int64, UInt64, or Double; strings as encoded-length (<=63 bytes) or StrL1/StrL2/StrL4; arrays and objects as the length+count ArrLC*/ObjLC* forms with the narrowest fitting width. It deliberately skips the writer-side size optimizations (system/user strings, reference dedup, compressed strings, compact Arr0/1 + Obj0/1 forms, uniform arrays); the decoder handles all of those, so encode then decode round-trips. Encoding is infallible (trusted in-memory input). Wires the writer module and re-exports encode from mod.rs. Adds 16 tests, including round-trip coverage (encode then decode equals the original) for null/bools, every integer and float form, all string length forms, nested arrays and objects, a container wide enough to need the 2-byte length/count form, and the P0c golden scalar corpus values. The encoder is not yet wired into the SDK write/query paths.
Wires the binary JSON encoder into the SDK item write paths. create_item, replace_item, and upsert_item now build their request body via a new serialize_item_body helper that, when binary is enabled, serializes the item to a serde_json::Value and runs it through binary_json::encode (preamble-prefixed); otherwise it is the original serde_json::to_vec text path, byte-for-byte unchanged. Enablement is a temporary internal switch: binary_request_encoding_enabled reads AZURE_COSMOS_BINARY_ENCODING_ENABLED from the environment and is OFF unless set truthy, so there is no default behavior change. A proper client/driver option (defaulting from the same variable) plus the request-side negotiation header replace it in P3. Scope is the item write paths only. The query request body uses Content-Type application/query+json, where binary first-byte auto-detection is an open question deferred to P3 (query specifics); patch and batch are deferred per the spec; replace_container is control-plane, not item data. Adds tests that the text branch matches serde_json::to_vec, the binary branch round-trips through binary_json::decode, and the two branches differ.
…(P3a) Replaces P2b's temporary per-call binary_request_encoding_enabled() env read with a BinaryEncoding value resolved once at client construction and carried in ClientContext. It is the single source of truth shared by request encoding and (in P3b) response negotiation, so the two cannot drift apart. Enablement still defaults from AZURE_COSMOS_BINARY_ENCODING_ENABLED (disabled unless truthy: 1/true/yes/on, case-insensitive, trimmed); binary encoding is in preview so it stays env-only for now, with a public builder option to layer on when it ships. create/replace/upsert_item now read self.context.binary_encoding.enabled(). Adds hermetic tests for the truthy-value parser.
…ps (P3b) Driver: adds the x-ms-cosmos-supported-serialization-formats request header. A new CosmosRequestHeaders.supported_serialization_formats field is emitted by write_to_headers (mirroring supported_query_features), and CosmosOperation::with_supported_serialization_formats sets it. The driver stays a passthrough; the SDK decides the value. SDK: read_item, create_item, replace_item, and upsert_item now advertise JsonText,CosmosBinary via apply_binary_negotiation when binary encoding is enabled (the resolved-once flag from P3a). The value matches .NET's default (string.Join with no space). This tells the service the client accepts binary responses, which the P1 decoder handles; it is omitted (request byte-for-byte unchanged) when disabled. Scope is item operations only -- query uses application/query+json and is deferred to P3c; delete returns no body. Adds driver tests for the header field/emission/setter and SDK tests that the header is set when enabled and omitted when disabled (via a test-only BinaryEncoding constructor).
The decoder parses untrusted service bytes, so its contract is: for any input it must terminate and either succeed or return a BinaryError -- never panic, hang, or allocate on an attacker-controlled length prefix. Adds a fuzz_tests module asserting that contract with deterministic (seeded SplitMix64, dependency-free) sweeps: - 20k random buffers (biased toward interesting markers, some preamble-forced) decode without panicking; - every truncation prefix of the golden corpus and encoder output decodes or errors; - single-byte corruption at every position with boundary replacement bytes; - adversarial u32::MAX length prefixes (StrL4/ArrL4/ObjL4/Binary4/uniform array) error in O(1) instead of allocating ~4 GiB; - 10k-deep nesting hits the depth guard instead of overflowing the stack; - all 256 two-byte inputs terminate. Adds PRNG to the cosmos cspell dictionary.
Teach the emulator to decode binary request bodies and reply binary when the client advertises CosmosBinary, turning the emulator suite into a self-contained end-to-end validation of binary encoding: the SDK encodes a binary write body, the emulator decodes and stores it, replies binary, and the SDK auto-decodes. dispatch.rs parses the supported-serialization-formats header into a binary_response flag; response.rs adds with_value_body and success_response_with_format; operations.rs adds decode_request_body and honors the negotiated response format. binary_round_trip.rs adds two E2E tests covering create, read, upsert, replace and binary/text interop.
Adds a live test target that drives item create/read/upsert/replace/delete with AZURE_COSMOS_BINARY_ENCODING_ENABLED set, validating the full binary request-encode and binary response-decode loop against a real Cosmos DB account. Writes request a content response so each write also exercises the binary response path. Gated behind the new binary_encoding test category (ignored by default, like the emulator/multi_write/split targets) and a live connection string. Registers the test_category value in build.rs and the [[test]] target in Cargo.toml.
Add ints, fuzzable, and transcoders to the cosmos cspell dictionary, and convert the relative ARCHITECTURE.md, PATCH_HANDLER_SPEC.md, and TRANSPORT_PIPELINE_SPEC.md links to absolute GitHub URLs per the azure-sdk link guideline.
Refactor the minimal encoder's scalar/string/container emit logic into pub(super) helpers (encode_i64/u64/f64, encode_string, encode_container) plus shared ARRAY_LC_MARKERS/OBJECT_LC_MARKERS, so a future native serde Serializer can reuse them and produce byte-identical output. Pure internal extraction; encode(&Value) behavior and tests unchanged.
Add a BinaryError::Custom(String) variant and impl serde::ser::Error so the native binary-JSON serializer can surface a value's failing Serialize impl through the codec's own error type. Includes a Display arm and a unit test covering the ser::Error::custom mapping.
Add binary_json::ser with a BinarySerializer (serde::Serializer) and to_vec entry point that drives a value's Serialize impl straight to Cosmos binary JSON, eliminating the intermediate serde_json::Value tree used by the v1 encode(&Value) path. Reuses the shared step-1 emit helpers so scalar/string/container bytes stay identical to the Value encoder; each compound type buffers children in a scratch Vec and frames them with the length+count LC markers on end. Enums use serde's externally-tagged representation. Typed structs preserve field declaration order (like serde_json::to_vec) rather than the alphabetized to_value order. Includes parity, round-trip, ordering, and enum-representation tests; re-exports to_vec from the module root.
Switch serialize_item_body's binary branch from the two-pass T -> serde_json::Value -> encode(&Value) path to the driver's native binary_json::to_vec, encoding T straight to Cosmos binary JSON with no intermediate Value tree. Add From<BinaryError> for CosmosError (maps to SERIALIZATION_RESPONSE_BODY_INVALID) so the to_vec error propagates through ?. Text path and negotiation are unchanged; container_client tests pass.
…izer Add a generative property test that builds 2000 random serde_json::Value trees (nested arrays/objects, all scalar forms spanning literal-int, wide i64, double, and encoded-length/StrL1 strings) via a tiny dependency-free deterministic LCG, and asserts the native to_vec emits byte-identical output to encode(&Value) and round-trips through decode. Parity holds because both paths observe the same Value with identical key ordering.
Add a criterion benchmark comparing three item-write serialization strategies on a small (~64B) and a large (~1.7MB) log-entry item: serde_json::to_vec (text), the two-pass T -> serde_json::Value -> encode(&Value) v1 binary path, and the native binary_json::to_vec v2 path. Quantifies the Value-tree-elision win of v2 over v1. Registered as the binary_encode bench target.
…alizer Update BINARY_ENCODING_SPEC.md status from 'Planning / not yet implemented' to 'Implemented (encode + decode)'. Rewrite section 8.3 to describe the shipped native serde serializer (binary_json::to_vec): shared emit helpers, length-prefix scratch buffering, externally-tagged enums, declaration-order field preservation, and the criterion benchmark results (v2 ~2.5x faster than v1 on small items, ~24% faster and ~1.0 GiB/s on ~1.7MB items). Note the deferred native serde Deserializer. Point the benchmarks bullet at the implemented binary_encode bench.
…rializer Recenter BINARY_ENCODING_SPEC.md on the shipped native serde path (binary_json::to_vec) as the single encode strategy, since the transcode-through-Value prototype (v1) is no longer on the write path. Rewrite the architecture flowchart to show to_vec -> body and is_binary-gated decode; add a write/read sequence diagram; fold the serializer to the front of the codec section (8.1) with the length-prefix design; move the deferred native Deserializer to 8.3; mark scope table and delivery phases done; resolve the negotiation/Content-Type/v1-vs-v2 open questions; refresh the change map to the as-implemented layout. A pre-rewrite copy is kept locally as BINARY_ENCODING_SPEC.md.bak (untracked).
…ron3) Gates binary encoding to Document item ops, honors an explicit disable at the PATCH and FFI layers (rather than inheriting a lower layer), and adds CHANGELOG entries. - Guard on resource type: add CosmosDriver::binary_encoding_applies(resource_type, operation_type) requiring ResourceType::Document AND a point item op. Create/Read/Replace/Upsert are shared by control-plane resources (databases, containers, offers, ...), several of which carry JSON bodies that must never be transcoded to binary. Adds a negative test across control-plane resource types. - PATCH: set binary_encoding = Some(disabled) instead of None. None inherits the account/client layer via the layered OperationOptionsView, which would leak binary into the internal Read/Replace sub-ops. - FFI: honor an explicit tri-state false. The old 'if let Some(true)' collapsed false into None (inherit); now 'if let Some(enabled)' maps false to Some(disabled) so a host can force binary off. Splits the test into unset-vs-explicit-false and regenerates azurecosmosdriver.h. - CHANGELOG: add binary-encoding Features Added entries to azure_data_cosmos (0.38.0) and azure_data_cosmos_driver (0.7.0).
… (PR #4671) Addresses tvaron3's review: 'if client is enabled but for this operation it is disabled', the SDK ignored the per-operation value and always used the client-level flag. Replaces with_binary_encoding with resolve_binary_encoding, which prefers a caller-set OperationOptions.binary_encoding over the client default and returns the effective value so both serialize_item_body and the driver option are driven by the same decision. Previously serialize_item_body read only ClientContext.binary_encoding.enabled and with_binary_encoding unconditionally overwrote any per-op value, so 'client on, operation off' was impossible. Applied to create/replace/upsert/read item paths. Adds unit tests for both override directions (client-on/op-off -> text wire; client-off/op-on -> binary wire).
The 29 sample JSON files under azure_data_cosmos_perf/testdata (~507 MB) were added on this branch and were bloating the repo. They are used by exactly one consumer: the opt-in live test binary_sampled_testdata.rs (gated behind test_category="binary_encoding", requires a live account). Nothing in the build or CI gates depends on them, and the benchmarks use their own synthetic data. Untracks the files (git rm --cached) while keeping them on disk as a local copy, and adds azure_data_cosmos_perf/.gitignore so they are not re-added. Documents the runtime dependency in the test module docs, the load_sample_pool error messages (which now explain how to restore the corpus), and the perf crate README.
- SDK resolve_binary_encoding: write the resolved decision back as Some(effective) instead of erasing a disabled result to None. None means inherit in the driver layered OperationOptions, so erasing an explicit/resolved disable let a runtime/account default silently re-enable binary. Some(false) keeps the wire byte-for-byte unchanged while authoritatively overriding lower layers. - driver reader: fix uniform-array-of-empty-arrays false rejection (the inner_count==0 guard compared outer_count to remaining bytes, 0 at top level) and validate the inner-array marker; raise reference_budget floor from 64KB to 64MB (above the max Cosmos item size) so legitimately dictionary-encoded documents decode. - driver ser: reject serde_json RawValue explicitly instead of silently corrupting raw JSON into a wrapper object. - models: make OperationType::supports_binary_encoding pub(crate) (necessary-but-not-sufficient predicate).
…fuzzer implementation Adds BINARY_ENCODING_RFC.md, a normative, self-contained wire-format specification modeled on RFC 8949 (CBOR) and the protobuf encoding guide. Covers the preamble/auto-detection, the full marker taxonomy, scalars (ints/floats/strings incl. system/base64/compressed/GUID forms), containers, uniform number arrays, reference strings, canonical encoding rules, decoder conformance + security requirements, plus golden-vector and worked-example appendices. Wire constants are transcribed from the .NET reference and cross-checked against the Rust codec; details not confirmable from Rust alone are tagged [CROSS-VERIFY: .NET/C++] for a follow-up cross-language pass. Adds CBOR/Signedness to the cspell dictionary. cosmos(perf): add canonicalize+hash binary-encoding round-trip fuzzer Adds a design doc + runnable harness that generates random JSON with a seeded PRNG and validates it survives a live Cosmos round-trip across binary-encoding configs (text control, binary, binary+text-response), comparing a Cosmos-compatible canonical form of sent vs returned. Enables validating millions of distinct JSON structures over a multi-day soak; every run prints its seed for exact reproduction. Canonicalization normalizes numbers to a backend-compatible form (integral floats -> integer, shortest round-trippable decimal otherwise) and computes the sent canonical form from a normalized (one serialize->parse) copy so sent and backend-round-tripped docs are on equal footing - the number-normalization is the documented tuning/calibration surface. Ships 8 offline unit tests (canonicalizer + generator determinism + normalize idempotence) that run without a live account; the live fuzzer is gated behind test_category=binary_encoding. Adds canonicalizer/reparses/trippable to the cspell dictionary. cosmos(driver): add encode-direction conformance tests from binary-encoding RFC Implements the encoder conformance requirements of BINARY_ENCODING_RFC.md (section 7 canonical encoding + Appendix A golden vectors), filling the previously-missing encode side (decoder conformance per section 8 is already covered in reader.rs/de.rs). Adds binary_json/conformance.rs with four tests: (1) every golden-corpus value re-encodes to a buffer that decodes back to itself; (2) the encoder's exact canonical bytes for representative values are pinned as regression snapshots; (3) where the corpus stores a compact form the encoder does not emit (system strings, Arr0/Arr1, NumberUInt8, uniform arrays), the encoder's verbose output differs byte-wise but still round-trips - pinning the intentional valid-subset asymmetry; (4) the encoder always emits the preamble. 4 new tests, binary_json suite now 102 passing. cosmos(driver): cross-link the binary-encoding RFC and round-trip fuzzer docs Adds RFC section 1.4 'Relationship to the other binary-encoding artifacts' with a Mermaid diagram showing the RFC as source of truth and the golden corpus / conformance tests / in-tree fuzz / round-trip fuzzer as derived validators, plus a table mapping each artifact to the RFC sections it enforces. Updates the round-trip fuzzer doc's section 8 to point at the RFC as the normative spec and note that its number-canonicalization calibration feeds the backend rewrite rules back into RFC section 7. cosmos(perf): implement calibration mode for the round-trip fuzzer Adds AZURE_COSMOS_FUZZ_CALIBRATE mode to the binary round-trip fuzzer. It stores a fixed spread of numeric edge cases (NUMBER_PROBES: integral floats, repeating/high-precision floats, large/small exponents, integers near 2^63/2^64, negative zero, trailing zeros) through the binary path, reads them back, and prints a table comparing how canonicalize_number renders each value against the backend's actual returned form. DIFF rows flag number forms the canonicalizer does not yet model, so canonicalize_number can be tuned to the real backend rewrite - closing the design-doc section 3.1 calibration gap that a trustworthy soak depends on. Calibration is a diagnostic (prints the table, does not assert) since a DIFF on the first run is the expected signal to tune. Adds an offline unit test asserting every probe literal parses as a unique JSON number (9 offline tests now). Updates the design doc section 3.1/section 6 to document the implemented mode. cosmos(perf): calibrate canonicalize_number for integers above i64::MAX First live calibration run (18 probes) showed 16/18 number forms already match the backend: integral floats and integral exponents collapse to integers, -0 -> 0, trailing zeros drop, high-precision/0.1+0.2 round-trip exactly, and large/small exponents reparse to the same f64. The two DIFFs were integers above i64::MAX: the backend stores them as IEEE-754 doubles (lossy) and returns scientific notation (18446744073709551614 -> 1.8446744073709552e+19, 2^63 -> 9.223372036854776e+18). Routes u64-above-i64::MAX through f64 in canonicalize_number so a sent u64 and its returned double canonicalize identically; values up to i64::MAX keep exact integer form. Updates the offline test to assert the sent u64 matches the backend double form (and i64::MAX stays exact), and records the calibration findings in the design doc section 3.1. Re-running calibration after this yields all MATCH. cosmos(driver): document how the round-trip fuzzer detects codec gaps Adds section 2.1 'How this detects codec gaps' to the fuzzer design doc: a table mapping the failing config (text vs binary vs binary+text-response) to the broken layer (encoder / decoder / driver transcode / backend-rewrite calibration), the specific gap classes it surfaces that curated tests miss (encoder<->decoder disagreement on backend-only wire forms, number precision edges, unicode/escaping, container framing, the transcode path), the seed-based debugging loop, and its calibration/generator-range limitations. cosmos(perf): give each fuzzer config a distinct item id to avoid 409 conflicts The fuzzer stored one document under all three configs (text-control, binary, binary+text-response) using a single id generated per iteration, so the second config's create_item collided on the (pk, id) key and failed with 409 Conflict (surfacing as 'iter=0 config=binary: create failed: 409'). This was a harness bug, not a codec issue. Generates the document body once per iteration (so all three configs still test the same value three ways) but assigns a distinct id per config, recomputing the sent canonical form per config. Document content stays deterministic from the seed; only the ids differ (they were always random uuids).
…on-canon, sha2) Phase 1 of evolving the binary round-trip fuzzer to use arbitrary-json for generation, json-canon (RFC 8785) for structural canonicalization, and sha2 for durable hashing. Adds the workspace deps and wires them as dev-dependencies of azure_data_cosmos_perf (test-only; crate is publish=false). No behavior change yet.
…r Phase 2) Isolate the one number-specific canonicalization step (the calibrated backend number-rewrite rules, design doc 3.1) into a standalone normalize_number / normalize_numbers Value-to-Value transform. This is the piece that must stay under our control because RFC 8785 (JCS) number formatting is not the backend's store-time rewrite; the surrounding structural canonicalization can later be delegated to a JCS serializer. canonicalize_number now delegates to normalize_number and renders the result, keeping the canonical output byte-identical (verified by the existing tests plus a new normalize_number_matches_canonicalize_number probe check). Adds recursive-rewrite and idempotence tests. No live-behavior change.
…fuzzer Phase 3) Replace the hand-rolled structural canonicalizer with json_canon (RFC 8785 / JCS) fed by the calibrated normalize_numbers, and switch the differential hash from 64-bit SipHash to SHA-256 (stable across runs/platforms for a durable corpus). Finding: json-canon refuses to emit integers >= 2^53 (u64 must be less than JSON max safe integer). Cosmos preserves i64 integers exactly and stores u64 above i64::MAX as lossy doubles, so normalize_number now maps JCS-unsafe integers (and integral floats >= 2^53) to stable decimal/double string tokens that are only compared for equality, keeping sent and round-tripped values comparable without tripping the JCS safe-integer guard. Offline tests updated; 12 passed.
…e 4) Replace the hand-rolled recursive generator (gen_value/gen_array/gen_string/gen_number/gen_key) with an arbitrary-json-backed generator. The document is produced by feeding a byte buffer -- derived deterministically from the SplitMix64 seed stream -- into arbitrary_json::ArbitraryObject, so the same AZURE_COSMOS_FUZZ_SEED still reproduces the same document. max_depth scales the byte budget (arbitrary stops nesting when the bytes run out). A post-generation bound_value pass preserves the env-knob contract: by default numbers are clamped into the calibrated-safe envelope (design doc 3.2) and strings to ASCII; wide_numbers / unicode widen those. Removed the now-unused SplitMix64 chance/f64_unit helpers. Offline determinism + idempotence tests still pass (12).
…nteger finding (Phase 5) Add 9.7 implementation-status table (Phases 1-4 landed, Phase 5 live re-calibration manual, Phase 6 deferred), document the json-canon >= 2^53 integer rejection and the string-token workaround, and update 2's hash description from SipHash to SHA-256.
…, 1500 round-trips clean) Calibration against a real account: all 18 number probes MATCH, including every JCS-unsafe edge routed through the string-token workaround. Soak: 500 docs x 3 configs = 1500 round-trips all canonical-equal (seed 1784934026943565900). Marks the first three 9.6 acceptance items done and flips the Phase 5 status to landed.
… fuzzer design Adds section 2.2 with four Mermaid diagrams -- the per-document canonicalize/store/compare loop, the three-config layer-localization, the reproduce-reduce-classify debugging loop, and the calibration safety valve -- so readers can grasp how the live fuzzer catches and localizes codec issues at a glance.
…create/read/replace/upsert) The harness previously only drove create + read. Binary encoding is honored for the four body-carrying point ops -- create, read, replace, upsert -- so each config now round-trips all four against the same generated document, widening coverage of the request-encode + response-decode paths for replace and upsert. Progress/DONE counters and the design doc (2, 2.2 loop diagram) updated accordingly. delete (no body) and patch/batch/bulk (deferred) stay excluded.
…/upsert) Add the all-four-point-ops soak result: 1000 docs x 3 configs x 4 ops = 12,000 round-trips all canonical-equal (seed 1784944014111583800), confirming replace/upsert round-trip identically to create/read. Keeps the earlier create+read soak for history.
… JSON The arbitrary-json-only generator was near-flat (avg nesting depth ~1.3 regardless of max_depth) because arbitrary_iter stops recursing almost immediately. Replace it with a hybrid: an explicit depth-controlled skeleton builds a nested object/array spine to a target depth in [1, max_depth], while every leaf and filler branch is irregular arbitrary-json (random keys/strings/numbers, mixed and uniform-number arrays, nested arrays-of-objects). Numbers/strings still pass through bound_value for the calibrated envelope. Measured avg depth now scales with the knob (~3.9 at 3, ~8.5 at 12; deepest 11-17 levels), exercising the codec container framing and the decoder MAX_DEPTH guard. Adds a generator_depth_scales_with_max_depth regression test; updates design doc 4. 13 offline tests pass, clippy clean.
Phase 6: add an offline, coverage-guided cargo-fuzz crate (azure_data_cosmos_driver/fuzz) that fuzzes the binary-JSON protocol itself with four targets (decode, from_slice, transcode_to_text, decode_reencode_roundtrip) feeding arbitrary/mutated bytes into the codec. It closes the gap the value-space round-trip fuzzer can't: mis-encoded frames the encoder never produces. Isolated from the stable workspace via its own empty [workspace]; nightly/libFuzzer only. Enrich the live round-trip generator so each document is a really complex JSON: a guaranteed _sampler subtree covering every value category (integer, float, alphabetic, alphanumeric, free text, non-ASCII/emoji, boolean, null, number array, nested), plus a new AZURE_COSMOS_FUZZ_BREADTH branching knob, deeper default depth, and a bigger arbitrary-json budget. Add a coverage assertion test, an offline print_sample_documents test, and an AZURE_COSMOS_FUZZ_PRINT knob to pretty-print generated docs. Update the design doc (new section 9.8) and cspell dictionary.
Member
Author
|
Duplicate of #4898. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.