diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml index 210fcea..de9e821 100644 --- a/.github/workflows/scheduled.yml +++ b/.github/workflows/scheduled.yml @@ -33,7 +33,7 @@ jobs: strategy: fail-fast: false matrix: - target: [fuzz_journal_line, fuzz_entry_validate, fuzz_op_sequence, fuzz_core_serde, fuzz_store_open, fuzz_store_replay, fuzz_settings_replay, fuzz_import_csv, fuzz_config_parse, fuzz_extract_pdf, fuzz_extract_xlsx] + target: [fuzz_journal_line, fuzz_entry_validate, fuzz_op_sequence, fuzz_core_serde, fuzz_store_open, fuzz_store_replay, fuzz_settings_replay, fuzz_import_csv, fuzz_config_parse, fuzz_extract_pdf, fuzz_extract_xlsx, fuzz_http_mcp_request, fuzz_upload_put] steps: - uses: actions/checkout@v4 - uses: jdx/mise-action@v2 diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 99e0016..3cedeb2 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -8,6 +8,28 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common 0.2.2", + "inout", +] + +[[package]] +name = "aes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures 0.3.1", + "zeroize", +] + [[package]] name = "ahash" version = "0.8.12" @@ -104,12 +126,81 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "az" version = "1.3.0" @@ -212,6 +303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", + "zeroize", ] [[package]] @@ -303,6 +395,15 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + [[package]] name = "cc" version = "1.8.0" @@ -327,6 +428,30 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chacha20poly1305" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", +] + [[package]] name = "chinese-number" version = "0.7.8" @@ -386,6 +511,17 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "inout", +] + [[package]] name = "citationberg" version = "0.7.0" @@ -460,6 +596,21 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb6feb175df65d0a3ccd3e73484e940d0ac664e8034f0519fd49a93db3071d9" +dependencies = [ + "rustversion", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -475,6 +626,12 @@ dependencies = [ "libm", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -549,7 +706,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.3", "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -582,12 +741,52 @@ dependencies = [ "cmov", ] +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.9", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core", + "quote", + "syn 3.0.9", +] + [[package]] name = "data-url" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + [[package]] name = "defmt" version = "1.2.0" @@ -659,8 +858,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", + "const-oid", "crypto-common 0.2.2", "ctutils", + "zeroize", ] [[package]] @@ -695,6 +896,12 @@ dependencies = [ "syn 3.0.9", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecow" version = "0.2.6" @@ -736,6 +943,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "euclid" version = "0.22.14" @@ -889,12 +1106,71 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + [[package]] name = "futures-task" version = "0.3.34" @@ -907,8 +1183,13 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -956,6 +1237,7 @@ dependencies = [ "js-sys", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasm-bindgen", ] @@ -1272,6 +1554,60 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hybrid-array" version = "0.4.15" @@ -1281,6 +1617,40 @@ dependencies = [ "typenum", ] +[[package]] +name = "hyper" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" +dependencies = [ + "atomic-waker", + "bytes", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hypher" version = "0.1.8" @@ -1507,6 +1877,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1581,6 +1957,15 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +[[package]] +name = "inout" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b5cf4372d022092674a83b8febf5865f7a573cc0c5baec5e49c879b6a20978f" +dependencies = [ + "hybrid-array", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1754,6 +2139,7 @@ dependencies = [ "ledger-config", "ledger-core", "ledger-import", + "ledger-mcp", "ledger-store", "libfuzzer-sys", "rust_decimal", @@ -1782,6 +2168,52 @@ dependencies = [ "typst-pdf", ] +[[package]] +name = "ledger-mcp" +version = "0.1.0" +dependencies = [ + "axum", + "base64 0.22.1", + "chrono", + "futures-util", + "http-body-util", + "ledger-config", + "ledger-core", + "ledger-import", + "ledger-package", + "ledger-store", + "ledger-vault", + "libc", + "rmcp", + "rust_decimal", + "schemars", + "serde", + "serde_json", + "sha2 0.10.9", + "tempfile", + "thiserror", + "tokio", + "tokio-util", + "toml 1.1.8+spec-1.1.0", + "tower", + "zeroize", +] + +[[package]] +name = "ledger-package" +version = "0.1.0" +dependencies = [ + "chrono", + "ledger-config", + "ledger-core", + "ledger-import", + "ledger-store", + "ledger-vault", + "rust_decimal", + "serde_json", + "zip 9.1.3", +] + [[package]] name = "ledger-reader" version = "0.1.0" @@ -1809,11 +2241,33 @@ dependencies = [ "rust_decimal", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "subtle", "thiserror", + "windows-sys", ] +[[package]] +name = "ledger-vault" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "chacha20poly1305", + "getrandom 0.2.17", + "hex", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror", + "zeroize", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f454fd6831a29c0e827ed075bca2a15be201bd57f4168f0fd1041124eab17e" + [[package]] name = "libc" version = "0.2.190" @@ -1868,6 +2322,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "lipsum" version = "0.9.1" @@ -1902,6 +2362,21 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +[[package]] +name = "lzma-rust2" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c46f3fcd2e888afdb01a07efc071cb21300e07c68d32c3e8b01b5c8cb1fcaa" +dependencies = [ + "sha2 0.11.1", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.8.3" @@ -1917,6 +2392,12 @@ dependencies = [ "libc", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1937,6 +2418,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + [[package]] name = "moxcms" version = "0.8.1" @@ -2092,6 +2584,22 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.3", + "hmac", +] + [[package]] name = "pdf-writer" version = "0.15.0" @@ -2231,6 +2739,16 @@ dependencies = [ "miniz_oxide 0.8.9", ] +[[package]] +name = "poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" +dependencies = [ + "cpufeatures 0.3.1", + "universal-hash", +] + [[package]] name = "polycool" version = "0.4.0" @@ -2282,6 +2800,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" +[[package]] +name = "ppmd-rust" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4acc8d264ea385db64ccfdcdfd5909ee9eda0975666723d59f3bfec80b3611" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2404,6 +2928,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2442,6 +2977,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rayon" version = "1.12.0" @@ -2513,6 +3054,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "regex" version = "1.13.1" @@ -2568,6 +3129,52 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rmcp" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8be15815186873ea063bf005011d16c356804973489e40c39116ae3a2878d1f" +dependencies = [ + "async-trait", + "base64 0.23.1", + "bytes", + "chrono", + "futures", + "http", + "http-body", + "http-body-util", + "indexmap", + "libc", + "pastey", + "pin-project-lite", + "rand 0.10.3", + "rmcp-macros", + "schemars", + "serde", + "serde_json", + "sse-stream", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "rmcp-macros" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0748e054a5f5598b3213cf72099ae6b2745ba4d996c59ccf455150a7dbb839" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn 3.0.9", +] + [[package]] name = "roman-numerals-rs" version = "3.1.0" @@ -2635,6 +3242,19 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" +[[package]] +name = "rustix" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -2674,6 +3294,32 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" +dependencies = [ + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c3a92094fa7d61aa124645844facb6b554dfc797136d0f5fd1f890e2bffc69" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.9", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2716,6 +3362,17 @@ dependencies = [ "syn 3.0.9", ] +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "serde_json" version = "1.0.154" @@ -2758,6 +3415,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -2771,6 +3440,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2782,12 +3462,33 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.10" @@ -2860,12 +3561,35 @@ version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +[[package]] +name = "sse-stream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c25ac7aff0abd1dbc474536e40416e1102c7dd9bfba0b9861c6d357f835dcfb4" +dependencies = [ + "bytes", + "futures-util", + "http-body", + "http-body-util", + "pin-project-lite", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -2894,6 +3618,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.27.2" @@ -2965,6 +3695,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "synstructure" version = "0.14.0" @@ -2997,6 +3733,19 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "thin-vec" version = "0.2.21" @@ -3030,6 +3779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -3096,6 +3846,59 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" +[[package]] +name = "tokio" +version = "1.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "libc", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.23" @@ -3188,6 +3991,66 @@ version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + [[package]] name = "ttf-parser" version = "0.25.1" @@ -3658,6 +4521,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3723,6 +4596,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -4145,6 +5029,15 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" +dependencies = [ + "serde", +] + [[package]] name = "zerotrie" version = "0.2.5" @@ -4202,15 +5095,27 @@ version = "9.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1156b39d142a34b999c2310a50455e0cb3760bf7efad0122f796d813bee26ccb" dependencies = [ + "aes", + "bzip2", + "constant_time_eq", "crc32fast", + "deflate64", "flate2", "getrandom 0.4.3", + "hmac", "indexmap", + "lzma-rust2", "memchr", "nix", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", "typed-path", "windows-sys", + "zeroize", "zopfli", + "zstd", ] [[package]] @@ -4237,6 +5142,34 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zstd" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.1.2+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zune-core" version = "0.5.3" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 381a730..5988cde 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -24,6 +24,10 @@ path = "../crates/ledger-store" [dependencies.ledger-import] path = "../crates/ledger-import" +[dependencies.ledger-mcp] +path = "../crates/ledger-mcp" +features = ["fuzzing"] + [dependencies.ledger-config] path = "../crates/ledger-config" @@ -105,3 +109,17 @@ path = "fuzz_targets/fuzz_settings_replay.rs" test = false doc = false bench = false + +[[bin]] +name = "fuzz_http_mcp_request" +path = "fuzz_targets/fuzz_http_mcp_request.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz_upload_put" +path = "fuzz_targets/fuzz_upload_put.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/fuzz_http_mcp_request.rs b/fuzz/fuzz_targets/fuzz_http_mcp_request.rs new file mode 100644 index 0000000..6ed94b2 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_http_mcp_request.rs @@ -0,0 +1,169 @@ +// Fuzz target: the books server's HTTP door takes arbitrary requests. +// Method, path, headers and body are all untrusted; the door is the very +// router the server mounts (ledger-mcp local.rs `router`), in-process over a +// real book, with no socket (`fuzz_door`). Whatever arrives: the door +// answers (no panic, no hang), and a request that carries no token live +// for the book is refused 4xx and never run (INVARIANT 8, local.rs); one +// that carries it never makes the door fail (no 5xx on the MCP route). +#![no_main] + +use arbitrary::Arbitrary; +use ledger_mcp::local::fuzz_door::FuzzDoor; +use libfuzzer_sys::fuzz_target; +use std::cell::RefCell; + +#[derive(Arbitrary, Debug)] +enum Auth { + None, + Valid, + Other, + /// `Authorization` with whatever text. + Raw(Vec), +} + +#[derive(Arbitrary, Debug)] +enum Body { + Raw(Vec), + /// A JSON-RPC message the server could understand. + Rpc { + method: u8, + id: u8, + params: String, + }, + /// `tools/call` of a name picked from a few real ones, arguments as text. + Call { + tool: u8, + arguments: String, + }, +} + +#[derive(Arbitrary, Debug)] +struct Request { + method: Vec, + verb: u8, + route: u8, + book: u8, + suffix: String, + auth: Auth, + session: Option, + content_type: u8, + accept: bool, + extra: Vec<(Vec, Vec)>, + body: Body, +} + +const METHODS: [&str; 6] = ["POST", "GET", "DELETE", "PUT", "OPTIONS", "PATCH"]; +const RPC: [&str; 8] = [ + "initialize", + "notifications/initialized", + "tools/list", + "ping", + "resources/list", + "prompts/list", + "tools/call", + "logging/setLevel", +]; +const TOOLS: [&str; 6] = [ + "books.start", + "books.trial_balance", + "books.draft_entry", + "books.begin_upload", + "books.settings", + "books.handbook", +]; + +thread_local! { + /// The door, and how many requests it has had: a fresh book every so + /// often, because sessions and the access log only grow. + static DOOR: RefCell> = const { RefCell::new(None) }; +} + +fn clean(s: &str) -> String { + s.chars() + .filter(|c| c.is_ascii_alphanumeric() || "-_.".contains(*c)) + .take(40) + .collect() +} + +fuzz_target!(|r: Request| { + DOOR.with(|cell| { + let mut cell = cell.borrow_mut(); + if cell.as_ref().is_none_or(|(d, n)| *n > 3000 || d.sessions() > 500) { + *cell = Some((FuzzDoor::new(), 0)); + } + let (door, n) = cell.as_mut().unwrap(); + *n += 1; + + let method: Vec = if r.verb % 4 == 0 { + r.method.clone() + } else { + METHODS[r.verb as usize % METHODS.len()].as_bytes().to_vec() + }; + let book = match r.book % 4 { + 0 | 1 => "acme".to_string(), + 2 => "nobody".to_string(), + _ => clean(&r.suffix), + }; + let path = match r.route % 6 { + 0..=2 => format!("/b/{book}/mcp"), + 3 => format!("/b/{book}/u/{}", clean(&r.suffix)), + 4 => format!("/b/{book}/a/{}", clean(&r.suffix)), + _ => format!("/b/{book}/x/{}", clean(&r.suffix)), + }; + let mut headers: Vec<(Vec, Vec)> = Vec::new(); + match &r.auth { + Auth::None => {} + Auth::Valid => { + headers.push((b"authorization".to_vec(), format!("Bearer {}", door.bearer()).into_bytes())); + } + Auth::Other => { + headers.push((b"authorization".to_vec(), format!("Bearer {}x", door.other_bearer()).into_bytes())); + } + Auth::Raw(v) => { + headers.push((b"authorization".to_vec(), v.clone())); + } + } + if let Some(s) = &r.session { + headers.push((b"mcp-session-id".to_vec(), clean(s).into_bytes())); + } + headers.push(( + b"content-type".to_vec(), + ["application/json", "text/plain", "application/octet-stream", ""][r.content_type as usize % 4] + .as_bytes() + .to_vec(), + )); + if r.accept { + headers.push((b"accept".to_vec(), b"application/json, text/event-stream".to_vec())); + } + headers.extend(r.extra.iter().take(4).cloned()); + // The door's own rule, asked directly: any header (the authorization + // one, or a smuggled extra) can carry a token live for the book. + let live = door.carries_a_token(&headers); + let body = match &r.body { + Body::Raw(b) => b.clone(), + Body::Rpc { method, id, params } => { + let params = serde_json::from_str::(params).unwrap_or_default(); + serde_json::json!({"jsonrpc": "2.0", "id": id, "method": RPC[*method as usize % RPC.len()], "params": params}) + .to_string() + .into_bytes() + } + Body::Call { tool, arguments } => { + let args = serde_json::from_str::(arguments).unwrap_or_default(); + serde_json::json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call", + "params": {"name": TOOLS[*tool as usize % TOOLS.len()], "arguments": args}}) + .to_string() + .into_bytes() + } + }; + + let Some((status, _)) = door.send(&method, &path, &headers, body) else { + return; // not an HTTP request at all + }; + if !live && book == "acme" && path.ends_with("/mcp") { + assert!((400..500).contains(&status), "no token, yet {status} for {path}"); + } + if live && path.ends_with("/mcp") && book == "acme" { + assert!(status < 500, "a valid token made the door fail: {status}"); + } + }); +}); diff --git a/fuzz/fuzz_targets/fuzz_upload_put.rs b/fuzz/fuzz_targets/fuzz_upload_put.rs new file mode 100644 index 0000000..820a1b7 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_upload_put.rs @@ -0,0 +1,130 @@ +// Fuzz target: a file's bytes come in only by `PUT /b/{book}/u/{upload}` +// (ledger-mcp local.rs INVARIANT 9), through the same in-process router as +// fuzz_http_mcp_request. The body, the declared size and hash, the upload +// id and the token are all arbitrary. Whatever arrives: the door answers, +// and bytes are taken only if they are exactly the declared ones, from the +// token the upload was granted to, once: a 200 for anything else is a bug. +#![no_main] + +use arbitrary::Arbitrary; +use ledger_mcp::local::fuzz_door::FuzzDoor; +use libfuzzer_sys::fuzz_target; +use std::cell::RefCell; + +#[derive(Arbitrary, Debug)] +enum Declared { + Honest, + /// The size off by this much (never 0). + SizeOff(i8), + /// The hash of other bytes. + HashOf(Vec), + /// A hash that is not 64 hex digits. + BadHash(String), +} + +#[derive(Arbitrary, Debug)] +enum Who { + Owner, + Other, + Nobody, +} + +#[derive(Arbitrary, Debug)] +struct Put { + body: Vec, + declared: Declared, + who: Who, + /// Use an id that was never granted (this text, cleaned). + unknown_id: Option, + /// Send it a second time too. + again: bool, + /// And a body of another length the second time. + second: Vec, +} + +thread_local! { + static DOOR: RefCell> = const { RefCell::new(None) }; +} + +fuzz_target!(|p: Put| { + DOOR.with(|cell| { + let mut cell = cell.borrow_mut(); + if cell.as_ref().is_none_or(|(_, n)| *n > 1500) { + *cell = Some((FuzzDoor::new(), 0)); + } + let (door, n) = cell.as_mut().unwrap(); + *n += 1; + + let sha = ledger_store::journal::sha256_hex(&p.body); + let (grant_sha, grant_size) = match &p.declared { + Declared::Honest => (sha.clone(), p.body.len() as u64), + Declared::SizeOff(d) => { + let d = if *d == 0 { 1 } else { i64::from(*d) }; + (sha.clone(), (p.body.len() as i64 + d).max(0) as u64) + } + Declared::HashOf(other) => ( + ledger_store::journal::sha256_hex(other), + p.body.len() as u64, + ), + Declared::BadHash(s) => (s.clone(), p.body.len() as u64), + }; + let size_ok = grant_size == p.body.len() as u64 && grant_sha == sha; + + let granted = door.grant(&grant_sha, grant_size); + let id = match (&p.unknown_id, &granted) { + (Some(s), _) => { + let s: String = s + .chars() + .filter(|c| c.is_ascii_alphanumeric() || *c == '_') + .take(40) + .collect(); + if s.is_empty() { "up_x".to_string() } else { s } + } + (None, Ok(id)) => id.clone(), + (None, Err(_)) => "up_none".to_string(), + }; + let real = p.unknown_id.is_none() && granted.is_ok(); + let bearer = match p.who { + Who::Owner => Some(door.bearer().to_string()), + Who::Other => Some(door.other_bearer().to_string()), + Who::Nobody => None, + }; + let mut headers = Vec::new(); + if let Some(b) = &bearer { + headers.push(( + b"authorization".to_vec(), + format!("Bearer {b}").into_bytes(), + )); + } + let path = format!("/b/acme/u/{id}"); + let Some((status, text)) = door.send(b"PUT", &path, &headers, p.body.clone()) else { + return; + }; + let may_succeed = real && matches!(p.who, Who::Owner) && size_ok; + if status == 200 { + assert!( + may_succeed, + "bytes taken that should not be: {:?} {:?}", + p.who, p.declared + ); + let said = String::from_utf8_lossy(&text); + assert!( + said.contains(&sha), + "the answer doesn't name the bytes' hash: {said}" + ); + } else if may_succeed { + // Everything lined up: the only way not to be taken is a + // failure of the door itself. + panic!( + "honest upload by its owner refused: {status} {}", + String::from_utf8_lossy(&text) + ); + } + if status == 200 && p.again { + let Some((again, _)) = door.send(b"PUT", &path, &headers, p.second.clone()) else { + return; + }; + assert_ne!(again, 200, "an upload taken twice"); + } + }); +});