Project import generated by Copybara. GitOrigin-RevId: ae57b289f93d58e7c0c8a0e15db62a9659c85ac8 Change-Id: I5bb44e2cf1472491ae55d154c52f870a45348e0f
diff --git a/BUILD b/BUILD index db6ef52..5456139 100644 --- a/BUILD +++ b/BUILD
@@ -114,7 +114,10 @@ rust_library( name = "handle_map", srcs = glob(include = ["common/handle_map/src/**/*.rs"]), - deps = [":lock_adapter"], + deps = [ + ":lock_adapter", + "@crate_index//:lazy_static", + ], ) rust_library( @@ -141,6 +144,7 @@ "@crate_index//:nom", "@crate_index//:strum", "@crate_index//:tinyvec", + "@crate_index//:itertools" ], )
diff --git a/bazel_placeholder/Cargo.lock b/bazel_placeholder/Cargo.lock index bed8428..7f40048 100644 --- a/bazel_placeholder/Cargo.lock +++ b/bazel_placeholder/Cargo.lock
@@ -219,6 +219,12 @@ ] [[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -331,6 +337,15 @@ ] [[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -397,6 +412,7 @@ "ed25519-dalek", "hkdf", "hmac", + "itertools", "lazy_static", "nom", "p256",
diff --git a/bazel_placeholder/Cargo.toml b/bazel_placeholder/Cargo.toml index e7ee82a..3344be6 100644 --- a/bazel_placeholder/Cargo.toml +++ b/bazel_placeholder/Cargo.toml
@@ -29,4 +29,5 @@ x25519-dalek = { version = "2.0.0", default-features = false } subtle = { version = "2.5.0", default-features = false } sec1 = "0.7.3" -sha2 = { version = "0.10.8", default-features = false } \ No newline at end of file +sha2 = { version = "0.10.8", default-features = false } +itertools = "0.13.0" \ No newline at end of file
diff --git a/common/Cargo.lock b/common/Cargo.lock index 496db0d..1b19b5a 100644 --- a/common/Cargo.lock +++ b/common/Cargo.lock
@@ -3,6 +3,21 @@ version = 3 [[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -34,9 +49,9 @@ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -49,33 +64,33 @@ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -83,15 +98,26 @@ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] -name = "arbitrary" -version = "1.3.2" +name = "async-trait" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" @@ -100,31 +126,92 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] -name = "bit-set" -version = "0.5.3" +name = "axum" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ - "bit-vec", + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "tokio", + "tower", + "tower-layer", + "tower-service", ] [[package]] -name = "bit-vec" -version = "0.6.3" +name = "axum-core" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.5.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", @@ -152,9 +239,9 @@ [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "cast" @@ -164,14 +251,9 @@ [[package]] name = "cc" -version = "1.0.97" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" -dependencies = [ - "jobserver", - "libc", - "once_cell", -] +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cesu8" @@ -196,7 +278,7 @@ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -228,9 +310,9 @@ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -238,9 +320,9 @@ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -250,9 +332,9 @@ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -262,9 +344,9 @@ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cmd_runner" @@ -277,15 +359,17 @@ "globset", "log", "owo-colors", + "serde", + "serde_json", "shell-escape", "xshell", ] [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "combine" @@ -298,6 +382,16 @@ ] [[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -354,9 +448,9 @@ [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -391,9 +485,9 @@ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -402,65 +496,52 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] -name = "derive_fuzz_example" -version = "0.1.0" -dependencies = [ - "arbitrary", - "derive_fuzztest", - "libfuzzer-sys", - "quickcheck", -] - -[[package]] -name = "derive_fuzztest" -version = "0.1.0" -dependencies = [ - "arbitrary", - "derive_fuzztest_macro", - "proptest", - "proptest-arbitrary-interop", - "quickcheck", -] - -[[package]] -name = "derive_fuzztest_macro" -version = "0.1.0" -dependencies = [ - "derive_fuzztest", - "pretty_assertions", - "prettyplease", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "diff" -version = "0.1.13" +name = "directories" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] -name = "env_logger" -version = "0.8.4" +name = "encoding_rs" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ - "log", - "regex", + "cfg-if", ] [[package]] -name = "errno" -version = "0.3.8" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -492,6 +573,80 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -503,6 +658,12 @@ ] [[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -522,6 +683,25 @@ ] [[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] name = "half" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -541,6 +721,12 @@ ] [[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -553,6 +739,176 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] name = "iana-time-zone" version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -576,6 +932,32 @@ ] [[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] name = "is-terminal" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -588,9 +970,9 @@ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -609,9 +991,9 @@ [[package]] name = "java-locator" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90003f2fd9c52f212c21d8520f1128da0080bad6fff16b68fe6e7f2f0c3780c2" +checksum = "d2abecabd9961c5e01405a6426687fcf1bd94a269927137e4c3cc1a7419b93fd" dependencies = [ "glob", "lazy_static", @@ -642,15 +1024,6 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] -name = "jobserver" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" -dependencies = [ - "libc", -] - -[[package]] name = "js-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -661,29 +1034,18 @@ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" -dependencies = [ - "arbitrary", - "cc", - "once_cell", -] +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" @@ -696,16 +1058,20 @@ ] [[package]] -name = "libm" -version = "0.2.8" +name = "libredox" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] [[package]] name = "license" -version = "3.3.1" +version = "3.4.0+3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bba2f02ee1d13cd4bea565658939cd851d70e391f34f7c27b45b2077df3a2e4" +checksum = "a7da1e0d845faf299a9fe5f201a918a0dc0d5fc22c7b9580a6a23fed3a912b37" dependencies = [ "reword", "serde", @@ -714,15 +1080,15 @@ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_adapter" version = "0.1.0" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -737,15 +1103,27 @@ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" @@ -754,6 +1132,44 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -770,7 +1186,32 @@ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", +] + +[[package]] +name = "oauth" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "directories", + "hex", + "log", + "open", + "rand", + "reqwest", + "serde", + "tokio", + "url", +] + +[[package]] +name = "object" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +dependencies = [ + "memchr", ] [[package]] @@ -781,9 +1222,69 @@ [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[package]] +name = "open" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" +dependencies = [ + "pathdiff", + "windows-sys 0.42.0", +] + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "owo-colors" @@ -792,10 +1293,83 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] -name = "plotters" -version = "0.3.5" +name = "parking_lot" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plotters" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ "num-traits", "plotters-backend", @@ -806,15 +1380,15 @@ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" dependencies = [ "plotters-backend", ] @@ -846,81 +1420,15 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] -name = "pretty_assertions" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" -dependencies = [ - "diff", - "yansi", -] - -[[package]] -name = "prettyplease" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] -name = "proptest" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" -dependencies = [ - "bit-set", - "bit-vec", - "bitflags", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "proptest-arbitrary-interop" -version = "0.1.0" -source = "git+https://github.com/brson/proptest-arbitrary-interop.git?branch=incorrect-format#9ae407e9805feb109b3d49cc737166bda7e698c3" -dependencies = [ - "arbitrary", - "proptest", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quickcheck" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" -dependencies = [ - "env_logger", - "log", - "rand", -] - -[[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -960,15 +1468,6 @@ ] [[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - -[[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -989,10 +1488,30 @@ ] [[package]] -name = "regex" -version = "1.10.4" +name = "redox_syscall" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -1002,9 +1521,9 @@ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -1013,9 +1532,53 @@ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] [[package]] name = "reword" @@ -1027,12 +1590,33 @@ ] [[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1040,18 +1624,52 @@ ] [[package]] -name = "rusty-fork" -version = "0.3.0" +name = "rustls" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1067,25 +1685,57 @@ ] [[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "serde" -version = "1.0.200" +name = "security-framework" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -1094,9 +1744,9 @@ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -1104,16 +1754,66 @@ ] [[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[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 = "shell-escape" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" [[package]] -name = "spin" -version = "0.5.2" +name = "signal-hook-registry" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "spin" @@ -1131,10 +1831,16 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "syn" -version = "2.0.61" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -1142,6 +1848,39 @@ ] [[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1155,18 +1894,18 @@ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -1184,10 +1923,142 @@ ] [[package]] -name = "unarray" -version = "0.1.4" +name = "tinyvec" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -1196,27 +2067,50 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] -name = "utf8parse" -version = "0.2.1" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "wait-timeout" -version = "0.2.0" +name = "url" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ - "libc", + "form_urlencoded", + "idna", + "percent-encoding", ] [[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1227,6 +2121,15 @@ ] [[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1258,6 +2161,18 @@ ] [[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] name = "wasm-bindgen-macro" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1333,7 +2248,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -1347,11 +2277,20 @@ [[package]] name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1371,18 +2310,33 @@ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1393,9 +2347,15 @@ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -1405,9 +2365,15 @@ [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -1417,15 +2383,21 @@ [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -1435,9 +2407,15 @@ [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -1447,9 +2425,15 @@ [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -1459,9 +2443,15 @@ [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -1471,9 +2461,25 @@ [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] [[package]] name = "xshell" @@ -1491,7 +2497,7 @@ checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" [[package]] -name = "yansi" -version = "0.5.1" +name = "zeroize" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
diff --git a/common/Cargo.toml b/common/Cargo.toml index 9e8972b..30689fc 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml
@@ -2,11 +2,9 @@ members = [ "build_scripts", "cmd_runner", - "derive_fuzztest", - "derive_fuzztest/fuzz", - "derive_fuzztest_macro", "handle_map", "lock_adapter", + "oauth", "pourover", "pourover_macro", ] @@ -32,8 +30,6 @@ [workspace.dependencies] # local crates cmd_runner = { path = "cmd_runner" } -derive_fuzztest = { path = "derive_fuzztest" } -derive_fuzztest_macro = { path = "derive_fuzztest_macro" } lock_adapter = { path = "lock_adapter" } handle_map = { path = "handle_map" } pourover = { path = "pourover" } @@ -42,7 +38,7 @@ # from crates.io anyhow = "1.0.75" arbitrary = "1.3.2" -clap = { version = "4.4.11", features = ["derive"] } +clap = { version = "4.5.13", features = ["derive"] } criterion = { version = "0.5.1", features = ["html_reports"] } jni = "0.21.1" lazy_static = { version = "1.4.0", features = ["spin_no_std"] } @@ -51,18 +47,22 @@ pretty_assertions = "1.4.0" prettyplease = "0.2.16" proc-macro2 = "1.0" -proptest = "1.4.0" -proptest-arbitrary-interop = { git = "https://github.com/brson/proptest-arbitrary-interop.git", branch = "incorrect-format" } quickcheck = "1.0.3" quote = "1.0" spin = { version = "0.9.8", features = ["once", "lock_api", "rwlock"] } syn = { version = "2.0", features = ["full"] } xshell = "0.2.6" +hex = "0.4.3" +log = "0.4.22" +rand = "0.8.5" +reqwest = "0.12.5" +tokio = { version = "1.39.1", features = ["full"] } [workspace.package] version = "0.1.0" edition = "2021" publish = false +license = "Apache-2.0" [profile.test] # speed up test execution
diff --git a/common/build_scripts/Cargo.toml b/common/build_scripts/Cargo.toml index 8f9d217..35efdb8 100644 --- a/common/build_scripts/Cargo.toml +++ b/common/build_scripts/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true rust-version = "1.71.0" [dependencies]
diff --git a/common/cmd_runner/Cargo.toml b/common/cmd_runner/Cargo.toml index 435b29f..189d607 100644 --- a/common/cmd_runner/Cargo.toml +++ b/common/cmd_runner/Cargo.toml
@@ -3,15 +3,17 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [dependencies] anyhow = "1.0.64" -shell-escape = "0.1.5" -owo-colors = "3.5.0" -xshell = "0.2.6" -clap = { version = "4.5.4", features = ["derive"] } -file-header = "0.1.2" chrono = "0.4.37" -log = "0.4.21" +clap = { version = "4.5.13", features = ["derive"] } +file-header = "0.1.2" globset = "0.4.14" - +log = "0.4.21" +owo-colors = "3.5.0" +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.118" +shell-escape = "0.1.5" +xshell = "0.2.6"
diff --git a/common/cmd_runner/src/cargo_workspace.rs b/common/cmd_runner/src/cargo_workspace.rs index 2b6840e..9bc3f27 100644 --- a/common/cmd_runner/src/cargo_workspace.rs +++ b/common/cmd_runner/src/cargo_workspace.rs
@@ -40,9 +40,9 @@ #[derive(clap::Args, Debug, Clone, Default)] pub struct CargoOptions { #[arg(long, help = "whether to run cargo with --locked")] - locked: bool, + pub locked: bool, #[arg(long, help = "gather coverage metrics")] - coverage: bool, + pub coverage: bool, } impl CargoOptions {
diff --git a/common/cmd_runner/src/fuzzers.rs b/common/cmd_runner/src/fuzzers.rs new file mode 100644 index 0000000..2d0ca3b --- /dev/null +++ b/common/cmd_runner/src/fuzzers.rs
@@ -0,0 +1,114 @@ +// Copyright 2023 Google LLC +// +// Licensed 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 std::{ + collections::HashMap, + path::{self, PathBuf}, +}; + +use serde::Deserialize; +use xshell::{cmd, Shell}; + +/// Partial structure for parsing the output of `cargo metadata --format-version=1`. +/// +/// This doesn't contain all the fields in `cargo metadata`, just the ones we need. +#[derive(Deserialize)] +struct CargoMetadata { + packages: Vec<CargoMetadataPackage>, +} + +#[derive(Deserialize)] +struct CargoMetadataPackage { + manifest_path: String, + metadata: Option<HashMap<String, serde_json::Value>>, + targets: Vec<CargoMetadataPackageTarget>, +} + +#[derive(Deserialize)] +struct CargoMetadataPackageTarget { + name: String, + kind: Vec<String>, +} + +/// A `bin` target defined for fuzzing. See [`iter_fuzz_binaries`]. +#[derive(Debug)] +pub struct FuzzTarget { + pub fuzz_dir: PathBuf, + pub target_name: String, +} + +impl FuzzTarget { + /// Run the fuzz target using `cargo fuzz run`. + pub fn run(&self, sh: &xshell::Shell) -> xshell::Result<()> { + let FuzzTarget { + fuzz_dir, + target_name, + } = self; + cmd!( + sh, + "cargo +nightly fuzz run --fuzz-dir {fuzz_dir} {target_name} -- -runs=1000000 -max_total_time=30" + ) + .run() + } +} + +/// Create an iterator over all of the fuzz targets defined in the workspace at `root`. +/// +/// This iterator discovers targets using `cargo metadata`. It looks for crates with the metadata +/// `cargo-fuzz = true` in its Cargo.toml, and looks for `[[bin]]` targets in all of those crates. +pub fn iter_fuzz_binaries(root: &path::Path) -> anyhow::Result<impl Iterator<Item = FuzzTarget>> { + let sh = Shell::new()?; + sh.change_dir(root); + let cargo_metadata = cmd!(sh, "cargo metadata --no-deps --format-version=1").read()?; + let metadata_json: CargoMetadata = serde_json::from_str(&cargo_metadata)?; + let packages = metadata_json.packages.into_iter().filter_map(|package| { + let metadata_map = package.metadata.as_ref()?; + metadata_map + .get("cargo-fuzz")? + .as_bool()? + .then_some(package) + }); + Ok(packages.flat_map(|package| { + let fuzz_manifest = PathBuf::from(&package.manifest_path); + let fuzz_dir = fuzz_manifest + .parent() + .expect("Fuzz manifest should have a parent directory") + .to_owned(); + package + .targets + .into_iter() + .filter(|t| t.kind.contains(&String::from("bin"))) + .map(move |target| FuzzTarget { + fuzz_dir: fuzz_dir.clone(), + target_name: target.name.to_string(), + }) + })) +} + +/// Runs all of the fuzz targets defined within the workspace `root`. +pub fn run_workspace_fuzz_targets(root: &path::Path) -> anyhow::Result<()> { + log::info!("Running rust fuzzers"); + let sh = Shell::new()?; + sh.change_dir(root); + let fuzz_targets: Vec<_> = iter_fuzz_binaries(root)?.collect(); + log::info!( + "Fuzzing on {} targets. This may take up to {} seconds.", + fuzz_targets.len(), + 30 * fuzz_targets.len() + ); + for fuzz_target in fuzz_targets { + fuzz_target.run(&sh)?; + } + Ok(()) +}
diff --git a/common/cmd_runner/src/lib.rs b/common/cmd_runner/src/lib.rs index 8c45df8..24b4427 100644 --- a/common/cmd_runner/src/lib.rs +++ b/common/cmd_runner/src/lib.rs
@@ -17,6 +17,7 @@ use std::{collections, env, ffi, io, io::BufRead, path, process, thread}; pub mod cargo_workspace; +pub mod fuzzers; pub mod license_checker; pub fn run_cmd_shell(
diff --git a/common/deny.toml b/common/deny.toml index bd3f18b..ff83d5e 100644 --- a/common/deny.toml +++ b/common/deny.toml
@@ -71,6 +71,7 @@ allow = [ "MIT", "Apache-2.0", + "BSD-3-Clause", "Unicode-DFS-2016", "ISC", ] @@ -85,6 +86,10 @@ # Each entry is the crate and version constraint, and its specific allow # list + + # "Reciprocal" licensed crate pulled directly from crates.io without modifications + # Important: Update https://third-party-mirror.googlesource.com/option-ext/ if you update this version + { allow = ["MPL-2.0"], name = "option-ext", version = "0.2.0" }, ] # Some crates don't have (easily) machine readable licensing information, @@ -103,8 +108,8 @@ # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration #license-files = [ - # Each entry is a crate relative path, and the (opaque) hash of its contents - #{ path = "LICENSE", hash = 0xbd0eed23 } +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } #] [[licenses.clarify]] @@ -121,7 +126,7 @@ # published to private registries. # To see how to mark a crate as unpublished (to the official registry), # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. -ignore = true +ignore = false # One or more private registries that you might publish crates to, if a crate # is only published to private registries, and ignore is true, the crate will # not have its license(s) checked
diff --git a/common/derive_fuzztest/Cargo.toml b/common/derive_fuzztest/Cargo.toml deleted file mode 100644 index 6a22ce5..0000000 --- a/common/derive_fuzztest/Cargo.toml +++ /dev/null
@@ -1,24 +0,0 @@ -[package] -name = "derive_fuzztest" -version.workspace = true -edition.workspace = true -publish.workspace = true - -[dependencies] -arbitrary.workspace = true -derive_fuzztest_macro.workspace = true -proptest = { workspace = true, optional = true } -proptest-arbitrary-interop = { workspace = true, optional = true } -quickcheck = { workspace = true, optional = true } - -[features] -default = ["quickcheck"] -quickcheck = ["dep:quickcheck", "derive_fuzztest_macro/quickcheck"] -proptest = [ - "dep:proptest", - "dep:proptest-arbitrary-interop", - "derive_fuzztest_macro/proptest", -] - -[lints] -workspace = true
diff --git a/common/derive_fuzztest/fuzz/.gitignore b/common/derive_fuzztest/fuzz/.gitignore deleted file mode 100644 index b94a8f4..0000000 --- a/common/derive_fuzztest/fuzz/.gitignore +++ /dev/null
@@ -1 +0,0 @@ -/corpus \ No newline at end of file
diff --git a/common/derive_fuzztest/fuzz/Cargo.toml b/common/derive_fuzztest/fuzz/Cargo.toml deleted file mode 100644 index 1f493b6..0000000 --- a/common/derive_fuzztest/fuzz/Cargo.toml +++ /dev/null
@@ -1,16 +0,0 @@ -[package] -name = "derive_fuzz_example" -version.workspace = true -edition.workspace = true -publish.workspace = true - -[package.metadata] -cargo-fuzz = true - -[dependencies] -arbitrary.workspace = true -derive_fuzztest.workspace = true -quickcheck.workspace = true - -[target.'cfg(fuzzing)'.dependencies] -libfuzzer-sys.workspace = true
diff --git a/common/derive_fuzztest/fuzz/src/bin/arbitrary.rs b/common/derive_fuzztest/fuzz/src/bin/arbitrary.rs deleted file mode 100644 index 8f8d77b..0000000 --- a/common/derive_fuzztest/fuzz/src/bin/arbitrary.rs +++ /dev/null
@@ -1,32 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed 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. - -#![cfg_attr(fuzzing, no_main)] - -use arbitrary::{Arbitrary, Unstructured}; -use derive_fuzztest::fuzztest; - -#[derive(Debug, Clone)] -pub struct SmallU8(u8); - -impl<'a> Arbitrary<'a> for SmallU8 { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { - Ok(SmallU8(u.int_in_range(0..=127)?)) - } -} - -#[fuzztest] -pub fn test(a: SmallU8, b: SmallU8) { - let _ = a.0 + b.0; // Succeeds because our custom arbitrary impl only generates 0-127 so it never overflows -}
diff --git a/common/derive_fuzztest/src/lib.rs b/common/derive_fuzztest/src/lib.rs deleted file mode 100644 index c0dd11c..0000000 --- a/common/derive_fuzztest/src/lib.rs +++ /dev/null
@@ -1,141 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed 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. - -//! Derive macros that generates both a fuzz target for use with `cargo fuzz`, and a property test -//! (via `quickcheck` or `proptest`) for use with `cargo test`. -//! -//! The reason for having both is that property testing allows for quick iteration to make sure the -//! test works, and can be checked in presubmit CI, while fuzzing can test the input space more -//! exhaustively and run continuously. -//! -//! # Example -//! -//! ```no_run -//! #![cfg_attr(fuzzing, no_main)] -//! -//! #[derive_fuzztest::fuzztest] -//! fn transitive_ord(a: u32, b: u32, c: u32) { -//! if a >= b && b >= c { -//! assert!(a >= c); -//! } -//! if a <= b && b <= c { -//! assert!(a <= c); -//! } -//! } -//! -//! #[test] -//! fn additional_test_here() { -//! /* ... */ -//! } -//! ``` -//! -//! # Usage -//! -//! -//! Run the generated property tests -//! ```sh -//! cargo test -//! ``` -//! -//! Run continuous fuzzing -//! ```sh -//! cargo +nightly fuzz run <binary name> -//! ``` -//! -//! # Crate structure -//! -//! If you use `#[fuzz]` or `#[fuzztest]`, the fuzz target imposes the following requirements: -//! -//! * The target must be in a separate `[[bin]]` target that only contains a single fuzz target. -//! * The crate containing the bin target has `[package.metadata] cargo-fuzz = true` -//! * The bin target is annotated with `#![cfg_attr(fuzzing, no_main)]` -//! -//! The recommended structure for your crate `foo` is to put your tests under `foo/fuzz/src/bin`: -//! -//! ```text -//! foo -//! ├── fuzz -//! │ ├── src -//! │ │ └── bin -//! │ │ └── fuzz_target_1.rs -//! │ └── Cargo.toml -//! ├── src -//! │ └── [project source] -//! └── Cargo.toml -//! ``` -//! -//! This is different from the default structure generated by `cargo fuzz init` or `cargo fuzz add` -//! so that we can take advantage of [target -//! auto-discovery](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#target-auto-discovery). -//! If you prefer, the default structure generated by `cargo fuzz` can also work, but make sure you -//! remove `test = false` from the generated target in `Cargo.toml`. -//! -//! You will also need to declare a dependency on the `libfuzzer-sys` crate, but only if fuzzing is -//! requested: -//! -//! ```toml -//! [target.'cfg(fuzzing)'.dependencies] -//! libfuzzer-sys = "*" -//! ``` -//! -//! (The reason for this conditional dependency is that `libfuzzer-sys` injects a main function to -//! the resulting binary, and there will be linking failures if we link that in without defining a -//! corresponding `fuzz_target`.) -//! -//! # Features -//! -//! * `quickcheck` (default) — Enable generation of -//! [`quickcheck`](https://docs.rs/quickcheck/latest/quickcheck/) property tests. -//! * `proptest` — Enable generation of [`proptest`](https://docs.rs/proptest/latest/proptest/) -//! property tests. -//! -//! #### See also -//! * [Announcing Better Support for Fuzzing with Structured Inputs in -//! Rust](https://fitzgeraldnick.com/2020/01/16/better-support-for-fuzzing-structured-inputs-in-rust.html#how-is-all-this-different-from-quickcheck-and-proptest) -//! * [Bridging Fuzzing and Property -//! Testing](https://blog.yoshuawuyts.com/bridging-fuzzing-and-property-testing/) - -pub use derive_fuzztest_macro::{fuzz, fuzztest, proptest}; - -#[doc(hidden)] -pub mod reexport { - #[cfg(feature = "proptest")] - pub use proptest; - #[cfg(feature = "proptest")] - pub use proptest_arbitrary_interop; - #[cfg(feature = "quickcheck")] - pub use quickcheck; -} - -#[cfg(feature = "quickcheck")] -#[doc(hidden)] -pub mod arbitrary_bridge { - - /// Wrapper type that allows `arbitrary::Arbitrary` to be used as `quickcheck::Arbitrary` - #[derive(Debug, Clone)] - pub struct ArbitraryAdapter<T: for<'a> arbitrary::Arbitrary<'a>>( - pub Result<T, arbitrary::Error>, - ); - - impl<T> quickcheck::Arbitrary for ArbitraryAdapter<T> - where - T: for<'a> arbitrary::Arbitrary<'a> + Clone + 'static, - { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let bytes = Vec::<u8>::arbitrary(g); - let mut unstructured = arbitrary::Unstructured::new(&bytes); - Self(T::arbitrary(&mut unstructured)) - } - } -}
diff --git a/common/derive_fuzztest_macro/Cargo.toml b/common/derive_fuzztest_macro/Cargo.toml deleted file mode 100644 index 37b480a..0000000 --- a/common/derive_fuzztest_macro/Cargo.toml +++ /dev/null
@@ -1,23 +0,0 @@ -[package] -name = "derive_fuzztest_macro" -version.workspace = true -edition.workspace = true -publish.workspace = true - -[dependencies] -quote.workspace = true -proc-macro2.workspace = true -syn = { workspace = true, features = ["extra-traits"]} - -[dev-dependencies] -derive_fuzztest.workspace = true -pretty_assertions.workspace = true -prettyplease.workspace = true - -[features] -quickcheck = [] -proptest = [] - -[lib] -proc-macro = true -doc = false
diff --git a/common/derive_fuzztest_macro/src/lib.rs b/common/derive_fuzztest_macro/src/lib.rs deleted file mode 100644 index beb692a..0000000 --- a/common/derive_fuzztest_macro/src/lib.rs +++ /dev/null
@@ -1,463 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed 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. - -//! Internal crate for use by [`derive_fuzztest`](../derive_fuzztest/index.html). See the -//! documentation there for usage information. - -use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::{parse::Nothing, spanned::Spanned, ItemFn, Pat, PatType, Type}; - -/// Define a fuzz test. -/// -/// All input parameters of the given function must implement `arbitrary::Arbitrary`. -/// -/// This macro derives new items based on the given function. -/// 1. A `fuzz_target!` is generated that can be used with `cargo fuzz`. -/// 2. Property tests (`quickcheck` or `proptest`, based on which features are enabled) are -/// generated that can be tested using `cargo test`. -/// -/// See the crate documentation [`derive_fuzztest`](../derive_fuzztest/index.html) for details. -#[proc_macro_attribute] -pub fn fuzztest(attr: TokenStream, item: TokenStream) -> TokenStream { - fuzztest_impl(attr.into(), item.into()) - .unwrap_or_else(|e| e.into_compile_error()) - .into() -} - -fn fuzztest_impl(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> { - syn::parse2::<Nothing>(attr)?; - let func = syn::parse2::<ItemFn>(item)?; - let fn_def = FunctionDefinition::parse(func)?; - let original_fn = &fn_def.func; - let fuzz_target = derive_fuzz_target(&fn_def); - let proptest_target = proptest::derive_proptest(&fn_def); - let quickcheck_target = quickcheck::derive_quickcheck(&fn_def); - - Ok(quote! { - #[allow(unused)] - #original_fn - #fuzz_target - #proptest_target - #quickcheck_target - }) -} - -/// Define a fuzz target only without corresponding test. -/// -/// All input parameters of the given function must implement `arbitrary::Arbitrary`. -/// -/// This macro derives a `fuzz_target!` that can be used with `cargo fuzz`. If you wish to generate -/// property tests that can be used with `cargo test` as well, use [`fuzztest`][macro@fuzztest]. -/// -/// See the crate documentation [`derive_fuzztest`](../derive_fuzztest/index.html) for details. -#[proc_macro_attribute] -pub fn fuzz(attr: TokenStream, item: TokenStream) -> TokenStream { - fuzz_impl(attr.into(), item.into()) - .unwrap_or_else(|e| e.into_compile_error()) - .into() -} - -fn fuzz_impl(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> { - syn::parse2::<Nothing>(attr)?; - let func = syn::parse2::<ItemFn>(item)?; - let fn_def = FunctionDefinition::parse(func)?; - let original_fn = &fn_def.func; - let fuzz_target = derive_fuzz_target(&fn_def); - - Ok(quote! { - #[allow(unused)] - #original_fn - #fuzz_target - }) -} - -/// Define a property test. -/// -/// This is similar to using `quickcheck!` or `proptest::proptest!` directly. -/// -/// All input parameters of the given function must implement `arbitrary::Arbitrary`. -/// -/// Unlike [`fuzztest`][macro@fuzztest], this macro does not have to be placed in a `[[bin]]` target -/// and a single file can contain multiple of these tests. The generated tests can be run with -/// `cargo test` as usual. -#[proc_macro_attribute] -pub fn proptest(attr: TokenStream, item: TokenStream) -> TokenStream { - proptest_impl(attr.into(), item.into()) - .unwrap_or_else(|e| e.into_compile_error()) - .into() -} - -fn proptest_impl(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> { - syn::parse2::<Nothing>(attr)?; - let func = syn::parse2::<ItemFn>(item)?; - let fn_def = FunctionDefinition::parse(func)?; - let original_fn = &fn_def.func; - let proptest_target = proptest::derive_proptest(&fn_def); - - Ok(quote! { - #[allow(unused)] - #original_fn - #proptest_target - }) -} - -fn derive_fuzz_target(fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { - let FunctionDefinition { func, args, types } = fn_def; - let func_ident = &func.sig.ident; - quote! { - #[automatically_derived] - #[cfg(fuzzing)] - ::libfuzzer_sys::fuzz_target!(|args: ( #(#types),* )| { - let ( #(#args),* ) = args; // https://github.com/rust-fuzz/libfuzzer/issues/77 - #func_ident ( #(#args),* ) - }); - - #[cfg(not(any(fuzzing, rust_analyzer)))] - fn main() { - ::std::unreachable!("Run this target with `cargo fuzz` or `cargo test` instead"); - } - } -} - -#[cfg(any(feature = "quickcheck", test))] -mod quickcheck { - use crate::FunctionDefinition; - use quote::quote; - - pub(crate) fn derive_quickcheck(fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { - let FunctionDefinition { func, args, types } = fn_def; - let func_ident = &func.sig.ident; - let adapted_types: Vec<_> = types - .iter() - .map(|ty| quote! { ArbitraryAdapter<#ty> }) - .collect(); - let arg_pattern: Vec<_> = args - .iter() - .map(|arg| quote! { ArbitraryAdapter(::core::result::Result::Ok(#arg)) }) - .collect(); - let test_name = quote::format_ident!("quickcheck_{func_ident}"); - quote! { - #[automatically_derived] - #[test] - fn #test_name() { - use ::derive_fuzztest::reexport::quickcheck::TestResult; - use ::derive_fuzztest::arbitrary_bridge::ArbitraryAdapter; - - fn inner(args: (#(#adapted_types),*)) -> TestResult { - let (#(#arg_pattern),*) = args else { return TestResult::discard() }; - match ::std::panic::catch_unwind(move || { - #func_ident ( #(#args),* ); - }) { - ::core::result::Result::Ok(()) => TestResult::passed(), - ::core::result::Result::Err(e) => TestResult::error(::std::format!("{e:?}")), - } - } - - ::derive_fuzztest::reexport::quickcheck::QuickCheck::new().tests(1024) - .quickcheck(inner as fn(_) -> TestResult); - } - } - } -} - -#[cfg(not(any(feature = "quickcheck", test)))] -mod quickcheck { - use crate::FunctionDefinition; - - pub(crate) fn derive_quickcheck(_fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { - proc_macro2::TokenStream::default() - } -} - -#[cfg(any(feature = "proptest", test))] -mod proptest { - use crate::FunctionDefinition; - use quote::quote; - use syn::{Ident, Signature}; - - pub(crate) fn derive_proptest(fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { - let FunctionDefinition { func, args, types } = fn_def; - let func_attrs = &func.attrs; - let Signature { - constness, - asyncness, - unsafety, - abi, - fn_token, - ident, - generics, - paren_token: _, - inputs: _, - variadic: _, - output, - } = &func.sig; - let proptest_ident = Ident::new(&format!("proptest_{ident}"), ident.span()); - quote! { - #[automatically_derived] - #[cfg(test)] - mod #proptest_ident { - use super::*; - use ::derive_fuzztest::reexport::proptest; - use ::derive_fuzztest::reexport::proptest_arbitrary_interop::arb; - - proptest::proptest! { - #![proptest_config(proptest::prelude::ProptestConfig { - cases: 1024, - failure_persistence: Some(Box::new(proptest::test_runner::FileFailurePersistence::WithSource("regression"))), - ..Default::default() - })] - #[test] - #(#func_attrs)* - #constness #asyncness #unsafety #abi #fn_token #proptest_ident #generics ( args in arb::<(#(#types),*)>() ) #output { - let (#(#args),*) = args; - #ident ( #(#args),* ); - } - } - } - } - } -} - -#[cfg(not(any(feature = "proptest", test)))] -mod proptest { - use crate::FunctionDefinition; - - pub(crate) fn derive_proptest(_fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { - proc_macro2::TokenStream::default() - } -} - -/// Representation of a function definition annotated with one of the attribute macros in this -/// crate. -struct FunctionDefinition { - func: ItemFn, - args: Vec<Pat>, - types: Vec<Type>, -} - -impl FunctionDefinition { - pub fn parse(func: ItemFn) -> syn::Result<Self> { - let (args, types) = func - .sig - .inputs - .clone() - .into_iter() - .map(|arg| match arg { - syn::FnArg::Receiver(arg_receiver) => Err(syn::Error::new( - arg_receiver.span(), - "Receiver not supported", - )), - syn::FnArg::Typed(PatType { - attrs: _, - pat, - colon_token: _, - ty, - }) => Ok((*pat, *ty)), - }) - .try_fold((Vec::new(), Vec::new()), |(mut args, mut types), result| { - result.map(|(arg, type_)| { - args.push(arg); - types.push(type_); - (args, types) - }) - })?; - Ok(Self { func, args, types }) - } -} - -#[cfg(test)] -mod tests { - use crate::{fuzz_impl, fuzztest_impl, proptest_impl}; - use quote::quote; - use syn::parse_quote; - - /// Assert that a token stream for a `syn::File` is the same as expected. - /// - /// Usage is similar to `assert_eq!`: - /// ```no_run - /// assert_syn_file!( - /// macro_impl(quote! { - /// fn foobar() {} - /// }), - /// quote! { - /// fn macro_rewritten_foobar() {} - /// } - /// ); - /// ``` - macro_rules! assert_syn_file { - ($actual:expr, $expected:expr) => { - let actual = syn::parse2::<syn::File>($actual).unwrap(); - let expected: syn::File = $expected; - assert!( - actual == expected, - "{}", - pretty_assertions::StrComparison::new( - &prettyplease::unparse(&expected), - &prettyplease::unparse(&actual), - ) - ) - }; - } - - #[test] - fn test_fuzztest_expansion() { - assert_syn_file!( - fuzztest_impl( - quote! {}, - quote! { - fn foobar(input: &[u8]) { - panic!("I am just a test") - } - } - ) - .unwrap(), - parse_quote! { - #[allow(unused)] - fn foobar(input: &[u8]) { - panic!("I am just a test") - } - - #[automatically_derived] - #[cfg(fuzzing)] - ::libfuzzer_sys::fuzz_target!(|args: (&[u8])| { - let (input) = args; - foobar(input) - }); - - #[cfg(not(any(fuzzing, rust_analyzer)))] - fn main() { - ::std::unreachable!("Run this target with `cargo fuzz` or `cargo test` instead"); - } - - #[automatically_derived] - #[cfg(test)] - mod proptest_foobar { - use super::*; - use ::derive_fuzztest::reexport::proptest; - use ::derive_fuzztest::reexport::proptest_arbitrary_interop::arb; - proptest::proptest! { - #![proptest_config(proptest::prelude::ProptestConfig { - cases: 1024, - failure_persistence: Some(Box::new(proptest::test_runner::FileFailurePersistence::WithSource("regression"))), - ..Default::default() - })] - #[test] - fn proptest_foobar(args in arb::<(&[u8])>()) { - let (input) = args; - foobar(input); - } - } - } - - #[automatically_derived] - #[test] - fn quickcheck_foobar() { - use ::derive_fuzztest::reexport::quickcheck::TestResult; - use ::derive_fuzztest::arbitrary_bridge::ArbitraryAdapter; - - fn inner(args: (ArbitraryAdapter<&[u8]>)) -> TestResult { - let (ArbitraryAdapter(::core::result::Result::Ok(input))) = args else { - return TestResult::discard() - }; - match ::std::panic::catch_unwind(move || { - foobar(input); - }) { - ::core::result::Result::Ok(()) => TestResult::passed(), - ::core::result::Result::Err(e) => TestResult::error(::std::format!("{e:?}")), - } - } - ::derive_fuzztest::reexport::quickcheck::QuickCheck::new() - .tests(1024) - .quickcheck(inner as fn(_) -> TestResult); - } - } - ); - } - - #[test] - fn test_fuzz_expansion() { - assert_syn_file!( - fuzz_impl( - quote! {}, - quote! { - fn foobar(input: &[u8]) { - panic!("I am just a test") - } - } - ) - .unwrap(), - parse_quote! { - #[allow(unused)] - fn foobar(input: &[u8]) { - panic!("I am just a test") - } - - #[automatically_derived] - #[cfg(fuzzing)] - ::libfuzzer_sys::fuzz_target!(|args: (&[u8])| { - let (input) = args; - foobar(input) - }); - - #[cfg(not(any(fuzzing, rust_analyzer)))] - fn main() { - ::std::unreachable!("Run this target with `cargo fuzz` or `cargo test` instead"); - } - } - ); - } - - #[test] - fn test_proptest_expansion() { - assert_syn_file!( - proptest_impl( - quote! {}, - quote! { - fn foobar(input: &[u8]) { - panic!("I am just a test") - } - } - ) - .unwrap(), - parse_quote! { - #[allow(unused)] - fn foobar(input: &[u8]) { - panic!("I am just a test") - } - - #[automatically_derived] - #[cfg(test)] - mod proptest_foobar { - use super::*; - use ::derive_fuzztest::reexport::proptest; - use ::derive_fuzztest::reexport::proptest_arbitrary_interop::arb; - proptest::proptest! { - #![proptest_config(proptest::prelude::ProptestConfig { - cases: 1024, - failure_persistence: Some(Box::new(proptest::test_runner::FileFailurePersistence::WithSource("regression"))), - ..Default::default() - })] - #[test] - fn proptest_foobar(args in arb::<(&[u8])>()) { - let (input) = args; - foobar(input); - } - } - } - } - ); - } -}
diff --git a/common/handle_map/Cargo.toml b/common/handle_map/Cargo.toml index 13973d4..8db74f3 100644 --- a/common/handle_map/Cargo.toml +++ b/common/handle_map/Cargo.toml
@@ -3,16 +3,17 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true [dependencies] +lazy_static.workspace = true lock_adapter.workspace = true [dev-dependencies] criterion.workspace = true -lazy_static.workspace = true [[bench]] name = "benches"
diff --git a/common/handle_map/src/declare_handle_map.rs b/common/handle_map/src/declare_handle_map.rs index 89078fa..8710275 100644 --- a/common/handle_map/src/declare_handle_map.rs +++ b/common/handle_map/src/declare_handle_map.rs
@@ -42,9 +42,6 @@ /// function `sample` which will print "Hello World". /// /// ``` -/// #[macro_use]/// -/// extern crate lazy_static; -/// /// use core::ops::Deref; /// use handle_map::{declare_handle_map, HandleMapDimensions, HandleLike}; /// @@ -105,7 +102,7 @@ " which references values of type `", ::core::stringify!($wrapped_type), "`." )] pub mod $handle_module_name { - lazy_static! { + $crate::reexport::lazy_static::lazy_static! { static ref GLOBAL_HANDLE_MAP: $crate::HandleMap<$wrapped_type> = $crate::HandleMap::with_dimensions($map_dimension_provider); }
diff --git a/common/handle_map/src/lib.rs b/common/handle_map/src/lib.rs index 72bb78a..43eeb19 100644 --- a/common/handle_map/src/lib.rs +++ b/common/handle_map/src/lib.rs
@@ -29,6 +29,11 @@ use shard::{HandleMapShard, ShardAllocationError}; +#[doc(hidden)] +pub mod reexport { + pub use lazy_static; +} + /// An individual handle to be given out by a [`HandleMap`]. /// This representation is untyped, and just a wrapper /// around a handle-id, in contrast to implementors of `HandleLike`.
diff --git a/common/lock_adapter/Cargo.toml b/common/lock_adapter/Cargo.toml index 6e3174b..23234ad 100644 --- a/common/lock_adapter/Cargo.toml +++ b/common/lock_adapter/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/common/pourover/Cargo.toml b/common/pourover/Cargo.toml index eb03cdb..2ed5642 100644 --- a/common/pourover/Cargo.toml +++ b/common/pourover/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/common/pourover/src/exception.rs b/common/pourover/src/exception.rs new file mode 100644 index 0000000..24e7962 --- /dev/null +++ b/common/pourover/src/exception.rs
@@ -0,0 +1,245 @@ +// Copyright 2024 Google LLC +// +// Licensed 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. + +//! Utilities for handling errors and propagating them into exceptions in Java. + +use jni::{ + descriptors::Desc, + objects::{JClass, JThrowable}, + JNIEnv, +}; +use std::fmt::Debug; + +/// An error that can be thrown as a Java exception, or an error in the JNI layer. +/// +/// This allows using Rust's error propagation mechanism (the `?` operator) to surface both JNI +/// errors and Java throwables, and convert them to Java exceptions at the top level. At the top +/// level of a JNI function implementation, it can use [`ThrowableJniResultExt::unwrap_or_throw`] to +/// convert the error to Java exception, or terminate the JVM with a fatal error in the case of JNI +/// errors. +pub enum ThrowableJniError<'local> { + /// A java throwable object instance. The throwable can be received from the Java side, or + /// created using [`call_constructor`][crate::call_constructor]. + JavaThrowable(JThrowable<'local>), + /// A [`jni::errors::Exception`], which contains the class and the message to be able to create + /// the exception to be thrown. + JavaException(jni::errors::Exception), + /// An error from the [`jni`] crate. + JniError(jni::errors::Error), +} + +impl<'local> ThrowableJniError<'local> { + /// Throws the error as a Java exception. + /// + /// If the error is a `JavaThrowable` or a `JavaException`, it will be thrown on the given + /// `env`. If the error is a `JniError`, this will turn it into a [`JNIEnv::fatal_error`], + /// unless the error type is [`jni::errors::Error::JavaException`], in which case the error will + /// be ignored, allowing the already-thrown exception to remain in JVM. + /// + /// In typical usages, callers should return some default value immediately after calling this. + /// See the example in [`try_throwable`]. + pub fn throw_on_jvm(self, env: &mut JNIEnv<'_>) { + match self { + ThrowableJniError::JavaThrowable(throwable) => match env.throw(throwable) { + Ok(()) => {} + Err(jni::errors::Error::JavaException) => { + let _ = env.exception_describe(); + env.fatal_error("Throwing exception failed"); + } + Err(_) => env.fatal_error("Throwing exception failed"), + }, + ThrowableJniError::JavaException(exception) => match env.throw(exception) { + Ok(()) => {} + Err(jni::errors::Error::JavaException) => { + let _ = env.exception_describe(); + env.fatal_error("Throwing exception failed"); + } + Err(_) => env.fatal_error("Throwing exception failed"), + }, + ThrowableJniError::JniError(err) => { + match err { + jni::errors::Error::JavaException => { + // Ignore the exception, it's already pending in the JVM + } + _ => env.fatal_error(format!("{err}")), + } + } + } + } +} + +impl Debug for ThrowableJniError<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::JavaThrowable(_) => f.debug_tuple("JavaThrowable").finish(), + Self::JavaException(arg0) => f + .debug_struct("JavaException") + .field("class", &arg0.class) + .field("msg", &arg0.msg) + .finish(), + Self::JniError(arg0) => f.debug_tuple("JniError").field(arg0).finish(), + } + } +} + +impl<'local> From<JThrowable<'local>> for ThrowableJniError<'local> { + fn from(value: JThrowable<'local>) -> Self { + Self::JavaThrowable(value) + } +} + +impl From<jni::errors::Error> for ThrowableJniError<'_> { + fn from(value: jni::errors::Error) -> Self { + Self::JniError(value) + } +} + +impl From<jni::errors::Exception> for ThrowableJniError<'_> { + fn from(value: jni::errors::Exception) -> Self { + Self::JavaException(value) + } +} + +/// Extension trait for `Result<T, ThrowableJniError>`. +pub trait ThrowableJniResultExt<T> { + /// Returns the contained `Ok` value, or throw an exception on the JVM and return the default. + /// + /// Consumes the `self` argument then, if `Ok`, returns the contained value. Otherwise, if + /// `Err`, the error will be thrown on the JVM using [`ThrowableJniError::throw_on_jvm`] and a + /// default value will be returned. The value returned by `default` typically does not matter, + /// because while there is an exception pending on the JVM, the return value from your JNI + /// method is ignored. + fn unwrap_or_throw(self, env: &mut JNIEnv<'_>) -> T + where + T: Default, + Self: Sized, + { + self.unwrap_or_throw_with_default(env, Default::default) + } + + /// Returns the contained `Ok` value, or throw an exception on the JVM and return some default. + /// + /// Consumes the `self` argument then, if `Ok`, returns the contained value. Otherwise, if + /// `Err`, the error will be thrown on the JVM using [`ThrowableJniError::throw_on_jvm`] and the + /// value from `default` will be returned. The value returned by `default` typically does not + /// matter, because while there is an exception pending on the JVM, the return value from your + /// JNI method is ignored. + fn unwrap_or_throw_with_default(self, env: &mut JNIEnv<'_>, default: impl FnOnce() -> T) -> T; +} + +impl<'local, T> ThrowableJniResultExt<T> for Result<T, ThrowableJniError<'local>> { + fn unwrap_or_throw_with_default(self, env: &mut JNIEnv<'_>, default: impl FnOnce() -> T) -> T { + match self { + Ok(v) => v, + Err(e) => { + e.throw_on_jvm(env); + default() + } + } + } +} + +/// Creates a `NullPointerException` with a default error message. +pub fn null_pointer_exception() -> jni::errors::Exception { + jni::errors::Exception { + class: "java/lang/NullPointerException".into(), + msg: "Null pointer".into(), + } +} + +/// Creates a runtime exception with the given message. +pub fn runtime_exception(msg: impl ToString) -> jni::errors::Exception { + jni::errors::Exception { + class: "java/lang/RuntimeException".into(), + msg: msg.to_string(), + } +} + +/// Runs the given `func` and turns any [`ThrowableJniError`] into Java exceptions. +/// +/// A convenience function that takes a closure `func`, runs it immediately, and calls +/// [`ThrowableJniResultExt::unwrap_or_throw`] on the result. This allows the code inside the +/// closure to propagate [`ThrowableJniErrors`][ThrowableJniError] using the `?` operator. +/// +/// # Example +/// +/// ``` +/// use pourover::{jni_method, exception::{runtime_exception, try_throwable}}; +/// use jni::{JNIEnv, objects::{JByteArray, JClass}, sys::{jboolean, JNI_TRUE}}; +/// +/// #[jni_method(package = "com.example", class = "Foo")] +/// extern "system" fn nativeMaybeThrowException<'local>( +/// mut env: JNIEnv<'local>, +/// _cls: JClass<'local>, +/// value: JByteArray<'local>, +/// ) -> jboolean { +/// try_throwable(&mut env, |env| { +/// let value = env.convert_byte_array(value)?; +/// if value == b"Throw exception" { +/// Err(runtime_exception("Argument was \"throw exception\""))? +/// } +/// Ok(JNI_TRUE) +/// }) +/// } +/// ``` +pub fn try_throwable<'local, R: Default>( + env: &mut JNIEnv<'local>, + func: impl FnOnce(&mut JNIEnv<'local>) -> Result<R, ThrowableJniError<'local>>, +) -> R { + func(env).unwrap_or_throw(env) +} + +/// Extension trait on `jni::errors::Result<T>`. +pub trait JniResultExt<T> { + /// Extracts an exception of the given class from a [`jni::errors::Result`]. + /// + /// If `self` is `Err(JavaException)` and the pending exception on the JVM matches the given + /// `desc`, the exception will be cleared on the JVM and the associated throwable will be + /// returned. + /// + /// # Returns + /// A nested result object. If `self` contains an exception matching the given `desc`, this will + /// return `Ok(Err(throwable))`. If `self` is `Ok`, this will return `Ok(Ok(value))`. Otherwise + /// if there are other errors or non-matching exceptions, the error will be propagated as + /// `Err(jni_error)`. + fn extract_exception<'local>( + self, + env: &mut JNIEnv<'local>, + desc: impl Desc<'local, JClass<'local>>, + ) -> jni::errors::Result<Result<T, JThrowable<'local>>>; +} + +impl<T> JniResultExt<T> for jni::errors::Result<T> { + fn extract_exception<'local>( + self, + env: &mut JNIEnv<'local>, + desc: impl Desc<'local, JClass<'local>>, + ) -> jni::errors::Result<Result<T, JThrowable<'local>>> { + match self { + Ok(v) => Ok(Ok(v)), + Err(jni::errors::Error::JavaException) => { + let throwable = env.exception_occurred()?; + // Need to clear the exception ahead of time, otherwise `is_instance_of` will fail + env.exception_clear()?; + if env.is_instance_of(&throwable, desc)? { + Ok(Err(throwable)) + } else { + env.throw(throwable)?; + Err(jni::errors::Error::JavaException) + } + } + Err(e) => Err(e), + } + } +}
diff --git a/common/pourover/src/lib.rs b/common/pourover/src/lib.rs index ed861ea..8cadc6e 100644 --- a/common/pourover/src/lib.rs +++ b/common/pourover/src/lib.rs
@@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Utilties for JNI interactions. +//! Utilities for JNI interactions. pub use pourover_macro::{call_constructor, call_method, call_static_method, jni_method}; pub mod desc; +pub mod exception; mod conversions; pub use conversions::{ToSigned, ToUnsigned};
diff --git a/common/pourover/tests/common/foo_class.rs b/common/pourover/tests/common/foo_class.rs index 3ded762..a0a59e4 100644 --- a/common/pourover/tests/common/foo_class.rs +++ b/common/pourover/tests/common/foo_class.rs
@@ -22,8 +22,7 @@ //! [`jni::JNIEnv::define_class`]. use pourover::desc::*; -use std::error::Error; -use std::process::Command; +use std::{error::Error, process::Command}; pub const CLASS_DESC: &str = "com/example/Foo"; pub static FOO: ClassDesc = ClassDesc::new(CLASS_DESC);
diff --git a/common/pourover/tests/exception_integration.rs b/common/pourover/tests/exception_integration.rs new file mode 100644 index 0000000..129d2e6 --- /dev/null +++ b/common/pourover/tests/exception_integration.rs
@@ -0,0 +1,176 @@ +// Copyright 2024 Google LLC +// +// Licensed 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. + +#![allow(unsafe_code, clippy::unwrap_used, clippy::expect_used, clippy::panic)] + +use jni::{ + descriptors::Desc, + objects::{JObject, JString}, + sys::jint, + JNIEnv, JavaVM, +}; +use pourover::{ + desc::ClassDesc, + exception::{null_pointer_exception, runtime_exception, JniResultExt, ThrowableJniResultExt}, +}; +use std::error::Error; + +mod common; +use common::foo_class::*; + +#[pourover::jni_method(package = "com.example", class = "Foo")] +extern "system" fn nativeReturnsInt<'local>( + mut env: JNIEnv<'local>, + _this: JObject<'local>, + n: jint, +) -> jint { + (|| match n { + -1 => Err(runtime_exception("test runtime exception"))?, + -2 => Err(null_pointer_exception())?, + _ => Ok(n), + })() + .unwrap_or_throw(&mut env) +} + +#[pourover::jni_method( + package = "com.example", + class = "Foo", + panic_returns = JObject::null().into(), +)] +extern "system" fn nativeReturnsObject<'local>( + _env: JNIEnv<'local>, + _this: JObject<'local>, + _n: jint, +) -> JString<'local> { + unimplemented!() +} + +pub static RUNTIME_EXCEPTION_CLASS: ClassDesc = ClassDesc::new("java/lang/RuntimeException"); + +#[test] +fn can_call_native_method() -> Result<(), Box<dyn Error>> { + // Create the environment + let vm = JavaVM::new( + jni::InitArgsBuilder::new() + .version(jni::JNIVersion::V8) + .option("-Xcheck:jni") + .build()?, + )?; + let mut env = vm.attach_current_thread()?; + + // Load `Foo.class` + { + let foo_class = compile_foo()?; + let loaded_foo = env.define_class(CLASS_DESC, &JObject::null(), &foo_class)?; + env.delete_local_ref(loaded_foo)?; + } + + let obj_foo = { + let method_id = CONSTRUCTOR.lookup(&mut env)?; + let args = &[jni::sys::jvalue { i: 123 }]; + // Safety: `args` must match the constructor arg count and types. + unsafe { env.new_object_unchecked(CONSTRUCTOR.cls(), method_id, args) }? + }; + let obj_foo = env.auto_local(obj_foo); + + // TODO: It would be better if the JVM was able to find the method on its own, + // but, since we are using the invocation API to create the JVM, I haven't found a way to make + // it visible to the JVM. Normally the methods are loaded dynamically with + // `System.loadLibrary`, but in this case the symbols already exist in the executable, and I'm + // unsure why the JVM cannot find them. I have tried compiling with + // `-Zexport-executable-symbols` but that wasn't working for me. + env.register_native_methods( + &FOO, + &[ + jni::NativeMethod { + name: "nativeReturnsInt".into(), + sig: "(I)I".into(), + fn_ptr: crate::nativeReturnsInt as *mut std::ffi::c_void, + }, + jni::NativeMethod { + name: "nativeReturnsObject".into(), + sig: "(I)Ljava/lang/String;".into(), + fn_ptr: crate::nativeReturnsObject as *mut std::ffi::c_void, + }, + ], + )?; + + // Sub-test 1: Call nativeReturnsInt(99) returns the argument without exceptions + { + let result = + pourover::call_method!(&mut env, FOO, "nativeReturnsInt", "(I)I", &obj_foo, 99); + assert_eq!(99, result.unwrap()); + } + + // Sub-test 2: Call nativeReturnsInt(99) returns the argument without exceptions, so + // extract_exception just returns Ok + { + let result = + pourover::call_method!(&mut env, FOO, "nativeReturnsInt", "(I)I", &obj_foo, 99); + let extracted_result = result.extract_exception(&mut env, "java/lang/Throwable"); + let Ok(Ok(99)) = extracted_result else { + panic!("extracted_result should be Ok(Ok(99))") + }; + } + + // Sub-test 3: Call nativeReturnsInt(-1) throws RuntimeException("test runtime exception") + { + let result = + pourover::call_method!(&mut env, FOO, "nativeReturnsInt", "(I)I", &obj_foo, -1); + let throwable = result + .extract_exception(&mut env, "java/lang/RuntimeException") + .unwrap() + .unwrap_err(); + let message = pourover::call_method!( + &mut env, + RUNTIME_EXCEPTION_CLASS, + "getMessage", + "()Ljava/lang/String;", + &throwable + ) + .unwrap(); + let message = env.get_string(&message).map(String::from).unwrap(); + assert_eq!("test runtime exception", message); + } + + // Sub-test 4: Call nativeReturnsInt(-2) throws NullPointerException() + { + let result = + pourover::call_method!(&mut env, FOO, "nativeReturnsInt", "(I)I", &obj_foo, -2); + let _ = result + .extract_exception(&mut env, "java/lang/NullPointerException") + .unwrap() + .unwrap_err(); + } + + // Sub-test 5: Call nativeReturnsInt(-1) throws RuntimeException(). Calling + // extract_exception with NullPointerException should return Err(JavaException). + { + let result = + pourover::call_method!(&mut env, FOO, "nativeReturnsInt", "(I)I", &obj_foo, -1); + + let Err(jni::errors::Error::JavaException) = + result.extract_exception(&mut env, "java/lang/NullPointerException") + else { + panic!(concat!( + "Extracting NullPointerException from a result that contains RuntimeException ", + "should return Err(JavaException)" + )) + }; + // The exception should still be pending on the JVM + assert!(env.exception_check().unwrap()); + } + + Ok(()) +}
diff --git a/common/pourover/tests/jni_method_integration.rs b/common/pourover/tests/jni_method_integration.rs index 4bd6547..87cb80d 100644 --- a/common/pourover/tests/jni_method_integration.rs +++ b/common/pourover/tests/jni_method_integration.rs
@@ -21,8 +21,10 @@ sys::jint, JNIEnv, JavaVM, }; -use std::error::Error; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::{ + error::Error, + sync::atomic::{AtomicBool, Ordering}, +}; mod common; use common::foo_class::*;
diff --git a/common/pourover_macro/Cargo.toml b/common/pourover_macro/Cargo.toml index 0473dd4..13dc189 100644 --- a/common/pourover_macro/Cargo.toml +++ b/common/pourover_macro/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/common/pourover_macro/src/lib.rs b/common/pourover_macro/src/lib.rs index f46b7b1..9aeca11 100644 --- a/common/pourover_macro/src/lib.rs +++ b/common/pourover_macro/src/lib.rs
@@ -15,27 +15,29 @@ //! Proc macros for `pourover`. These macros are reexported by the `pourover` crate, so this crate //! is an implementation detail. +#[cfg(proc_macro)] use proc_macro::TokenStream; mod call_method; mod jni_method; mod type_parser; -/// Export a function as a JNI native method. This will attach a `#[export_name = "..."]` attribute that -/// is formatted with the given parameters. The provided `package`, `class`, and `method_name` will -/// be combined and formatted in according to the [JNI method name resolution rules][JNI naming]. +/// Export a function as a JNI native method. This will attach a `#[export_name = "..."]` attribute +/// that is formatted with the given parameters. The provided `package`, `class`, and `method_name` +/// will be combined and formatted in according to the [JNI method name resolution rules][JNI +/// naming]. /// -/// [JNI naming]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names +/// [JNI naming]: +/// https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names /// /// # Parameters /// - `package` (LitStr): the Java package for the class being implemented -/// - `class` (LitStr): the Java class being implemented. Use `Foo.Inner` syntax for inner -/// classes. +/// - `class` (LitStr): the Java class being implemented. Use `Foo.Inner` syntax for inner classes. /// - `method_name` (*optional* LitStr): the method's name in Java. The Rust function name will be -/// used if this parameter is not set. +/// used if this parameter is not set. /// - `panic_returns` (*optional* Expr): the value to return when a panic is encountered. This can -/// not access local variables. This may only be used with `panic=unwind` and will produce a -/// compile error otherwise. +/// not access local variables. This may only be used with `panic=unwind` and will produce a +/// compile error otherwise. /// /// When using `panic_returns` function arguments must be [`std::panic::UnwindSafe`]. See /// [`std::panic::catch_unwind`] for details. In practice this will not cause issues as JNI @@ -57,6 +59,7 @@ /// ``` /// /// This function will be exported with `#[export_name = "Java_my_package_Foo_getFoo"]`. +#[cfg(proc_macro)] #[proc_macro_attribute] pub fn jni_method(meta: TokenStream, item: TokenStream) -> TokenStream { use quote::ToTokens; @@ -75,14 +78,13 @@ /// - `cls` (Expr: `&'static ClassDesc`): The class containing the method. /// - `name` (Expr: `&'static str`): The name of the method. /// - `sig` (LitStr): The JNI type signature of the method. This needs to be a literal so that it -/// can be parsed by the macro to type-check args and return a correctly-typed value. +/// can be parsed by the macro to type-check args and return a correctly-typed value. /// - `this` (Expr: `&JObject`): The Java object receiving the method call. /// - `args` (Expr ...): A variable number of arguments to be passed to the method. /// /// # Caching -/// Each macro callsite will generate a `static` `MethodDesc` to cache the -/// method id. Due to this, **this macro call should be wrapped in function** instead of being called -/// multiple times. +/// Each macro callsite will generate a `static` `MethodDesc` to cache the method id. Due to this, +/// **this macro call should be wrapped in function** instead of being called multiple times. /// /// # Type-Safety /// The given type signature will be parsed and arguments will be type checked against it. The @@ -94,8 +96,8 @@ /// Similarly, the return type will be one of the types above. /// /// # Returns -/// The macro will evaluate to `jni::errors::Result<R>` where `R` is the return type parsed from -/// the type signature. +/// The macro will evaluate to `jni::errors::Result<R>` where `R` is the return type parsed from the +/// type signature. /// /// # Example /// Let's call `sayHello` from the following class. @@ -119,6 +121,7 @@ /// call_method!(env, &MY_CLASS, "sayHello", "(Ljava/lang/String;)I", my_obj, name) /// } /// ``` +#[cfg(proc_macro)] #[proc_macro] pub fn call_method(args: TokenStream) -> TokenStream { call_method::call_method(args.into()) @@ -134,13 +137,12 @@ /// - `cls` (Expr: `&'static ClassDesc`): The class containing the method. /// - `name` (Expr: `&'static str`): The name of the method. /// - `sig` (LitStr): The JNI type signature of the method. This needs to be a literal so that it -/// can be parsed by the macro to type-check args and return a correctly-typed value. +/// can be parsed by the macro to type-check args and return a correctly-typed value. /// - `args` (Expr ...): A variable number of arguments to be passed to the method. /// /// # Caching -/// Each macro callsite will generate a `static` `StaticMethodDesc` to cache the -/// method id. Due to this, **this macro call should be wrapped in function** instead of being called -/// multiple times. +/// Each macro callsite will generate a `static` `StaticMethodDesc` to cache the method id. Due to +/// this, **this macro call should be wrapped in function** instead of being called multiple times. /// /// # Type-Safety /// The given type signature will be parsed and arguments will be type checked against it. The @@ -152,8 +154,8 @@ /// Similarly, the return type will be one of the types above. /// /// # Returns -/// The macro will evaluate to `jni::errors::Result<R>` where `R` is the return type parsed from -/// the type signature. +/// The macro will evaluate to `jni::errors::Result<R>` where `R` is the return type parsed from the +/// type signature. /// /// # Example /// Let's call `sayHello` from the following class. @@ -176,6 +178,7 @@ /// call_static_method!(env, &MY_CLASS, "sayHello", "(Ljava/lang/String;)I", name) /// } /// ``` +#[cfg(proc_macro)] #[proc_macro] pub fn call_static_method(args: TokenStream) -> TokenStream { call_method::call_static_method(args.into()) @@ -189,14 +192,13 @@ /// `call_constructor!($env, $cls, $sig, $($args),*)` /// - `env` (Expr: `&mut jni::JNIEnv`): The JNI environment. /// - `cls` (Expr: `&'static ClassDesc`): The class to be constructed. -/// - `sig` (LitStr): The JNI type signature of the constructor. This needs to be a literal so that it -/// can be parsed by the macro to type-check args and return a correctly-typed value. +/// - `sig` (LitStr): The JNI type signature of the constructor. This needs to be a literal so that +/// it can be parsed by the macro to type-check args and return a correctly-typed value. /// - `args` (Expr ...): A variable number of arguments to be passed to the constructor. /// /// # Caching -/// Each macro callsite will generate a `static` `MethodDesc` to cache the -/// method id. Due to this, **this macro call should be wrapped in function** instead of being called -/// multiple times. +/// Each macro callsite will generate a `static` `MethodDesc` to cache the method id. Due to this, +/// **this macro call should be wrapped in function** instead of being called multiple times. /// /// # Type-Safety /// The given type signature will be parsed and arguments will be type checked against it. The @@ -229,6 +231,7 @@ /// call_constructor!(env, &MY_CLASS, "(Ljava/lang/String;)V", name) /// } /// ``` +#[cfg(proc_macro)] #[proc_macro] pub fn call_constructor(args: TokenStream) -> TokenStream { call_method::call_constructor(args.into())
diff --git a/common/pourover_macro/src/type_parser.rs b/common/pourover_macro/src/type_parser.rs index e6834e2..9a120d2 100644 --- a/common/pourover_macro/src/type_parser.rs +++ b/common/pourover_macro/src/type_parser.rs
@@ -90,7 +90,7 @@ } } -#[cfg(jni)] +#[cfg(feature = "jni")] impl<'a> From<JavaType<'a>> for jni::signature::ReturnType { fn from(ty: JavaType<'a>) -> Self { match ty { @@ -221,7 +221,7 @@ } } -#[cfg(jni)] +#[cfg(feature = "jni")] impl From<Primitive> for jni::signature::Primitive { fn from(p: Primitive) -> Self { match p { @@ -275,7 +275,7 @@ } } -#[cfg(jni)] +#[cfg(feature = "jni")] impl<'a> From<ReturnType<'a>> for jni::signature::ReturnType { fn from(ty: ReturnType<'a>) -> Self { match ty {
diff --git a/nearby/Cargo.lock b/nearby/Cargo.lock index 6eb2999..982aed1 100644 --- a/nearby/Cargo.lock +++ b/nearby/Cargo.lock
@@ -91,9 +91,9 @@ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -106,33 +106,33 @@ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -140,9 +140,9 @@ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arbitrary" @@ -204,9 +204,9 @@ [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake2" @@ -248,21 +248,21 @@ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", ] [[package]] -name = "build-scripts" +name = "build_scripts" version = "0.1.0" dependencies = [ "anyhow", "chrono", - "clap 4.5.4", + "clap 4.5.13", "cmd_runner", "crossbeam", "env_logger 0.10.2", @@ -295,9 +295,9 @@ [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cast" @@ -335,13 +335,12 @@ [[package]] name = "cc" -version = "1.0.97" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -367,7 +366,7 @@ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -424,9 +423,9 @@ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -434,26 +433,26 @@ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.0", + "clap_lex 0.7.2", "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.72", ] [[package]] @@ -467,9 +466,9 @@ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cmd_runner" @@ -477,20 +476,22 @@ dependencies = [ "anyhow", "chrono", - "clap 4.5.4", + "clap 4.5.13", "file-header", "globset", "log", "owo-colors", + "serde", + "serde_json", "shell-escape", "xshell", ] [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "combine" @@ -525,9 +526,9 @@ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -541,7 +542,7 @@ "anes", "cast", "ciborium", - "clap 4.5.4", + "clap 4.5.13", "criterion-plot", "is-terminal", "itertools 0.10.5", @@ -583,9 +584,9 @@ [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -620,9 +621,9 @@ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -754,16 +755,15 @@ [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest", "fiat-crypto", - "platforms", "rustc_version", "subtle", "zeroize", @@ -777,7 +777,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.72", ] [[package]] @@ -798,25 +798,30 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.72", ] [[package]] name = "derive_fuzztest" -version = "0.1.0" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a03c381eaef21fd453d46a2c9be9f7527e588f113fdc63c3c4a11d7da8b473" dependencies = [ "arbitrary", "derive_fuzztest_macro", + "libfuzzer-sys", "quickcheck", ] [[package]] name = "derive_fuzztest_macro" -version = "0.1.0" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "001301b8ebda15d520d9916f226711c0fa30be0bc8f055dd2379523bde1b4a32" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.72", ] [[package]] @@ -863,9 +868,9 @@ [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -911,9 +916,9 @@ [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -937,9 +942,9 @@ [[package]] name = "fiat-crypto" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "file-header" @@ -956,9 +961,9 @@ [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" dependencies = [ "crc32fast", "miniz_oxide", @@ -1040,6 +1045,7 @@ name = "handle_map" version = "0.1.0" dependencies = [ + "lazy_static", "lock_adapter", ] @@ -1191,9 +1197,9 @@ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -1206,9 +1212,9 @@ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -1243,9 +1249,9 @@ [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -1261,11 +1267,11 @@ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] @@ -1276,7 +1282,7 @@ "anyhow", "base64", "blake2", - "clap 4.5.4", + "clap 4.5.13", "criterion", "crypto_provider", "crypto_provider_default", @@ -1357,7 +1363,7 @@ "ldt_np_adv", "np_hkdf", "rand", - "spin 0.9.8", + "spin", ] [[package]] @@ -1382,9 +1388,9 @@ [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libfuzzer-sys" @@ -1399,9 +1405,9 @@ [[package]] name = "license" -version = "3.3.1" +version = "3.4.0+3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bba2f02ee1d13cd4bea565658939cd851d70e391f34f7c27b45b2077df3a2e4" +checksum = "a7da1e0d845faf299a9fe5f201a918a0dc0d5fc22c7b9580a6a23fed3a912b37" dependencies = [ "reword", "serde", @@ -1410,15 +1416,15 @@ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_adapter" version = "0.1.0" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -1433,15 +1439,15 @@ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -1451,9 +1457,9 @@ [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -1478,6 +1484,7 @@ "crypto_provider", "crypto_provider_default", "hex", + "itertools 0.13.0", "lazy_static", "ldt", "ldt_np_adv", @@ -1509,6 +1516,18 @@ ] [[package]] +name = "np_adv_fuzz" +version = "0.1.0" +dependencies = [ + "arbitrary", + "crypto_provider", + "crypto_provider_default", + "derive_fuzztest", + "libfuzzer-sys", + "np_adv", +] + +[[package]] name = "np_c_ffi" version = "0.1.0" dependencies = [ @@ -1567,6 +1586,7 @@ name = "np_java_ffi" version = "0.1.0" dependencies = [ + "array_view", "crypto_provider_default", "handle_map", "jni", @@ -1578,9 +1598,9 @@ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -1612,9 +1632,9 @@ [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "opaque-debug" @@ -1655,16 +1675,10 @@ ] [[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - -[[package]] name = "plotters" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ "num-traits", "plotters-backend", @@ -1675,15 +1689,15 @@ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" dependencies = [ "plotters-backend", ] @@ -1715,14 +1729,17 @@ "nom", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.72", ] [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "primeorder" @@ -1735,9 +1752,9 @@ [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1884,9 +1901,9 @@ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -1896,9 +1913,9 @@ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -1907,9 +1924,9 @@ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "relative-path" @@ -1949,7 +1966,7 @@ "regex", "relative-path", "rustc_version", - "syn 2.0.61", + "syn 2.0.72", "unicode-ident", ] @@ -1962,7 +1979,7 @@ "quote", "rand", "rustc_version", - "syn 2.0.61", + "syn 2.0.72", ] [[package]] @@ -1980,7 +1997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1989,9 +2006,9 @@ [[package]] name = "rustversion" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" @@ -2035,31 +2052,32 @@ [[package]] name = "serde" -version = "1.0.200" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -2099,12 +2117,6 @@ [[package]] name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" @@ -2150,14 +2162,14 @@ "proc-macro2", "quote", "rustversion", - "syn 2.0.61", + "syn 2.0.72", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2172,9 +2184,9 @@ [[package]] name = "syn" -version = "2.0.61" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -2183,14 +2195,15 @@ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2207,7 +2220,7 @@ version = "0.1.0" dependencies = [ "hex", - "itertools 0.12.1", + "itertools 0.13.0", "serde_json", ] @@ -2227,22 +2240,22 @@ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.72", ] [[package]] @@ -2257,9 +2270,9 @@ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" [[package]] name = "toml" @@ -2359,7 +2372,7 @@ name = "ukey2_shell" version = "0.1.0" dependencies = [ - "clap 4.5.4", + "clap 4.5.13", "crypto_provider_rustcrypto", "ukey2_connections", ] @@ -2388,15 +2401,15 @@ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -2435,7 +2448,7 @@ "once_cell", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -2457,7 +2470,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2508,11 +2521,11 @@ [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2527,7 +2540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2545,7 +2558,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2565,18 +2587,18 @@ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2587,9 +2609,9 @@ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2599,9 +2621,9 @@ [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2611,15 +2633,15 @@ [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2629,9 +2651,9 @@ [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2641,9 +2663,9 @@ [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2653,9 +2675,9 @@ [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2665,9 +2687,9 @@ [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wycheproof" @@ -2751,7 +2773,28 @@ ] [[package]] -name = "zeroize" -version = "1.7.0" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
diff --git a/nearby/Cargo.toml b/nearby/Cargo.toml index f69bcf7..11aecd8 100644 --- a/nearby/Cargo.toml +++ b/nearby/Cargo.toml
@@ -1,5 +1,6 @@ [workspace] members = [ + "build_scripts", "connections/ukey2/ukey2", "connections/ukey2/ukey2_connections", "connections/ukey2/ukey2_connections/fuzz", @@ -22,6 +23,7 @@ "presence/ldt_np_jni", "presence/ldt_tbc", "presence/np_adv", + "presence/np_adv/fuzz", "presence/np_adv_dynamic", "presence/np_c_ffi", "presence/np_ed25519", @@ -35,11 +37,13 @@ "presence/xts_aes", "presence/xts_aes/fuzz", ] +default-members = ["build_scripts"] # TODO: remove boringssl once we figure out a better plan for integrating the build system exclude = [ "crypto/crypto_provider_boringssl", ] +resolver = "2" [workspace.lints.rust] missing_docs = "deny" @@ -50,6 +54,7 @@ unused_extern_crates = "deny" unused_import_braces = "deny" unused_results = "deny" +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)', 'cfg(blaze)'] } [workspace.lints.clippy] expect_used = "deny" @@ -80,14 +85,12 @@ np_adv = { path = "presence/np_adv" } np_adv_dynamic = { path = "presence/np_adv_dynamic" } np_ed25519 = { path = "presence/np_ed25519" } -np_ffi_core = { path = "presence/np_ffi_core", default-features=false } +np_ffi_core = { path = "presence/np_ffi_core", default-features = false } np_java_ffi = { path = "presence/np_java_ffi" } sink = { path = "presence/sink" } test_vector_hkdf = { path = "presence/test_vector_hkdf" } # from utils workspace -derive_fuzztest = { path = "../common/derive_fuzztest" } -derive_fuzztest_macro = { path = "../common/derive_fuzztest_macro" } handle_map = { path = "../common/handle_map" } lock_adapter = { path = "../common/lock_adapter" } pourover = { path = "../common/pourover" } @@ -129,7 +132,7 @@ log = "0.4.20" env_logger = "0.10.1" criterion = { version = "0.5.1", features = ["html_reports"] } -clap = { version = "4.4.11", features = ["derive"] } +clap = { version = "4.5.13", features = ["derive"] } lazy_static = { version = "1.4.0", features = ["spin_no_std"] } hex-literal = "0.4.1" cfg-if = "1.0.0" @@ -154,16 +157,19 @@ syn = { version = "2.0", features = ["full"] } proc-macro2 = "1.0" quote = "1.0" -itertools = "0.12.1" +itertools = { version = "0.13.0", default-features = false } quickcheck = "1.0.3" proptest = "1.4.0" proptest-arbitrary-interop = { git = "https://github.com/brson/proptest-arbitrary-interop.git", branch = "incorrect-format" } libfuzzer-sys = "0.4.7" +derive_fuzztest = "0.1.1" +derive_fuzztest_macro = "0.1.1" [workspace.package] version = "0.1.0" edition = "2021" publish = false +license = "Apache-2.0" [profile.test] # speed up test execution @@ -183,33 +189,3 @@ # z optimizes for size opt-level = "z" strip = true - -[package] -name = "build-scripts" -version.workspace = true -edition.workspace = true -publish.workspace = true -rust-version = "1.71.0" - -[dependencies] -clap.workspace = true -cmd_runner = { path = "../common/cmd_runner" } -anyhow.workspace = true -shell-escape = "0.1.5" -owo-colors.workspace = true -semver = "1.0.17" -walkdir = "2.3.3" -globset = "0.4.10" -glob = "0.3.1" -crossbeam = "0.8.2" -chrono.workspace = true -thiserror.workspace = true -log.workspace = true -env_logger.workspace = true -file-header = "0.1.2" -serde_json = { workspace = true, features = ["std"] } -regex = "1.10.2" -xshell = "0.2.6" - -[dev-dependencies] -tempfile.workspace = true
diff --git a/nearby/build_scripts/Cargo.toml b/nearby/build_scripts/Cargo.toml new file mode 100644 index 0000000..5349e9c --- /dev/null +++ b/nearby/build_scripts/Cargo.toml
@@ -0,0 +1,30 @@ +[package] +name = "build_scripts" +version.workspace = true +edition.workspace = true +publish.workspace = true +license.workspace = true +rust-version = "1.71.0" + +[dependencies] +clap.workspace = true +cmd_runner = { path = "../../common/cmd_runner" } +anyhow.workspace = true +shell-escape = "0.1.5" +owo-colors.workspace = true +semver = "1.0.17" +walkdir = "2.3.3" +globset = "0.4.10" +glob = "0.3.1" +crossbeam = "0.8.2" +chrono.workspace = true +thiserror.workspace = true +log.workspace = true +env_logger.workspace = true +file-header = "0.1.2" +serde_json = { workspace = true, features = ["std"] } +regex = "1.10.2" +xshell = "0.2.6" + +[dev-dependencies] +tempfile.workspace = true
diff --git a/nearby/src/coverage.rs b/nearby/build_scripts/src/coverage.rs similarity index 100% rename from nearby/src/coverage.rs rename to nearby/build_scripts/src/coverage.rs
diff --git a/nearby/src/crypto_ffi.rs b/nearby/build_scripts/src/crypto_ffi.rs similarity index 100% rename from nearby/src/crypto_ffi.rs rename to nearby/build_scripts/src/crypto_ffi.rs
diff --git a/nearby/src/ffi.rs b/nearby/build_scripts/src/ffi.rs similarity index 100% rename from nearby/src/ffi.rs rename to nearby/build_scripts/src/ffi.rs
diff --git a/nearby/build_scripts/src/fuzzers.rs b/nearby/build_scripts/src/fuzzers.rs new file mode 100644 index 0000000..c2caf29 --- /dev/null +++ b/nearby/build_scripts/src/fuzzers.rs
@@ -0,0 +1,56 @@ +// Copyright 2023 Google LLC +// +// Licensed 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 cmd_runner::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr}; +use std::{fs, path}; + +pub(crate) fn run_rust_fuzzers(root: &path::Path) -> anyhow::Result<()> { + cmd_runner::fuzzers::run_workspace_fuzz_targets(root)?; + run_cmd_shell_with_color::<YellowStderr>( + &root.join("crypto/crypto_provider_test"), + concat!( + "cargo +nightly fuzz run fuzz_p256 --features=boringssl --no-default-features ", + "-- -runs=1000000 -max_total_time=30" + ), + )?; + + Ok(()) +} + +// Runs the fuzztest fuzzers as short lived unit tests, compatible with gtest +pub(crate) fn build_fuzztest_unit_tests(root: &path::Path) -> anyhow::Result<()> { + log::info!("Checking fuzztest targets in unit test mode"); + run_cmd_shell(root, "cargo build -p np_c_ffi --release")?; + run_cmd_shell(root, "cargo build -p ldt_np_adv_ffi --release")?; + let build_dir = root.join("cmake-build"); + fs::create_dir_all(&build_dir)?; + run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake -G Ninja -DENABLE_FUZZ=true ..")?; + + for target in ["deserialization_fuzzer", "ldt_fuzzer"] { + run_cmd_shell_with_color::<YellowStderr>( + &build_dir, + format!("cmake --build . --target {}", target), + )?; + } + + run_cmd_shell_with_color::<YellowStderr>( + &build_dir.join("presence/np_cpp_ffi/fuzz/"), + "ctest", + )?; + run_cmd_shell_with_color::<YellowStderr>( + &build_dir.join("presence/ldt_np_adv_ffi/c/fuzz/"), + "ctest", + )?; + Ok(()) +}
diff --git a/nearby/src/jni.rs b/nearby/build_scripts/src/jni.rs similarity index 91% rename from nearby/src/jni.rs rename to nearby/build_scripts/src/jni.rs index 4ef3590..ddea7d2 100644 --- a/nearby/src/jni.rs +++ b/nearby/build_scripts/src/jni.rs
@@ -30,7 +30,10 @@ } pub fn run_np_java_ffi_tests(root: &path::Path) -> anyhow::Result<()> { - run_cmd_shell(root, "cargo build -p np_java_ffi -F crypto_provider_default/rustcrypto")?; + run_cmd_shell( + root, + "cargo build -p np_java_ffi -F crypto_provider_default/rustcrypto -F testing", + )?; let np_java_path = root.to_path_buf().join("presence/np_java_ffi"); run_cmd_shell(&np_java_path, "./gradlew :test --info --rerun")?; Ok(())
diff --git a/nearby/src/license.rs b/nearby/build_scripts/src/license.rs similarity index 100% rename from nearby/src/license.rs rename to nearby/build_scripts/src/license.rs
diff --git a/nearby/src/main.rs b/nearby/build_scripts/src/main.rs similarity index 91% rename from nearby/src/main.rs rename to nearby/build_scripts/src/main.rs index 6fe4263..63ab0e9 100644 --- a/nearby/src/main.rs +++ b/nearby/build_scripts/src/main.rs
@@ -19,6 +19,7 @@ use cmd_runner::{license_checker::LicenseSubcommand, run_cmd, run_cmd_shell, YellowStderr}; use env_logger::Env; use license::LICENSE_CHECKER; +use log::info; use std::{env, ffi::OsString, path}; mod crypto_ffi; @@ -31,13 +32,15 @@ env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let cli: Cli = Cli::parse(); - let root_dir = path::PathBuf::from( + let build_script_dir = path::PathBuf::from( env::var("CARGO_MANIFEST_DIR").expect("Must be run via Cargo to establish root directory"), ); + let root_dir = + build_script_dir.parent().expect("build_scripts crate directory should have a parent"); match cli.subcommand { Subcommand::RunDefaultChecks(ref check_options) => { - run_default_checks(&root_dir, check_options)?; + run_default_checks(root_dir, check_options)?; print!(concat!( "Congratulations, the default checks passed. Since you like quality, here are\n", "some more checks you may like:\n", @@ -45,33 +48,31 @@ " cargo run -- check-stack-usage\n", )); } - Subcommand::VerifyCi { ref check_options } => verify_ci(&root_dir, check_options)?, - Subcommand::RunAllChecks { ref check_options } => run_all_checks(&root_dir, check_options)?, - Subcommand::CleanEverything => clean_everything(&root_dir)?, - Subcommand::CheckFormat(ref options) => check_format(&root_dir, options)?, - Subcommand::CheckWorkspace(ref options) => check_workspace(&root_dir, options)?, - Subcommand::CheckAllFfi(ref options) => ffi::check_all_ffi(&root_dir, options, false)?, - Subcommand::BazelBuild => bazel_build(&root_dir)?, - Subcommand::BuildBoringssl => crypto_ffi::build_boringssl(&root_dir)?, - Subcommand::CheckBoringssl(ref options) => crypto_ffi::check_boringssl(&root_dir, options)?, + Subcommand::VerifyCi { ref check_options } => verify_ci(root_dir, check_options)?, + Subcommand::RunAllChecks { ref check_options } => run_all_checks(root_dir, check_options)?, + Subcommand::CleanEverything => clean_everything(root_dir)?, + Subcommand::CheckFormat(ref options) => check_format(root_dir, options)?, + Subcommand::CheckWorkspace(ref options) => check_workspace(root_dir, options)?, + Subcommand::CheckAllFfi(ref options) => ffi::check_all_ffi(root_dir, options, false)?, + Subcommand::BazelBuild => bazel_build(root_dir)?, + Subcommand::BuildBoringssl => crypto_ffi::build_boringssl(root_dir)?, + Subcommand::CheckBoringssl(ref options) => crypto_ffi::check_boringssl(root_dir, options)?, Subcommand::CheckBoringsslAtLatest(ref options) => { - crypto_ffi::check_boringssl_at_head(&root_dir, options)? + crypto_ffi::check_boringssl_at_head(root_dir, options)? } - Subcommand::RunRustFuzzers => fuzzers::run_rust_fuzzers(&root_dir)?, - Subcommand::CheckFuzztest => fuzzers::build_fuzztest_unit_tests(&root_dir)?, + Subcommand::RunRustFuzzers => fuzzers::run_rust_fuzzers(root_dir)?, + Subcommand::CheckFuzztest => fuzzers::build_fuzztest_unit_tests(root_dir)?, Subcommand::License(license_subcommand) => { - license_subcommand.run(&LICENSE_CHECKER, &root_dir)? + license_subcommand.run(&LICENSE_CHECKER, root_dir)? } - Subcommand::CheckUkey2Ffi(ref options) => { - ffi::check_ukey2_cmake(&root_dir, options, false)? - } - Subcommand::RunUkey2JniTests => jni::run_ukey2_jni_tests(&root_dir)?, - Subcommand::RunNpJavaFfiTests => jni::run_np_java_ffi_tests(&root_dir)?, - Subcommand::CheckLdtCmake(ref options) => ffi::check_ldt_cmake(&root_dir, options, false)?, + Subcommand::CheckUkey2Ffi(ref options) => ffi::check_ukey2_cmake(root_dir, options, false)?, + Subcommand::RunUkey2JniTests => jni::run_ukey2_jni_tests(root_dir)?, + Subcommand::RunNpJavaFfiTests => jni::run_np_java_ffi_tests(root_dir)?, + Subcommand::CheckLdtCmake(ref options) => ffi::check_ldt_cmake(root_dir, options, false)?, Subcommand::CheckNpFfiCmake(ref options) => { - ffi::check_np_ffi_cmake(&root_dir, options, false)? + ffi::check_np_ffi_cmake(root_dir, options, false)? } - Subcommand::RunLdtKotlinTests => jni::run_ldt_kotlin_tests(&root_dir)?, + Subcommand::RunLdtKotlinTests => jni::run_ldt_kotlin_tests(root_dir)?, } Ok(()) @@ -133,7 +134,7 @@ } pub fn check_workspace(root: &path::Path, options: &CheckOptions) -> anyhow::Result<()> { - log::info!("Running cargo checks on workspace"); + info!("Running cargo checks on workspace"); // ensure formatting is correct (Check for it first because it is fast compared to running tests) check_format(root, &options.formatter_options)?;
diff --git a/common/derive_fuzztest/fuzz/src/bin/integer_add.rs b/nearby/build_scripts/src/no_std.rs similarity index 70% copy from common/derive_fuzztest/fuzz/src/bin/integer_add.rs copy to nearby/build_scripts/src/no_std.rs index 38b8172..6258260 100644 --- a/common/derive_fuzztest/fuzz/src/bin/integer_add.rs +++ b/nearby/build_scripts/src/no_std.rs
@@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,12 +12,3 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg_attr(fuzzing, no_main)] - -use derive_fuzztest::fuzztest; - -#[fuzztest] -pub fn test(a: u8, b: u8) { - let _ = a.checked_add(b); - // a + b; // This fails because a + b can overflow. -}
diff --git a/common/derive_fuzztest/fuzz/src/bin/integer_add.rs b/nearby/build_scripts/src/tools.rs similarity index 70% copy from common/derive_fuzztest/fuzz/src/bin/integer_add.rs copy to nearby/build_scripts/src/tools.rs index 38b8172..6258260 100644 --- a/common/derive_fuzztest/fuzz/src/bin/integer_add.rs +++ b/nearby/build_scripts/src/tools.rs
@@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,12 +12,3 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg_attr(fuzzing, no_main)] - -use derive_fuzztest::fuzztest; - -#[fuzztest] -pub fn test(a: u8, b: u8) { - let _ = a.checked_add(b); - // a + b; // This fails because a + b can overflow. -}
diff --git a/nearby/connections/.clang-format b/nearby/connections/.clang-format new file mode 100644 index 0000000..8217234 --- /dev/null +++ b/nearby/connections/.clang-format
@@ -0,0 +1,17 @@ +# Copyright 2024 Google LLC +# +# Licensed 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. + +Language: Cpp +BasedOnStyle: Google +QualifierAlignment: Left \ No newline at end of file
diff --git a/nearby/connections/ukey2/ukey2/Cargo.toml b/nearby/connections/ukey2/ukey2/Cargo.toml index 35de144..900a5a0 100644 --- a/nearby/connections/ukey2/ukey2/Cargo.toml +++ b/nearby/connections/ukey2/ukey2/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml b/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml index 1034359..328400b 100644 --- a/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [dependencies] ukey2_connections = { path = "../ukey2_connections" } @@ -25,4 +26,4 @@ [lib] # Static lib is a bit large, resulting in quite a large test executable. # This will be also shipped as a dynamic lib in most environments (I think) so good to replicate those conditions. -crate_type = ["cdylib"] +crate-type = ["cdylib"]
diff --git a/nearby/connections/ukey2/ukey2_connections/Cargo.toml b/nearby/connections/ukey2/ukey2_connections/Cargo.toml index 5c9622b..ff2ee44 100644 --- a/nearby/connections/ukey2/ukey2_connections/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_connections/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true @@ -15,11 +16,11 @@ [dependencies] ukey2_rs = { path = "../ukey2" } -crypto_provider.workspace = true +crypto_provider = { workspace = true, features = ["alloc"] } rand = { workspace = true, features = ["std", "std_rng"] } ukey2_proto.workspace = true nom = { version = "7.1.3", features = ["alloc"] } -bytes = "1.5.0" +bytes = "1.7.1" criterion.workspace = true [dev-dependencies]
diff --git a/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.toml b/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.toml index 2c94ebb..6ba66f5 100644 --- a/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.toml
@@ -2,6 +2,7 @@ name = "ukey2_connections-fuzz" version.workspace = true publish.workspace = true +license.workspace = true edition.workspace = true [package.metadata] @@ -18,6 +19,9 @@ [target.'cfg(fuzzing)'.dependencies] libfuzzer-sys.workspace = true +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + [[bin]] name = "fuzz_connection" path = "fuzz_targets/fuzz_connection.rs"
diff --git a/nearby/connections/ukey2/ukey2_jni/Cargo.toml b/nearby/connections/ukey2/ukey2_jni/Cargo.toml index bd615f5..f66ddb7 100644 --- a/nearby/connections/ukey2/ukey2_jni/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_jni/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true @@ -28,4 +29,4 @@ std = ["lock_adapter/std"] [lib] -crate_type = ["cdylib"] \ No newline at end of file +crate-type = ["cdylib"]
diff --git a/nearby/connections/ukey2/ukey2_proto/Cargo.toml b/nearby/connections/ukey2/ukey2_proto/Cargo.toml index c94d5b8..e7292da 100644 --- a/nearby/connections/ukey2/ukey2_proto/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_proto/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]
diff --git a/nearby/connections/ukey2/ukey2_shell/Cargo.toml b/nearby/connections/ukey2/ukey2_shell/Cargo.toml index c5c84ea..e81c5dd 100644 --- a/nearby/connections/ukey2/ukey2_shell/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_shell/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/crypto/crypto_provider/Cargo.toml b/nearby/crypto/crypto_provider/Cargo.toml index bafdfcc..3862cfd 100644 --- a/nearby/crypto/crypto_provider/Cargo.toml +++ b/nearby/crypto/crypto_provider/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/crypto/crypto_provider_boringssl/Cargo.lock b/nearby/crypto/crypto_provider_boringssl/Cargo.lock index 356a65d..cf82847 100644 --- a/nearby/crypto/crypto_provider_boringssl/Cargo.lock +++ b/nearby/crypto/crypto_provider_boringssl/Cargo.lock
@@ -102,9 +102,9 @@ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ]
diff --git a/nearby/crypto/crypto_provider_default/Cargo.toml b/nearby/crypto/crypto_provider_default/Cargo.toml index d79739b..c67bad2 100644 --- a/nearby/crypto/crypto_provider_default/Cargo.toml +++ b/nearby/crypto/crypto_provider_default/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml index 14e8ad9..c26c7db 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml +++ b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/crypto/crypto_provider_stubs/Cargo.toml b/nearby/crypto/crypto_provider_stubs/Cargo.toml index 4e8bdec..5808d6a 100644 --- a/nearby/crypto/crypto_provider_stubs/Cargo.toml +++ b/nearby/crypto/crypto_provider_stubs/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [dependencies] crypto_provider = {workspace = true, features = ["std", "alloc"] }
diff --git a/nearby/crypto/crypto_provider_test/Cargo.toml b/nearby/crypto/crypto_provider_test/Cargo.toml index 4064330..1191aae 100644 --- a/nearby/crypto/crypto_provider_test/Cargo.toml +++ b/nearby/crypto/crypto_provider_test/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [dependencies] crypto_provider = { workspace = true, features = ["raw_private_key_permit", "test_vectors", "alloc"] }
diff --git a/nearby/crypto/crypto_provider_test/fuzz/Cargo.toml b/nearby/crypto/crypto_provider_test/fuzz/Cargo.toml index 10c3e5c..d8dbd6f 100644 --- a/nearby/crypto/crypto_provider_test/fuzz/Cargo.toml +++ b/nearby/crypto/crypto_provider_test/fuzz/Cargo.toml
@@ -3,6 +3,7 @@ version = "0.0.0" publish = false edition = "2021" +license = "Apache-2.0" [package.metadata] cargo-fuzz = true @@ -15,6 +16,9 @@ [target.'cfg(fuzzing)'.dependencies] libfuzzer-sys.workspace = true +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + [features] default = ["crypto_provider_default/default"] boringssl = ["crypto_provider_default/boringssl"]
diff --git a/nearby/deny.toml b/nearby/deny.toml index aaf8eea..bc7f4c5 100644 --- a/nearby/deny.toml +++ b/nearby/deny.toml
@@ -24,6 +24,9 @@ ignore = [ # comment explaining why we have to ignore it # "RUSTSEC-FOO", + + # Need a new release of cbindgen: https://github.com/mozilla/cbindgen/issues/983 + "RUSTSEC-2021-0145", ] # Threshold for security vulnerabilities, any vulnerability with a CVSS score # lower than the range specified will be ignored. Note that ignored advisories @@ -120,7 +123,7 @@ # published to private registries. # To see how to mark a crate as unpublished (to the official registry), # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. -ignore = true +ignore = false # One or more private registries that you might publish crates to, if a crate # is only published to private registries, and ignore is true, the crate will # not have its license(s) checked
diff --git a/nearby/presence/array_ref/Cargo.toml b/nearby/presence/array_ref/Cargo.toml index b74168b..c981ecf 100644 --- a/nearby/presence/array_ref/Cargo.toml +++ b/nearby/presence/array_ref/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/array_view/Cargo.toml b/nearby/presence/array_view/Cargo.toml index 8ac53d2..94a37d7 100644 --- a/nearby/presence/array_view/Cargo.toml +++ b/nearby/presence/array_view/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/ldt/Cargo.toml b/nearby/presence/ldt/Cargo.toml index 337dd0d..bb7c81c 100644 --- a/nearby/presence/ldt/Cargo.toml +++ b/nearby/presence/ldt/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/ldt/fuzz/Cargo.toml b/nearby/presence/ldt/fuzz/Cargo.toml index c192792..14659e8 100644 --- a/nearby/presence/ldt/fuzz/Cargo.toml +++ b/nearby/presence/ldt/fuzz/Cargo.toml
@@ -2,6 +2,7 @@ name = "ldt-fuzz" version.workspace = true publish.workspace = true +license.workspace = true edition.workspace = true [package.metadata] @@ -17,6 +18,9 @@ [target.'cfg(fuzzing)'.dependencies] libfuzzer-sys.workspace = true +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + [[bin]] name = "ldt_roundtrip" path = "src/bin/ldt_roundtrip.rs"
diff --git a/nearby/presence/ldt_np_adv/Cargo.toml b/nearby/presence/ldt_np_adv/Cargo.toml index 5d18a28..c846439 100644 --- a/nearby/presence/ldt_np_adv/Cargo.toml +++ b/nearby/presence/ldt_np_adv/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/ldt_np_adv/fuzz/Cargo.toml b/nearby/presence/ldt_np_adv/fuzz/Cargo.toml index e4f497c..544b257 100644 --- a/nearby/presence/ldt_np_adv/fuzz/Cargo.toml +++ b/nearby/presence/ldt_np_adv/fuzz/Cargo.toml
@@ -2,6 +2,7 @@ name = "ldt-np-adv-fuzz" version.workspace = true publish.workspace = true +license.workspace = true edition.workspace = true [package.metadata] @@ -19,6 +20,9 @@ [target.'cfg(fuzzing)'.dependencies] libfuzzer-sys.workspace = true +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + [[bin]] name = "ldt_np_roundtrip" path = "src/bin/ldt_np_roundtrip.rs"
diff --git a/nearby/presence/ldt_np_adv_ffi/Cargo.toml b/nearby/presence/ldt_np_adv_ffi/Cargo.toml index fdc5501..37176fe 100644 --- a/nearby/presence/ldt_np_adv_ffi/Cargo.toml +++ b/nearby/presence/ldt_np_adv_ffi/Cargo.toml
@@ -3,6 +3,7 @@ version = "0.1.0" edition = "2021" publish = false +license = "Apache-2.0" [dependencies] crypto_provider_default.workspace = true
diff --git a/nearby/presence/ldt_np_adv_ffi/c/tests/ldt_ffi_tests.cc b/nearby/presence/ldt_np_adv_ffi/c/tests/ldt_ffi_tests.cc index 65e0f68..674547a 100644 --- a/nearby/presence/ldt_np_adv_ffi/c/tests/ldt_ffi_tests.cc +++ b/nearby/presence/ldt_np_adv_ffi/c/tests/ldt_ffi_tests.cc
@@ -279,12 +279,12 @@ } TEST(LdtFfiTests, MultiThreadedTests) { - int i, num_threads = 100; + constexpr int num_threads = 100; pthread_t tid[num_threads]; memset(tid, 0, num_threads * sizeof(pthread_t)); // Create the threads - for (i = 0; i < num_threads; i++) + for (int i = 0; i < num_threads; i++) ASSERT_EQ(pthread_create(&tid[i], nullptr, worker_thread, (void *)&tid[i]), 0); @@ -295,6 +295,8 @@ pthread_cond_broadcast(&cond); // Wait for them all to finish and check the status - for (i = 0; i < num_threads; i++) ASSERT_EQ(pthread_join(tid[i], nullptr), 0); + for (int i = 0; i < num_threads; i++) { + ASSERT_EQ(pthread_join(tid[i], nullptr), 0); + } } #endif
diff --git a/nearby/presence/ldt_np_jni/Cargo.toml b/nearby/presence/ldt_np_jni/Cargo.toml index cf7c2db..698f022 100644 --- a/nearby/presence/ldt_np_jni/Cargo.toml +++ b/nearby/presence/ldt_np_jni/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/ldt_tbc/Cargo.toml b/nearby/presence/ldt_tbc/Cargo.toml index 8941051..bf7d363 100644 --- a/nearby/presence/ldt_tbc/Cargo.toml +++ b/nearby/presence/ldt_tbc/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/np_adv/Cargo.toml b/nearby/presence/np_adv/Cargo.toml index 6670192..4b79c97 100644 --- a/nearby/presence/np_adv/Cargo.toml +++ b/nearby/presence/np_adv/Cargo.toml
@@ -3,12 +3,13 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true [dependencies] -array_view = { path = "../array_view" } +array_view.workspace = true ldt_np_adv.workspace = true ldt.workspace = true np_hkdf.workspace = true @@ -21,12 +22,15 @@ lazy_static.workspace = true sink.workspace = true tinyvec.workspace = true +itertools = { workspace = true, default-features = false } [features] default = ["alloc"] devtools = [] testing = [] alloc = ["crypto_provider/alloc"] +# adds std::error::Error impls +std = ["alloc"] [dev-dependencies] hex.workspace = true
diff --git a/nearby/presence/np_adv/fuzz/.gitignore b/nearby/presence/np_adv/fuzz/.gitignore new file mode 100644 index 0000000..1a45eee --- /dev/null +++ b/nearby/presence/np_adv/fuzz/.gitignore
@@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage
diff --git a/nearby/presence/np_adv/fuzz/Cargo.toml b/nearby/presence/np_adv/fuzz/Cargo.toml new file mode 100644 index 0000000..d65d5b6 --- /dev/null +++ b/nearby/presence/np_adv/fuzz/Cargo.toml
@@ -0,0 +1,32 @@ +[package] +name = "np_adv_fuzz" +version.workspace = true +publish = false +edition.workspace = true +license.workspace = true + +[package.metadata] +cargo-fuzz = true + +[dependencies] +arbitrary = { workspace = true, features = ["derive"] } +derive_fuzztest.workspace = true +np_adv = { workspace = true, features = ["testing"] } +crypto_provider.workspace = true +crypto_provider_default = { workspace = true, features = ["std", "rustcrypto"] } + +[target.'cfg(fuzzing)'.dependencies] +libfuzzer-sys.workspace = true + +[[bin]] +name = "actions_de_deser" +doc = false + +[[bin]] +name = "actions_de_encoder" +doc = false + +[[bin]] +name = "actions_de_roundtrip" +doc = false +
diff --git a/nearby/presence/np_adv/fuzz/src/bin/actions_de_deser.rs b/nearby/presence/np_adv/fuzz/src/bin/actions_de_deser.rs new file mode 100644 index 0000000..de06305 --- /dev/null +++ b/nearby/presence/np_adv/fuzz/src/bin/actions_de_deser.rs
@@ -0,0 +1,41 @@ +// Copyright 2024 Google LLC +// +// Licensed 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. + +//! Fuzz test for actions data element parsing logic + +#![cfg_attr(fuzzing, no_main)] + +use arbitrary::Unstructured; +use np_adv::extended::data_elements::DeserializedActionsDE; +use np_adv::extended::deserialize::data_element::DataElement; + +#[derive(arbitrary::Arbitrary, Clone, Debug)] +struct ActionsDeserFuzzInput { + data: [u8; 127], + #[arbitrary(with = arbitrary_de_len)] + de_len: usize, +} + +fn arbitrary_de_len(u: &mut Unstructured) -> arbitrary::Result<usize> { + u.int_in_range(0..=127).map(|val| usize::try_from(val).expect("range is valid for a usize")) +} + +#[derive_fuzztest::fuzztest] +fn deserialize_actions_de(input: ActionsDeserFuzzInput) { + let de = DataElement::new_for_testing(0.into(), 6_u32.into(), &input.data[..input.de_len]); + let actions_de = DeserializedActionsDE::try_from(&de).map(|actions| { + // collect actions to trigger iterator parsing logic + let action_ids = actions.collect_action_ids(); + }); +}
diff --git a/common/derive_fuzztest/fuzz/src/bin/integer_add.rs b/nearby/presence/np_adv/fuzz/src/bin/actions_de_encoder.rs similarity index 60% copy from common/derive_fuzztest/fuzz/src/bin/integer_add.rs copy to nearby/presence/np_adv/fuzz/src/bin/actions_de_encoder.rs index 38b8172..9108169 100644 --- a/common/derive_fuzztest/fuzz/src/bin/integer_add.rs +++ b/nearby/presence/np_adv/fuzz/src/bin/actions_de_encoder.rs
@@ -12,12 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Fuzz test for actions data element encoding logic + #![cfg_attr(fuzzing, no_main)] -use derive_fuzztest::fuzztest; +use np_adv::extended::data_elements::ActionsDataElement; +use np_adv::ArrayVec; +use np_adv_fuzz::FuzzInput; -#[fuzztest] -pub fn test(a: u8, b: u8) { - let _ = a.checked_add(b); - // a + b; // This fails because a + b can overflow. +#[derive_fuzztest::fuzztest] +fn deserialize_actions_de(input: FuzzInput) { + let mut actions = ArrayVec::new(); + actions.extend_from_slice(&input.data[..input.count]); + let actions_de = ActionsDataElement::try_from_actions(actions); }
diff --git a/nearby/presence/np_adv/fuzz/src/bin/actions_de_roundtrip.rs b/nearby/presence/np_adv/fuzz/src/bin/actions_de_roundtrip.rs new file mode 100644 index 0000000..7976c5f --- /dev/null +++ b/nearby/presence/np_adv/fuzz/src/bin/actions_de_roundtrip.rs
@@ -0,0 +1,76 @@ +// Copyright 2024 Google LLC +// +// Licensed 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. + +//! Fuzz test for actions data element encoding and decoding round trip + +#![cfg_attr(fuzzing, no_main)] + +use crypto_provider_default::CryptoProviderImpl; +use np_adv::credential::book::CredentialBookBuilder; +use np_adv::credential::matched::EmptyMatchedCredential; +use np_adv::extended::data_elements::{ActionsDataElement, DeserializedActionsDE}; +use np_adv::extended::deserialize::{Section, V1DeserializedSection}; +use np_adv::extended::serialize::{ + AdvBuilder, AdvertisementType, SingleTypeDataElement, UnencryptedSectionEncoder, +}; +use np_adv::{deserialization_arena, deserialize_advertisement, ArrayVec}; +use np_adv_fuzz::FuzzInput; + +#[derive_fuzztest::fuzztest] +fn deserialize_actions_de(input: FuzzInput) { + let mut actions = ArrayVec::new(); + actions.extend_from_slice(&input.data[..input.count]); + let actions_de = match ActionsDataElement::try_from_actions(actions) { + Ok(de) => de, + Err(_) => return, + }; + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap(); + section_builder.add_de(|_salt| actions_de).unwrap(); + section_builder.add_to_advertisement::<CryptoProviderImpl>(); + let arena = deserialization_arena!(); + let adv = adv_builder.into_advertisement(); + let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + let contents = + deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book) + .expect("Should be a valid advertisement") + .into_v1() + .expect("Should be V1"); + assert_eq!(0, contents.invalid_sections_count()); + let sections = contents.sections().collect::<Vec<_>>(); + assert_eq!(1, sections.len()); + let section = match §ions[0] { + V1DeserializedSection::Plaintext(s) => s, + _ => panic!("this is a plaintext adv"), + }; + let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap(); + assert_eq!(1, data_elements.len()); + let de = &data_elements[0]; + assert_eq!(ActionsDataElement::DE_TYPE, de.de_type()); + let actions_de = + DeserializedActionsDE::try_from(de).expect("Should succeed since this de is an actions de"); + let decoded_actions = actions_de + .collect_action_ids() + .iter() + .map(|res| res.expect("valid action ids")) + .collect::<Vec<_>>(); + let mut expected = actions.to_vec(); + expected.sort(); + expected.dedup(); + assert_eq!(expected.as_slice(), decoded_actions.as_slice()); +}
diff --git a/nearby/presence/np_adv/fuzz/src/lib.rs b/nearby/presence/np_adv/fuzz/src/lib.rs new file mode 100644 index 0000000..b10929c --- /dev/null +++ b/nearby/presence/np_adv/fuzz/src/lib.rs
@@ -0,0 +1,38 @@ +// Copyright 2024 Google LLC +// +// Licensed 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 arbitrary::Unstructured; +use np_adv::extended::data_elements::ActionId; + +#[derive(arbitrary::Arbitrary, Clone, Debug)] +pub struct FuzzInput { + #[arbitrary(with = arbitrary_action_ids)] + pub data: [ActionId; 64], + #[arbitrary(with = arbitrary_actions_count)] + pub count: usize, +} + +fn arbitrary_actions_count(u: &mut Unstructured) -> arbitrary::Result<usize> { + u.int_in_range(0..=64).map(|val| usize::try_from(val).unwrap()) +} + +fn arbitrary_action_ids(u: &mut Unstructured) -> arbitrary::Result<[ActionId; 64]> { + Ok(std::array::from_fn(|_| { + let next = u16::try_from( + u.int_in_range(0..=2047).expect("fuzzer should generate enough data for a u16"), + ) + .expect("the range will always be a valid u16"); + ActionId::try_from(next).expect("rang is valid for action_ids") + })) +}
diff --git a/nearby/presence/np_adv/src/array_vec.rs b/nearby/presence/np_adv/src/array_vec.rs index bcc387e..3298ace 100644 --- a/nearby/presence/np_adv/src/array_vec.rs +++ b/nearby/presence/np_adv/src/array_vec.rs
@@ -77,6 +77,13 @@ self.0.push(Some(value)) } + /// Tries to place an element onto the end of the vec. + /// Returns back the element if the capacity is exhausted, + /// otherwise returns None. + pub fn try_push(&mut self, value: A) -> Option<A> { + self.0.try_push(Some(value)).unwrap_or_else(|| None) + } + /// Returns a reference to an element at the given index. pub fn get(&self, index: usize) -> Option<&A> { self.0.get(index).and_then(|opt| opt.as_ref())
diff --git a/nearby/presence/np_adv/src/extended/data_elements/actions.rs b/nearby/presence/np_adv/src/extended/data_elements/actions.rs new file mode 100644 index 0000000..3679555 --- /dev/null +++ b/nearby/presence/np_adv/src/extended/data_elements/actions.rs
@@ -0,0 +1,557 @@ +// Copyright 2024 Google LLC +// +// Licensed 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. + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use itertools::Itertools; +use nom::error::ErrorKind; +use nom::Err::Error; +use nom::{bytes, combinator, multi}; +use sink::Sink; +use tinyvec::ArrayVec; + +use crate::array_vec::ArrayVecOption; +use crate::extended::de_type::DeType; +use crate::extended::deserialize::data_element::DataElement; +use crate::extended::serialize::{DeHeader, SingleTypeDataElement, WriteDataElement}; +use crate::extended::MAX_DE_LEN; + +#[cfg(test)] +mod tests; + +pub const MAX_ACTION_ID_VALUE: u16 = 2047; + +pub const MAX_ACTIONS_CONTAINER_LENGTH: usize = 64; + +// The minimum length of a single container is 2 (one header byte + 1 byte payload) so an actions DE +/// can fit at most 127/2 = 63 total actions containers +pub const MAX_NUM_ACTIONS_CONTAINERS: usize = 63; + +/// Represents a valid action ID which can be encoded in an Actions DE +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Ord, PartialOrd)] +pub struct ActionId(u16); + +impl ActionId { + /// Gets the u16 value of the action id + pub fn as_u16(&self) -> u16 { + self.0 + } +} + +// The provided value was not in the range of valid action ids [0, 2047] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct ActionIdOutOfRange; + +impl TryFrom<u16> for ActionId { + type Error = ActionIdOutOfRange; + + fn try_from(value: u16) -> Result<Self, Self::Error> { + if value <= MAX_ACTION_ID_VALUE { + Ok(Self(value)) + } else { + Err(ActionIdOutOfRange) + } + } +} + +impl From<ActionId> for u16 { + fn from(value: ActionId) -> Self { + value.0 + } +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +enum ContainerType { + DeltaEncoded, + DeltaEncodedWithOffset, + BitVectorOffset, +} + +#[derive(Debug)] +struct UnknownContainerTypeValue; + +impl TryFrom<u8> for ContainerType { + type Error = UnknownContainerTypeValue; + + fn try_from(value: u8) -> Result<Self, Self::Error> { + match value { + 0b00 => Ok(ContainerType::DeltaEncoded), + 0b01 => Ok(ContainerType::DeltaEncodedWithOffset), + 0b10 => Ok(ContainerType::BitVectorOffset), + _ => Err(UnknownContainerTypeValue), + } + } +} + +impl From<ContainerType> for u8 { + fn from(value: ContainerType) -> Self { + match value { + ContainerType::DeltaEncoded => 0b00, + ContainerType::DeltaEncodedWithOffset => 0b01, + ContainerType::BitVectorOffset => 0b10, + } + } +} + +/// Actions data element parsed from a slice of bytes, this type references the original slice of +/// bytes which was parsed +#[derive(Debug)] +pub struct DeserializedActionsDE<'adv> { + containers: ArrayVecOption<DeserializedActionsContainer<'adv>, MAX_NUM_ACTIONS_CONTAINERS>, +} + +impl<'adv> SingleTypeDataElement for DeserializedActionsDE<'adv> { + const DE_TYPE: DeType = DeType::const_from(0x06); +} + +#[derive(Debug, Eq, PartialEq)] +pub enum ActionsDeserializationError { + InvalidTypeCode, + InvalidContainerLength, + InvalidContainerType, + GenericDeserializationError, +} + +/// Convert a deserialized DE into one you can serialize +impl<'a> TryFrom<&'a DataElement<'a>> for DeserializedActionsDE<'a> { + type Error = ActionsDeserializationError; + + fn try_from(value: &'a DataElement<'a>) -> Result<Self, Self::Error> { + if value.de_type() != Self::DE_TYPE { + return Err(ActionsDeserializationError::InvalidTypeCode); + } + DeserializedActionsDE::deserialize(value.contents()).map_err(|e| match e { + Error(e) => match e.code { + ErrorKind::Eof | ErrorKind::Verify => { + ActionsDeserializationError::InvalidContainerLength + } + ErrorKind::MapOpt => ActionsDeserializationError::InvalidContainerType, + _ => ActionsDeserializationError::GenericDeserializationError, + }, + _ => ActionsDeserializationError::GenericDeserializationError, + }) + } +} + +impl<'adv> DeserializedActionsDE<'adv> { + /// Returns a collection over of all action ids contained within the data element + #[cfg(feature = "alloc")] + pub fn collect_action_ids(&self) -> Vec<Result<ActionId, ActionIdOutOfRange>> { + let mut result = Vec::new(); + self.containers.iter().for_each(|container| { + container.iter_action_ids().for_each(|action| result.push(action)) + }); + result + } + + /// Parses the raw bytes of an Actions DE into an intermediate format, that is the contents + /// are separated into containers with their corresponding container type, but no further decoding + /// is done on the contents of the containers, leaving the bytes in their compact encoded format. + fn deserialize( + de_contents: &'adv [u8], + ) -> Result<DeserializedActionsDE, nom::Err<nom::error::Error<&[u8]>>> { + combinator::all_consuming(multi::fold_many_m_n( + 1, + MAX_NUM_ACTIONS_CONTAINERS, + DeserializedActionsContainer::parse, + Self::new_empty, + |mut acc, item| { + acc.add_container(item); + acc + }, + ))(de_contents) + .map(|(rem, actions)| { + debug_assert!(rem.is_empty()); + actions + }) + } + + /// Initializes an actions de with empty contents + fn new_empty() -> Self { + let internal = ArrayVecOption::default(); + Self { containers: internal } + } + + /// Appends a container to the DE, panicking in the case where the max length is exceeded + fn add_container(&mut self, container: DeserializedActionsContainer<'adv>) { + self.containers.push(container); + } +} + +#[derive(Debug)] +struct DeserializedActionsContainer<'adv> { + container_type: ContainerType, + offset: u16, + payload: &'adv [u8], +} + +impl<'adv> DeserializedActionsContainer<'adv> { + fn parse(bytes: &'adv [u8]) -> nom::IResult<&[u8], Self> { + let (input, (container_type, container_encoded_len)) = + combinator::map_opt(nom::number::complete::u8, |b| { + // right shift by 6 to obtain the upper 2 type bits TTLLLLLL + let type_value = b >> 6; + let encoded_len = b & 0b00111111; + ContainerType::try_from(type_value) + .map(|container_type| (container_type, encoded_len)) + .ok() + })(bytes)?; + + let (input, payload_bytes) = bytes::complete::take(container_encoded_len + 1)(input)?; + + let (payload, offset) = match container_type { + ContainerType::DeltaEncoded => (payload_bytes, 0u16), + // A container which only contains the sub header offset and no + // subsequent data is disallowed by the spec + ContainerType::DeltaEncodedWithOffset | ContainerType::BitVectorOffset => { + combinator::verify(nom::number::complete::u8, |_| payload_bytes.len() > 1)( + payload_bytes, + ) + .map(|(remaining, byte)| (remaining, u16::from(byte) * 8))? + } + }; + Ok((input, DeserializedActionsContainer { container_type, offset, payload })) + } + + fn iter_action_ids(&self) -> ActionsContainerIterator { + ActionsContainerIterator::new(self) + } +} + +/// Iterates all the action ids in a container +enum ActionsContainerIterator<'c> { + DeltaEncoded { first: bool, delta: u16, bytes: &'c [u8] }, + BitVector { current_offset: u16, position_in_byte: u8, current_byte: u8, bytes: &'c [u8] }, +} + +impl<'c> ActionsContainerIterator<'c> { + fn new(container: &'c DeserializedActionsContainer) -> Self { + match container.container_type { + ContainerType::DeltaEncoded | ContainerType::DeltaEncodedWithOffset => { + Self::DeltaEncoded { + delta: container.offset, + bytes: container.payload, + first: true, + } + } + ContainerType::BitVectorOffset => { + let (remaining, byte) = + nom::number::complete::u8::<&[u8], nom::error::Error<_>>(container.payload) + .expect( + "we verified bytes has at least one byte when parsing original container", + ); + Self::BitVector { + current_offset: container.offset, + position_in_byte: 0, + current_byte: byte, + bytes: remaining, + } + } + } + } +} + +impl<'c> Iterator for ActionsContainerIterator<'c> { + type Item = Result<ActionId, ActionIdOutOfRange>; + + fn next(&mut self) -> Option<Self::Item> { + match self { + ActionsContainerIterator::DeltaEncoded { delta, bytes, first } => { + nom::number::complete::u8::<&[u8], nom::error::Error<_>>(bytes) + .map(|(rem, b)| { + let mut result = u16::from(b) + *delta; + if *first { + *first = false; + } else { + result += 1; + } + // save the result to be used as the next delta + *delta = result; + // update the slice to no longer include the current byte since we just used it + *bytes = rem; + result.try_into() + }) + .ok() + } + ActionsContainerIterator::BitVector { + current_offset, + position_in_byte, + current_byte, + bytes, + } => { + while *position_in_byte <= 7 || !bytes.is_empty() { + // take another byte and update position and offset + if *position_in_byte > 7 && !bytes.is_empty() { + let (remaining, byte) = + nom::number::complete::u8::<&[u8], nom::error::Error<_>>(bytes) + .expect("we verified bytes has at least one byte"); + *current_byte = byte; + *bytes = remaining; + *current_offset += 8; + *position_in_byte = 0 + } + if ((0b10000000 >> *position_in_byte) & *current_byte) != 0 { + let result: Option<Result<ActionId, _>> = + Some((*current_offset + u16::from(*position_in_byte)).try_into()); + *position_in_byte += 1; + return result; + } + *position_in_byte += 1; + } + None + } + } + } +} +/// Actions data element consists of one or more containers +#[derive(Debug)] +pub struct ActionsDataElement { + containers: ArrayVecOption<ActionsContainer, MAX_NUM_ACTIONS_CONTAINERS>, + len: usize, +} + +impl ActionsDataElement { + /// Creates an Actions DE storing the provided action ids in memory using the default encoding + /// scheme of simple delta encoding. This limits the amount of encodable actions to 64, and the + /// action id range to what can fit in a byte after being delta encoded. + // TODO: provide more flexibility over which encoding scheme is used + pub fn try_from_actions( + actions: ArrayVec<[ActionId; 64]>, + ) -> Result<Self, ActionsDataElementError> { + DeltaEncodedContainer::try_from_actions(actions).map(|c| { + let mut actions_de = ActionsDataElement::new_empty(); + // This will always succeed since we are only adding one container which cannot exceed + // the 127 max DE length + let res = actions_de.try_add_container(c.into()); + debug_assert!(res.is_none()); + actions_de + }) + } + + /// Initializes an actions de with empty contents + fn new_empty() -> Self { + let internal = ArrayVecOption::default(); + Self { containers: internal, len: 0 } + } + + /// Appends a container to the DE, returning back the container in the event that max length of + /// a DE would be exceeded or returning `None` in the case that it has been successfully added + fn try_add_container(&mut self, container: ActionsContainer) -> Option<ActionsContainer> { + if self.len + container.payload.len() + 1 > MAX_DE_LEN { + Some(container) + } else { + self.len = self.len + container.payload.len() + 1; + self.containers.push(container); + None + } + } +} + +/// The actions container type saved in the DE which can represent any of the 3 existing container +/// types as specified in its container_type field. This can be converted into from any of the 3 +/// more specific actions container types and is what is stored in `ActionsDataElement` +#[derive(Debug, Copy, Clone)] +struct ActionsContainer { + container_type: ContainerType, + payload: ArrayVec<[u8; MAX_ACTIONS_CONTAINER_LENGTH]>, +} + +trait ContainerEncoder { + const CONTAINER_TYPE: ContainerType; + /// encodes the bytes of the container after the initial container header + fn encoded_payload(&self) -> ArrayVec<[u8; MAX_ACTIONS_CONTAINER_LENGTH]>; +} + +impl<C: ContainerEncoder> From<C> for ActionsContainer { + fn from(value: C) -> Self { + Self { container_type: C::CONTAINER_TYPE, payload: value.encoded_payload() } + } +} + +#[derive(Debug)] +struct DeltaEncodedContainer { + payload: ArrayVec<[u8; MAX_ACTIONS_CONTAINER_LENGTH]>, +} + +impl DeltaEncodedContainer { + fn try_from_actions( + actions: ArrayVec<[ActionId; MAX_ACTIONS_CONTAINER_LENGTH]>, + ) -> Result<Self, ActionsDataElementError> { + let sorted = sort(actions); + let mut payload = ArrayVec::<[u8; MAX_ACTIONS_CONTAINER_LENGTH]>::default(); + let first = + u8::try_from(sorted.first().ok_or(ActionsDataElementError::EmptyActions)?.as_u16()) + .map_err(|_| ActionsDataElementError::ActionIdDeltaOverflow)?; + payload.push(first); + let remaining = delta_encoding(sorted)?; + payload.extend_from_slice(remaining.as_slice()); + Ok(Self { payload }) + } +} + +impl ContainerEncoder for DeltaEncodedContainer { + const CONTAINER_TYPE: ContainerType = ContainerType::DeltaEncoded; + fn encoded_payload(&self) -> ArrayVec<[u8; MAX_ACTIONS_CONTAINER_LENGTH]> { + self.payload + } +} + +fn sort<const N: usize>(actions: ArrayVec<[ActionId; N]>) -> ArrayVec<[ActionId; N]> { + let mut sorted = actions; + sorted.sort_unstable_by_key(|x| *x); + sorted +} + +/// Calculates the delta encoded for the given sorted collection offset by 1, so an encoding of +/// 0 represents a delta of 1, and an encoding of 0xFF represents a delta of 256 +fn delta_encoding<const N: usize>( + sorted: ArrayVec<[ActionId; N]>, +) -> Result<ArrayVec<[u8; N]>, ActionsDataElementError> { + sorted + .iter() + .tuple_windows() + .filter(|(a, b)| *a != *b) + .map(|(a, b)| u8::try_from(b.as_u16() - (a.as_u16() + 1))) + .collect::<Result<ArrayVec<[u8; N]>, _>>() + .map_err(|_| ActionsDataElementError::ActionIdDeltaOverflow) +} + +#[derive(Debug)] +struct DeltaEncodedOffsetContainer { + payload: ArrayVec<[u8; MAX_ACTIONS_CONTAINER_LENGTH]>, +} + +impl DeltaEncodedOffsetContainer { + #[allow(unused)] + fn try_from_actions( + actions: ArrayVec<[ActionId; MAX_ACTIONS_CONTAINER_LENGTH - 1]>, + ) -> Result<Self, ActionsDataElementError> { + let sorted = sort(actions); + let first_action = sorted.first().ok_or(ActionsDataElementError::EmptyActions)?.as_u16(); + let mut payload = ArrayVec::<[u8; MAX_ACTIONS_CONTAINER_LENGTH]>::default(); + let offset = u8::try_from(first_action / 8) + .expect("Max action id 2047 divided by 8 is always within range of a valid u8"); + payload.push(offset); + let first_action_encoding = u8::try_from(first_action - u16::from(offset) * 8) + .expect("This will always fit into a u8 because of the offset subtraction"); + payload.push(first_action_encoding); + + let remaining = delta_encoding(sorted)?; + payload.extend_from_slice(remaining.as_slice()); + Ok(Self { payload }) + } +} + +impl ContainerEncoder for DeltaEncodedOffsetContainer { + const CONTAINER_TYPE: ContainerType = ContainerType::DeltaEncodedWithOffset; + fn encoded_payload(&self) -> ArrayVec<[u8; MAX_ACTIONS_CONTAINER_LENGTH]> { + self.payload + } +} + +/// The maximum amount of expressible action_ids in a single bit vector container. +/// Each byte can hold 8 unique actions with a max of 64 bytes per container. +const MAX_BIT_VECTOR_ACTIONS: usize = MAX_ACTIONS_CONTAINER_LENGTH * 8; + +#[derive(Debug)] +struct BitVectorOffsetContainer { + payload: ArrayVec<[u8; MAX_ACTIONS_CONTAINER_LENGTH]>, +} + +impl BitVectorOffsetContainer { + #[allow(unused)] + fn try_from_actions(actions: &mut [ActionId]) -> Result<Self, ActionsDataElementError> { + if actions.len() > MAX_BIT_VECTOR_ACTIONS { + return Err(ActionsDataElementError::TooManyActions); + } + actions.sort_unstable_by_key(|x| *x); + + let mut payload = ArrayVec::<[u8; MAX_ACTIONS_CONTAINER_LENGTH]>::new(); + let first = actions.first().ok_or(ActionsDataElementError::EmptyActions)?.as_u16(); + let offset = u8::try_from(first / 8) + .expect("Max action id 2047 divided by 8 is always within range of a valid u8"); + payload.push(offset); + + let max_value = actions.last().expect("we have verified above that sorted is not empty"); + let bytes_required = (max_value.as_u16() / 8) + 1 - u16::from(offset); + for _ in 0..bytes_required { + if payload.try_push(0).is_some() { + return Err(ActionsDataElementError::ActionIdOutOfRange); + } + } + actions.iter().for_each(|a| { + // which byte this action id belongs in + let index = usize::from(a.as_u16() / 8 + 1 - u16::from(offset)); + // how far the action is shifted from the most significant bit in the byte + let shift = a.as_u16() % 8; + payload[index] |= 0b1000_0000 >> shift; + }); + + Ok(Self { payload }) + } +} + +impl ContainerEncoder for BitVectorOffsetContainer { + const CONTAINER_TYPE: ContainerType = ContainerType::BitVectorOffset; + fn encoded_payload(&self) -> ArrayVec<[u8; MAX_ACTIONS_CONTAINER_LENGTH]> { + self.payload + } +} + +/// Errors that can occur constructing an [ActionsDataElement]. +#[derive(Debug, PartialEq, Eq)] +#[allow(unused)] +pub enum ActionsDataElementError { + /// Must specify at least one action id to encode + EmptyActions, + /// The provided ActionIds cannot be delta encoded into u8 values + ActionIdDeltaOverflow, + /// The provided range of action ids cannot be encoded into a single container + ActionIdOutOfRange, + /// Too many actions provided than what can be encoded into a single container + TooManyActions, +} + +impl SingleTypeDataElement for ActionsDataElement { + const DE_TYPE: DeType = DeType::const_from(0x06); +} + +impl WriteDataElement for ActionsDataElement { + fn de_header(&self) -> DeHeader { + DeHeader::new( + Self::DE_TYPE, + // each containers length is the header byte + the length of its encoded contents + self.containers + .iter() + .map(|a| 1 + a.payload.len()) + .sum::<usize>() + .try_into() + .expect("An actions DE will always be <= 127, this is enforced upon creation"), + ) + } + + fn write_de_contents<S: Sink<u8>>(&self, sink: &mut S) -> Option<()> { + // write actions container header and bytes for each container in the de + let mut encoded_actions_de_bytes = ArrayVec::<[u8; MAX_DE_LEN]>::new(); + self.containers.iter().for_each(|a| { + let header_byte = (u8::from(a.container_type) << 6) | ((a.payload.len() as u8) - 1); + // This will not panic because the length of the actions DE is checked during creation + // to not exceed the max limit of 127 + encoded_actions_de_bytes.extend_from_slice(&[header_byte]); + encoded_actions_de_bytes.extend_from_slice(a.payload.as_slice()); + }); + sink.try_extend_from_slice(encoded_actions_de_bytes.as_slice()) + } +}
diff --git a/nearby/presence/np_adv/src/extended/data_elements/actions/tests.rs b/nearby/presence/np_adv/src/extended/data_elements/actions/tests.rs new file mode 100644 index 0000000..3abdbd1 --- /dev/null +++ b/nearby/presence/np_adv/src/extended/data_elements/actions/tests.rs
@@ -0,0 +1,636 @@ +// Copyright 2024 Google LLC +// +// Licensed 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. + +#![allow(clippy::unwrap_used)] + +use alloc::collections::VecDeque; +use alloc::vec; +use alloc::vec::Vec; + +extern crate std; + +use crate::credential::book::{CachedSliceCredentialBook, CredentialBook, CredentialBookBuilder}; +use crate::credential::matched::EmptyMatchedCredential; +use crate::deserialization_arena::DeserializationArena; +use crate::extended::data_elements::actions::{ + ActionsContainer, ActionsDataElement, ActionsDataElementError, ActionsDeserializationError, + BitVectorOffsetContainer, ContainerEncoder, ContainerType, DeltaEncodedContainer, + DeltaEncodedOffsetContainer, DeserializedActionsDE, MAX_ACTIONS_CONTAINER_LENGTH, +}; +use crate::extended::data_elements::ActionId; +use crate::extended::de_type::DeType; +use crate::extended::deserialize::data_element::DataElement; +use crate::extended::deserialize::{Section, V1AdvertisementContents, V1DeserializedSection}; +use crate::extended::serialize::{ + AdvBuilder, AdvertisementType, EncodedAdvertisement, SingleTypeDataElement, + UnencryptedSectionEncoder, +}; +use crate::{deserialization_arena, deserialize_advertisement}; +use crypto_provider_default::CryptoProviderImpl; +use nom::error::ErrorKind; +use nom::Err::Error; +use np_hkdf::v1_salt::DataElementOffset; +use rand::distributions::uniform::SampleRange; +use rand::distributions::Standard; +use rand::prelude::Distribution; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; +use tinyvec::{array_vec, ArrayVec}; + +#[test] +fn actions_de_round_trip() { + let mut actions = ArrayVec::<[ActionId; 64]>::new(); + actions.extend_from_slice([100u16, 500, 300, 600].map(|v| v.try_into().unwrap()).as_slice()); + let actions_de = ActionsDataElement::try_from_actions(actions).expect("should succeed"); + let adv = create_adv_with_de(actions_de); + let arena = deserialization_arena!(); + let cred_book = create_empty_cred_book(); + let contents = deser_into_v1_contents(arena, adv.as_slice(), &cred_book); + assert_eq!(0, contents.invalid_sections_count()); + let sections = contents.sections().collect::<Vec<_>>(); + assert_eq!(1, sections.len()); + let section = match §ions[0] { + V1DeserializedSection::Plaintext(s) => s, + _ => panic!("this is a plaintext adv"), + }; + let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap(); + assert_eq!(1, data_elements.len()); + let de = &data_elements[0]; + assert_eq!(ActionsDataElement::DE_TYPE, de.de_type()); + let actions_de = + DeserializedActionsDE::try_from(de).expect("Should succeed since this de is an actions de"); + assert_eq!( + actions_de + .collect_action_ids() + .iter() + .map(|res| res.expect("valid action ids").as_u16()) + .collect::<Vec<_>>(), + vec![100, 300, 500, 600] + ) +} + +#[test] +fn randomized_containers_roundtrip_tests() { + let mut rng = StdRng::from_entropy(); + for _ in 0..10_000 { + let mut actions_de = ActionsDataElement::new_empty(); + let mut expected = VecDeque::new(); + for _num_containers in 0..rng.gen_range(1..MAX_ACTIONS_CONTAINER_LENGTH) { + let (container, actions) = gen_random_container(&mut rng); + if actions_de.try_add_container(container).is_none() { + expected.push_back(actions); + } + } + let adv = create_adv_with_de(actions_de); + let arena = deserialization_arena!(); + let cred_book = create_empty_cred_book(); + let contents = deser_into_v1_contents(arena, adv.as_slice(), &cred_book); + assert_eq!(0, contents.invalid_sections_count()); + let sections = contents.sections().collect::<Vec<_>>(); + assert_eq!(1, sections.len()); + let section = match §ions[0] { + V1DeserializedSection::Plaintext(s) => s, + _ => panic!("this is a plaintext adv"), + }; + let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap(); + assert_eq!(1, data_elements.len()); + let de = &data_elements[0]; + assert_eq!(ActionsDataElement::DE_TYPE, de.de_type()); + let actions_de = DeserializedActionsDE::try_from(de) + .expect("Should succeed since this de is an actions de"); + assert_eq!(actions_de.containers.len(), expected.len()); + let mut expected_action_ids = + expected.iter().flatten().map(|action| action.as_u16()).collect::<Vec<_>>(); + expected_action_ids.sort(); + expected_action_ids.dedup(); + let mut decoded_action_ids = + actions_de.collect_action_ids().iter().map(|x| x.unwrap().as_u16()).collect::<Vec<_>>(); + decoded_action_ids.sort(); + decoded_action_ids.dedup(); + assert_eq!(expected_action_ids, decoded_action_ids); + } +} + +#[test] +fn roundtrip_edge_case_actions_ids_in_last_bit_of_byte() { + let mut actions = + [1260u16, 1268, 1269, 1273, 1285, 1288, 1302, 1303, 1306, 1308, 1310, 1312, 1320, 1330] + .map(|v| ActionId::try_from(v).unwrap()); + let container = BitVectorOffsetContainer::try_from_actions(actions.as_mut_slice()).unwrap(); + let mut de = ActionsDataElement::new_empty(); + assert!(de.try_add_container(container.into()).is_none()); + let adv = create_adv_with_de(de); + let arena = deserialization_arena!(); + let cred_book = create_empty_cred_book(); + let contents = deser_into_v1_contents(arena, adv.as_slice(), &cred_book); + assert_eq!(0, contents.invalid_sections_count()); + let sections = contents.sections().collect::<Vec<_>>(); + assert_eq!(1, sections.len()); + let section = match §ions[0] { + V1DeserializedSection::Plaintext(s) => s, + _ => panic!("this is a plaintext adv"), + }; + let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap(); + assert_eq!(1, data_elements.len()); + let de = &data_elements[0]; + assert_eq!(ActionsDataElement::DE_TYPE, de.de_type()); + let actions_de = + DeserializedActionsDE::try_from(de).expect("Should succeed since this de is an actions de"); + let decoded_actions = + actions_de.collect_action_ids().iter().map(|c| c.expect("")).collect::<Vec<ActionId>>(); + assert_eq!(actions.as_slice(), decoded_actions.as_slice()) +} + +fn create_adv_with_de(actions_de: ActionsDataElement) -> EncodedAdvertisement { + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap(); + section_builder.add_de(|_salt| actions_de).unwrap(); + section_builder.add_to_advertisement::<CryptoProviderImpl>(); + adv_builder.into_advertisement() +} + +fn create_empty_cred_book<'a>() -> CachedSliceCredentialBook<'a, EmptyMatchedCredential, 0, 0> { + CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]) +} + +pub fn deser_into_v1_contents<'adv, 'cred, B>( + arena: DeserializationArena<'adv>, + adv: &'adv [u8], + cred_book: &'cred B, +) -> V1AdvertisementContents<'adv, B::Matched> +where + B: CredentialBook<'cred>, +{ + deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv, cred_book) + .expect("Should be a valid advertisement") + .into_v1() + .expect("Should be V1") +} + +impl Distribution<ContainerType> for Standard { + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> ContainerType { + match rng.gen_range(0..=2) { + 0 => ContainerType::DeltaEncoded, + 1 => ContainerType::DeltaEncodedWithOffset, + _ => ContainerType::BitVectorOffset, + } + } +} + +fn gen_actions_in_rng<const N: usize, R: SampleRange<u16> + Clone>( + rng: &mut StdRng, + gen_range: R, +) -> ArrayVec<[ActionId; N]> { + let mut actions = ArrayVec::new(); + for _ in 0..rng.gen_range(1..N) { + let action_id: u16 = rng.gen_range(gen_range.clone()); + actions.push(ActionId::try_from(action_id).expect("range is a valid action id")) + } + actions +} + +fn gen_delta_actions<const N: usize, R: SampleRange<u16> + Clone>( + rng: &mut StdRng, + gen_range: R, +) -> ArrayVec<[ActionId; N]> { + let mut actions = ArrayVec::new(); + let mut previous = rng.gen_range(gen_range.clone()); + actions.push(ActionId::try_from(previous).unwrap()); + for _ in 0..rng.gen_range(1..N - 1) { + // make sure generated actions are within a valid delta range of other actions + let lower_bound = previous.saturating_sub(256); + let upper_bound = if previous + 256 > 2047 { 2047 } else { previous + 256 }; + let action_id = rng.gen_range(lower_bound..=upper_bound); + actions.push(ActionId::try_from(action_id).unwrap()); + previous = action_id + } + actions +} + +fn gen_random_container(rng: &mut StdRng) -> (ActionsContainer, Vec<ActionId>) { + let container_type: ContainerType = rng.gen(); + match container_type { + ContainerType::DeltaEncoded => { + let actions = gen_delta_actions(rng, 0..=255); + (DeltaEncodedContainer::try_from_actions(actions).unwrap().into(), actions.to_vec()) + } + ContainerType::DeltaEncodedWithOffset => { + let actions = gen_delta_actions(rng, 0..=2047); + ( + DeltaEncodedOffsetContainer::try_from_actions(actions).unwrap().into(), + actions.to_vec(), + ) + } + ContainerType::BitVectorOffset => { + let range_lower_bound = rng.gen_range(0u16..2047 - 495); + let range_upper_bound = range_lower_bound + 495; + let mut actions = + gen_actions_in_rng::<512, _>(rng, range_lower_bound..=range_upper_bound); + ( + BitVectorOffsetContainer::try_from_actions(actions.as_mut_slice()).unwrap().into(), + actions.to_vec(), + ) + } + } +} + +#[test] +fn parse_single_container_delta_encoded() { + let bytes = [0x01, 0x64, 0xC7]; + let actions_de = + DeserializedActionsDE::deserialize(&bytes).expect("bytes parse into valid actions DE"); + let action_containers = &actions_de.containers; + assert_eq!(action_containers.len(), 1); + let container = action_containers.first().unwrap(); + assert_eq!(container.container_type, ContainerType::DeltaEncoded); + assert_eq!(container.payload, &[0x64, 0xC7]); + + let action_ids: Vec<_> = container + .iter_action_ids() + .map(|id| id.expect("test data contains action ids within valid range").as_u16()) + .collect(); + assert_eq!(action_ids, vec![100u16, 300]); + assert_eq!( + actions_de.collect_action_ids(), + vec![Ok(100u16.try_into().unwrap()), Ok(300.try_into().unwrap())] + ); +} + +#[test] +fn encode_single_container_delta_encoded() { + let actions = array_vec!([ActionId; 64] => ActionId::try_from(100).unwrap(), ActionId::try_from(300).unwrap()); + let container = DeltaEncodedContainer::try_from_actions(actions).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0x64, 0xC7]) +} + +#[test] +fn encode_single_container_delta_encoded_delta_1() { + let actions = array_vec!([ActionId; 64] => ActionId::try_from(100).unwrap(), ActionId::try_from(101).unwrap()); + let container = DeltaEncodedContainer::try_from_actions(actions).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0x64, 0x00]) +} + +#[test] +fn encode_single_container_delta_encoded_max_delta() { + let actions = array_vec!([ActionId; 64] => ActionId::try_from(100).unwrap(), ActionId::try_from(356).unwrap()); + let container = DeltaEncodedContainer::try_from_actions(actions).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0x64, 0xFF]) +} + +#[test] +fn parse_single_container_delta_encoded_with_offset() { + let bytes = [0x42, 0x06, 0x02, 0x66]; + let actions_de = + DeserializedActionsDE::deserialize(&bytes).expect("provided bytes are valid actions de"); + assert_eq!(actions_de.containers.len(), 1); + let container = actions_de.containers.first().unwrap(); + assert_eq!(container.container_type, ContainerType::DeltaEncodedWithOffset); + assert_eq!(container.offset, 48); + let actions: Vec<u16> = container + .iter_action_ids() + .map(|action| action.expect("Encoded id is in valid range").as_u16()) + .collect(); + assert_eq!(actions, vec![50, 153]); + assert_eq!( + actions_de.collect_action_ids(), + vec![Ok(50.try_into().unwrap()), Ok(153.try_into().unwrap())] + ); +} + +#[test] +fn encode_single_container_delta_encoded_with_offset() { + let actions = array_vec!([ActionId; 63] => ActionId::try_from(50).unwrap(), ActionId::try_from(153).unwrap()); + let container = DeltaEncodedOffsetContainer::try_from_actions(actions).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0x06, 0x02, 0x66]) +} + +#[test] +fn encode_single_container_delta_encoded_with_offset_delta_of_1() { + let actions = array_vec!([ActionId; 63] => ActionId::try_from(50).unwrap(), ActionId::try_from(51).unwrap()); + let container = DeltaEncodedOffsetContainer::try_from_actions(actions).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0x06, 0x02, 0x00]) +} + +#[test] +fn encode_single_container_delta_encoded_with_offset_max_delta() { + let actions = array_vec!([ActionId; 63] => ActionId::try_from(50).unwrap(), ActionId::try_from(306).unwrap()); + let container = DeltaEncodedOffsetContainer::try_from_actions(actions).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0x06, 0x02, 0xFF]) +} + +#[test] +fn encode_single_container_delta_encoded_with_offset_delta_of_0() { + let actions = array_vec!([ActionId; 63] => ActionId::try_from(48).unwrap(), ActionId::try_from(304).unwrap()); + let container = DeltaEncodedOffsetContainer::try_from_actions(actions).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0x06, 0x00, 0xFF]) +} + +#[test] +fn parse_single_container_bit_vector_offset() { + let bytes = [0x82, 0x01, 0x5C, 0x08]; + let actions_de = + DeserializedActionsDE::deserialize(&bytes).expect("provided bytes are valid actions de"); + assert_eq!(actions_de.containers.len(), 1); + let container = actions_de.containers.first().unwrap(); + assert_eq!(container.container_type, ContainerType::BitVectorOffset); + assert_eq!(container.offset, 8); + let actions: Vec<u16> = container + .iter_action_ids() + .map(|action| action.expect("Encoded id is in valid range").as_u16()) + .collect(); + assert_eq!(actions, vec![9, 11, 12, 13, 20]); +} + +#[test] +fn encode_single_container_bit_vector_offset() { + let mut actions = [9, 11, 12, 13, 20].map(|x| ActionId::try_from(x).unwrap()); + let container = + BitVectorOffsetContainer::try_from_actions(actions.as_mut_slice()).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0x01, 0x5C, 0x08]) +} + +#[test] +fn encode_single_container_bit_vector_offset_offset_of_0() { + let mut actions = [0, 3, 5, 7].map(|x| ActionId::try_from(x).unwrap()); + let container = + BitVectorOffsetContainer::try_from_actions(actions.as_mut_slice()).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0x00, 0b1001_0101]) +} + +#[test] +fn encode_single_container_bit_vector_offset_max_offset() { + let mut actions = [2046, 2047].map(|x| ActionId::try_from(x).unwrap()); + let container = + BitVectorOffsetContainer::try_from_actions(actions.as_mut_slice()).expect("Should succeed"); + assert_eq!(container.encoded_payload().as_slice(), &[0xFF, 0b0000_0011]) +} + +#[test] +fn encode_single_container_bit_vector_max_range() { + // A container is at most 64 bytes. One byte is always used for the offset so that leaves 63 total bytes of + // bit vector encoded action ids where each bit represents an action. Since the first byte represent 0-7, + // byte 63 represents actions 496-503, making 503 the maximum possible action id which can be encoded + // in this container + let mut actions = [0, 503].map(|x| ActionId::try_from(x).unwrap()); + let container = + BitVectorOffsetContainer::try_from_actions(actions.as_mut_slice()).expect("Should succeed"); + let mut expected = [0; 64]; + expected[1] = 0b1000_0000; + expected[63] = 0b0000_0001; + assert_eq!(container.encoded_payload().as_slice(), &expected); +} + +#[test] +fn encode_single_container_bit_vector_invalid_range() { + let mut actions = [1, 504].map(|x| ActionId::try_from(x).unwrap()); + let err = BitVectorOffsetContainer::try_from_actions(actions.as_mut_slice()).unwrap_err(); + assert_eq!(err, ActionsDataElementError::ActionIdOutOfRange) +} + +#[test] +fn encode_single_container_bit_vector_max_range_with_offset() { + // now 559 should still be in range since this is offset by 7 bytes + let mut actions = [56, 559].map(|x| ActionId::try_from(x).unwrap()); + let container = + BitVectorOffsetContainer::try_from_actions(actions.as_mut_slice()).expect("Should succeed"); + let mut expected = [0; 64]; + expected[0] = 7; + expected[1] = 0b1000_0000; + expected[63] = 0b0000_0001; + assert_eq!(container.encoded_payload().as_slice(), &expected); +} + +#[test] +fn encode_single_container_bit_vector_out_of_range_with_offset() { + let mut actions = [56, 560].map(|x| ActionId::try_from(x).unwrap()); + let err = BitVectorOffsetContainer::try_from_actions(actions.as_mut_slice()).unwrap_err(); + assert_eq!(err, ActionsDataElementError::ActionIdOutOfRange); +} + +#[test] +fn encode_bit_vector_max_amount_of_ids() { + let mut actions_ids = [0xFF; 512].map(|x| ActionId::try_from(x).unwrap()); + assert!(BitVectorOffsetContainer::try_from_actions(actions_ids.as_mut_slice()).is_ok()) +} + +#[test] +fn encode_bit_vector_too_many_actionids() { + let mut actions_ids = [0xFF; 513].map(|x| ActionId::try_from(x).unwrap()); + assert_eq!( + BitVectorOffsetContainer::try_from_actions(actions_ids.as_mut_slice()).unwrap_err(), + ActionsDataElementError::TooManyActions + ); +} + +#[test] +fn parse_multiple_containers() { + let bytes = [ + vec![0x01, 0x64, 0xC7], // delta encoded container + vec![0x42, 0x06, 0x02, 0x66], // delta encoded with offset + vec![0x82, 0x01, 0x5C, 0x08], // bit vector offset + ] + .concat(); + let de = DeserializedActionsDE::deserialize(&bytes).expect("bytes are valid de"); + assert_eq!(de.containers.len(), 3); + assert_eq!(de.containers[0].container_type, ContainerType::DeltaEncoded); + assert_eq!(de.containers[1].container_type, ContainerType::DeltaEncodedWithOffset); + assert_eq!(de.containers[2].container_type, ContainerType::BitVectorOffset); + + let action_ids = de.collect_action_ids(); + assert_eq!( + action_ids + .iter() + .map(|a| { a.expect("action_ids encoded are in valid range").as_u16() }) + .collect::<Vec<_>>(), + vec![100, 300, 50, 153, 9, 11, 12, 13, 20] + ); +} + +#[test] +fn try_from_invalid_type_code() { + let data = []; + let de = DataElement::new(DataElementOffset::from(0), DeType::const_from(5), &data); + assert_eq!( + DeserializedActionsDE::try_from(&de).unwrap_err(), + ActionsDeserializationError::InvalidTypeCode + ); +} + +#[test] +fn try_from_invalid_container_length_empty_after_subheader() { + let data = [0x40, 0x64]; + let de = DataElement::new(DataElementOffset::from(0), DeType::const_from(6), &data); + assert_eq!( + DeserializedActionsDE::try_from(&de).unwrap_err(), + ActionsDeserializationError::InvalidContainerLength + ); +} + +#[test] +fn try_from_invalid_container_length_not_enough_data() { + let data = [0x02, 0x64, 0xC8]; + let de = DataElement::new(DataElementOffset::from(0), DeType::const_from(6), &data); + assert_eq!( + DeserializedActionsDE::try_from(&de).unwrap_err(), + ActionsDeserializationError::InvalidContainerLength + ); +} + +#[test] +fn try_from_invalid_container_type() { + let data = [0xC0, 0x64]; + let de = DataElement::new(DataElementOffset::from(0), DeType::const_from(6), &data); + assert_eq!( + DeserializedActionsDE::try_from(&de).unwrap_err(), + ActionsDeserializationError::InvalidContainerType + ); +} + +#[test] +fn parse_single_byte_delta_encoding() { + let bytes = [0x00, 0x64]; + let actions_de = DeserializedActionsDE::deserialize(&bytes).unwrap(); + let action_containers = &actions_de.containers; + assert_eq!(action_containers.len(), 1); + let container = action_containers.first().unwrap(); + assert_eq!(container.container_type, ContainerType::DeltaEncoded); + assert_eq!(container.payload, &[0x64]) +} + +#[test] +fn parse_single_byte_delta_encoding_offset_should_fail() { + let bytes = [0x40, 0x64]; + let err = DeserializedActionsDE::deserialize(&bytes).unwrap_err(); + assert_eq!(err, Error(nom::error::Error { input: &[100][..], code: ErrorKind::Verify })); +} + +#[test] +fn parse_single_container_max_length() { + let mut bytes = vec![0x3F]; + bytes.extend_from_slice(&[0x01; 64]); + + let actions_de = + DeserializedActionsDE::deserialize(&bytes).expect("bytes parse into valid actions DE"); + let action_containers = &actions_de.containers; + assert_eq!(action_containers.len(), 1); + let container = action_containers.first().unwrap(); + assert_eq!(container.container_type, ContainerType::DeltaEncoded); + assert_eq!(container.payload, &[0x01; 64]); +} + +#[test] +fn parse_single_byte_bit_vector_offset_should_fail() { + let bytes = [0x80, 0x64]; + let err = DeserializedActionsDE::deserialize(&bytes).unwrap_err(); + assert_eq!(err, Error(nom::error::Error { input: &[100][..], code: ErrorKind::Verify })); +} + +#[test] +fn parse_actions_de_invalid_length_bytes() { + let bytes = [0x02, 0x64, 0xC8]; + let err = DeserializedActionsDE::deserialize(&bytes).unwrap_err(); + assert_eq!(err, Error(nom::error::Error { input: &[100u8, 200][..], code: ErrorKind::Eof })) +} + +#[test] +fn parse_actions_de_extra_bytes() { + let bytes = [0x01, 0x64, 0xC8, 0xFF]; + let err = DeserializedActionsDE::deserialize(&bytes).unwrap_err(); + assert_eq!(err, Error(nom::error::Error { input: &[255u8][..], code: ErrorKind::Eof })) +} + +#[test] +fn parse_actions_de_invalid_container_type() { + let bytes = [0xC2, 0x64, 0xC8]; + let err = DeserializedActionsDE::deserialize(&bytes).unwrap_err(); + assert_eq!( + err, + Error(nom::error::Error { input: &[0xC2, 0x64, 0xC8][..], code: ErrorKind::MapOpt }) + ) +} + +#[test] +fn encode_empty_actions() { + let actions = ArrayVec::new(); + let err = ActionsDataElement::try_from_actions(actions).unwrap_err(); + assert_eq!(err, ActionsDataElementError::EmptyActions); +} + +#[test] +fn encode_empty_delta_container() { + let actions = ArrayVec::new(); + let err = DeltaEncodedContainer::try_from_actions(actions).unwrap_err(); + assert_eq!(err, ActionsDataElementError::EmptyActions); +} + +#[test] +fn encode_empty_delta_offset_container() { + let actions = ArrayVec::new(); + let err = DeltaEncodedOffsetContainer::try_from_actions(actions).unwrap_err(); + assert_eq!(err, ActionsDataElementError::EmptyActions); +} + +#[test] +fn encode_empty_bit_vector_offset_container() { + let err = BitVectorOffsetContainer::try_from_actions(&mut []).unwrap_err(); + assert_eq!(err, ActionsDataElementError::EmptyActions); +} + +#[test] +fn encode_basic_delta_encoding() { + let mut actions = ArrayVec::<[ActionId; 64]>::new(); + actions.extend_from_slice([100u16, 500, 300, 600].map(|v| v.try_into().unwrap()).as_slice()); + let result = ActionsDataElement::try_from_actions(actions).expect("should succeed"); + assert_eq!(result.containers[0].container_type, ContainerType::DeltaEncoded); + assert_eq!(result.containers[0].payload.as_slice(), &[100, 199, 199, 99]); +} + +#[test] +fn encode_basic_delta_encoding_id_out_of_range() { + let mut actions = ArrayVec::<[ActionId; 64]>::new(); + actions.extend_from_slice([256].map(|v| v.try_into().unwrap()).as_slice()); + let err = ActionsDataElement::try_from_actions(actions).unwrap_err(); + assert_eq!(err, ActionsDataElementError::ActionIdDeltaOverflow); +} + +#[test] +fn encode_basic_delta_encoding_max_id() { + let mut actions = ArrayVec::<[ActionId; 64]>::new(); + actions.extend_from_slice([255].map(|v| v.try_into().unwrap()).as_slice()); + let de = ActionsDataElement::try_from_actions(actions).expect("should succeed"); + assert_eq!(de.containers[0].container_type, ContainerType::DeltaEncoded); + assert_eq!(de.containers[0].payload.as_slice(), &[255]); +} + +#[test] +fn encode_basic_delta_encoding_duplicate_ids() { + let mut actions = ArrayVec::<[ActionId; 64]>::new(); + actions + .extend_from_slice([1, 1, 1, 6, 3, 3, 6, 3, 3].map(|v| v.try_into().unwrap()).as_slice()); + let de = ActionsDataElement::try_from_actions(actions).expect("should succeed"); + assert_eq!(de.containers[0].container_type, ContainerType::DeltaEncoded); + assert_eq!(de.containers[0].payload.as_slice(), &[1, 1, 2]); +} + +#[test] +fn encode_basic_delta_encoding_delta_out_of_range() { + let mut actions = ArrayVec::<[ActionId; 64]>::new(); + actions.extend_from_slice([100, 400].map(|v| v.try_into().unwrap()).as_slice()); + let err = ActionsDataElement::try_from_actions(actions).unwrap_err(); + assert_eq!(err, ActionsDataElementError::ActionIdDeltaOverflow); +}
diff --git a/nearby/presence/np_adv/src/extended/data_elements/mod.rs b/nearby/presence/np_adv/src/extended/data_elements/mod.rs index 2989966..a5b7f91 100644 --- a/nearby/presence/np_adv/src/extended/data_elements/mod.rs +++ b/nearby/presence/np_adv/src/extended/data_elements/mod.rs
@@ -29,6 +29,11 @@ use array_view::ArrayView; use sink::Sink; +mod actions; +pub use actions::ActionId; +pub use actions::ActionsDataElement; +pub use actions::DeserializedActionsDE; + #[cfg(test)] mod tests; @@ -102,44 +107,6 @@ } } -/// List of actions -pub struct ActionsDataElement { - actions: tinyvec::ArrayVec<[u8; MAX_DE_LEN]>, -} - -impl ActionsDataElement { - /// Returns `Some` if the actions will fit in a DE, `None` otherwise - pub fn try_from_actions(actions: &[u8]) -> Result<Self, ActionsDataElementError> { - let mut de = Self { actions: tinyvec::ArrayVec::new() }; - - de.actions - .try_extend_from_slice(actions) - .map(|_| de) - .ok_or(ActionsDataElementError::ActionsTooLong) - } -} - -impl SingleTypeDataElement for ActionsDataElement { - const DE_TYPE: DeType = DeType::const_from(0x06); -} - -impl WriteDataElement for ActionsDataElement { - fn de_header(&self) -> DeHeader { - DeHeader::new(Self::DE_TYPE, self.actions.len().try_into().expect("always <= max length")) - } - - fn write_de_contents<S: Sink<u8>>(&self, sink: &mut S) -> Option<()> { - sink.try_extend_from_slice(&self.actions) - } -} - -/// Errors that can occur constructing an [ActionsDataElement]. -#[derive(Debug, PartialEq, Eq)] -pub enum ActionsDataElementError { - /// Too many action bytes. - ActionsTooLong, -} - /// Context sync sequence number pub struct ContextSyncSeqNumDataElement { num: ContextSyncSeqNum,
diff --git a/nearby/presence/np_adv/src/extended/data_elements/tests.rs b/nearby/presence/np_adv/src/extended/data_elements/tests.rs index 018f92c..868a127 100644 --- a/nearby/presence/np_adv/src/extended/data_elements/tests.rs +++ b/nearby/presence/np_adv/src/extended/data_elements/tests.rs
@@ -16,11 +16,16 @@ extern crate std; -use super::*; +use tinyvec::ArrayVec; + +use crypto_provider_default::CryptoProviderImpl; + +use crate::extended::data_elements::actions::{ActionId, ActionsDataElement}; use crate::extended::serialize::{section_tests::SectionBuilderExt, AdvBuilder}; use crate::extended::serialize::{AdvertisementType, UnencryptedSectionEncoder}; use crate::extended::V1_ENCODING_UNENCRYPTED; -use crypto_provider_default::CryptoProviderImpl; + +use super::*; #[test] fn serialize_tx_power_de() { @@ -40,40 +45,33 @@ ); } -#[test] -fn serialize_actions_de_empty() { - let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); - let mut section_builder = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap(); - - section_builder.add_de_res(|_| ActionsDataElement::try_from_actions(&[])).unwrap(); - - assert_eq!( - &[ - V1_ENCODING_UNENCRYPTED, //header - 1, // section len - 0x06, // len 0 type 0x06 - ], - section_builder.into_section::<CryptoProviderImpl>().as_slice() - ); +fn actions_ids_from_u16_collection<const N: usize>(actions: [u16; N]) -> ArrayVec<[ActionId; 64]> { + let mut result = ArrayVec::new(); + result.extend_from_slice(&actions.map(|x| x.try_into().unwrap())); + result } -#[rustfmt::skip] #[test] fn serialize_actions_de_non_empty() { let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); - let mut section_builder = - adv_builder.section_builder(UnencryptedSectionEncoder).unwrap(); + let mut section_builder = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap(); section_builder - .add_de_res(|_| ActionsDataElement::try_from_actions(&[1, 1, 2, 3, 5, 8])) + .add_de_res(|_| { + ActionsDataElement::try_from_actions(actions_ids_from_u16_collection([ + 1, 1, 2, 3, 5, 8, // fibonacci, of course + ])) + }) .unwrap(); + #[rustfmt::skip] assert_eq!( &[ V1_ENCODING_UNENCRYPTED, 7, // section len 0x66, // len 6 type 0x06 - 1, 1, 2, 3, 5, 8 // fibonacci, of course + 0b00000100, // container type and len TTLLLLLL + 1, 0, 0, 1, 2 // de-duped delta encoded fibonacci ], section_builder.into_section::<CryptoProviderImpl>().as_slice() ); @@ -196,8 +194,10 @@ } mod coverage_gaming { - use super::*; use alloc::format; + + use super::*; + #[test] fn de_type_const_from() { let _ = DeType::const_from(3); @@ -214,13 +214,6 @@ } #[test] - fn actions_de_error_derives() { - let err = ActionsDataElementError::ActionsTooLong; - let _ = format!("{:?}", err); - assert_eq!(err, err); - } - - #[test] fn generic_data_element_debug() { let generic = GenericDataElement::try_from(DeType::from(1000_u32), &[10, 11, 12, 13]).unwrap();
diff --git a/nearby/presence/np_adv/src/extended/de_type.rs b/nearby/presence/np_adv/src/extended/de_type.rs index baefe6a..be26555 100644 --- a/nearby/presence/np_adv/src/extended/de_type.rs +++ b/nearby/presence/np_adv/src/extended/de_type.rs
@@ -23,7 +23,7 @@ impl DeType { /// A `const` equivalent to `From<u32>` since trait methods can't yet be const. - pub(crate) const fn const_from(value: u32) -> Self { + pub const fn const_from(value: u32) -> Self { Self { code: value } }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/data_element/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/data_element/mod.rs index e717fa1..b90de8a 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/data_element/mod.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/data_element/mod.rs
@@ -16,6 +16,7 @@ use crate::extended::{de_requires_extended_bit, de_type::DeType, deserialize, DeLength}; use array_view::ArrayView; +use core::fmt; use nom::{branch, bytes, combinator, error, number, sequence}; use np_hkdf::v1_salt; @@ -34,6 +35,22 @@ } impl<'adv> DataElement<'adv> { + /// The offset of the DE in its containing Section. + /// + /// Used with the section salt to derive per-DE salt. + pub fn offset(&self) -> v1_salt::DataElementOffset { + self.offset + } + + /// The type of the DE + pub fn de_type(&self) -> DeType { + self.de_type + } + /// The contents of the DE + pub fn contents(&self) -> &'adv [u8] { + self.contents + } + pub(crate) fn new( offset: v1_salt::DataElementOffset, de_type: DeType, @@ -41,6 +58,17 @@ ) -> Self { Self { offset, de_type, contents } } + + /// Exposes the ability to create a DE for testing purposes, real clients should only obtain + /// one by deserializing an advertisement + #[cfg(feature = "testing")] + pub fn new_for_testing( + offset: v1_salt::DataElementOffset, + de_type: DeType, + contents: &'adv [u8], + ) -> Self { + Self { offset, de_type, contents } + } } impl DeHeader { @@ -144,23 +172,6 @@ } } -impl<'adv> DataElement<'adv> { - /// The offset of the DE in its containing Section. - /// - /// Used with the section salt to derive per-DE salt. - pub fn offset(&self) -> v1_salt::DataElementOffset { - self.offset - } - /// The type of the DE - pub fn de_type(&self) -> DeType { - self.de_type - } - /// The contents of the DE - pub fn contents(&self) -> &'adv [u8] { - self.contents - } -} - /// An iterator that parses the given data elements iteratively. In environments where memory is /// not severely constrained, it is usually safer to collect this into `Result<Vec<DataElement>>` /// so the validity of the whole advertisement can be checked before proceeding with further @@ -225,6 +236,19 @@ NomError(error::ErrorKind), } +impl fmt::Display for DataElementParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DataElementParseError::UnexpectedDataAfterEnd => write!(f, "Unexpected data after end"), + DataElementParseError::TooManyDataElements => write!(f, "Too many data elements"), + DataElementParseError::NomError(_) => write!(f, "Nom error"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for DataElementParseError {} + /// Deserialize-specific version of a DE header that incorporates the header length. /// This is needed for encrypted identities that need to construct a slice of everything in the /// section following the identity DE header.
diff --git a/nearby/presence/np_adv/src/extended/deserialize/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/mod.rs index c6c7ba4..123cee6 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/mod.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/mod.rs
@@ -38,7 +38,7 @@ }, }, salt::MultiSalt, - V1IdentityToken, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, + V1IdentityToken, NP_V1_ADV_MAX_SECTION_COUNT, }, header::V1AdvHeader, AdvDeserializationError, AdvDeserializationErrorDetailsHazmat, @@ -94,9 +94,9 @@ } /// A section deserialized from a V1 advertisement. -pub trait Section<'adv, E: Debug> { +pub trait Section<'adv> { /// The iterator type used to iterate over data elements - type Iterator: Iterator<Item = Result<DataElement<'adv>, E>>; + type Iterator: Iterator<Item = Result<DataElement<'adv>, DataElementParseError>>; /// Iterator over the data elements in a section, except for any DEs related to resolving the /// identity or otherwise validating the payload (e.g. MIC, Signature, any identity DEs like @@ -106,7 +106,7 @@ /// Collects the data elements into a vector, eagerly catching and resolving any errors during /// parsing. #[cfg(any(test, feature = "alloc"))] - fn collect_data_elements(&self) -> Result<Vec<DataElement<'adv>>, E> + fn collect_data_elements(&self) -> Result<Vec<DataElement<'adv>>, DataElementParseError> where Self: Sized, { @@ -162,7 +162,7 @@ } } -impl<'adv> Section<'adv, DataElementParseError> for DecryptedSection<'adv> { +impl<'adv> Section<'adv> for DecryptedSection<'adv> { type Iterator = DataElementParsingIterator<'adv>; fn iter_data_elements(&self) -> Self::Iterator { @@ -225,14 +225,10 @@ /// section ordering as they appeared within the original advertisement to ensure /// that the fully-deserialized advertisement may be correctly reconstructed. struct SectionsInProcessing<'adv, M: MatchedCredential> { - deserialized_sections: ArrayVecOption< - (usize, V1DeserializedSection<'adv, M>), - { NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT }, - >, - encrypted_sections: ArrayVecOption< - (usize, ResolvableCiphertextSection<'adv>), - { NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT }, - >, + deserialized_sections: + ArrayVecOption<(usize, V1DeserializedSection<'adv, M>), { NP_V1_ADV_MAX_SECTION_COUNT }>, + encrypted_sections: + ArrayVecOption<(usize, ResolvableCiphertextSection<'adv>), { NP_V1_ADV_MAX_SECTION_COUNT }>, malformed_sections_count: usize, } @@ -387,16 +383,13 @@ /// The contents of a deserialized and decrypted V1 advertisement. #[derive(Debug, PartialEq, Eq)] pub struct V1AdvertisementContents<'adv, M: MatchedCredential> { - sections: ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT>, + sections: ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_SECTION_COUNT>, invalid_sections: usize, } impl<'adv, M: MatchedCredential> V1AdvertisementContents<'adv, M> { fn new( - sections: ArrayVecOption< - V1DeserializedSection<'adv, M>, - NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, - >, + sections: ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_SECTION_COUNT>, invalid_sections: usize, ) -> Self { Self { sections, invalid_sections } @@ -406,7 +399,7 @@ /// which could be successfully deserialized and decrypted pub fn into_sections( self, - ) -> ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT> { + ) -> ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_SECTION_COUNT> { self.sections } @@ -431,6 +424,18 @@ Decrypted(WithMatchedCredential<M, DecryptedSection<'adv>>), } +// Allow easy DE iteration if the user doesn't care which kind of section it is +impl<'adv, M: MatchedCredential> Section<'adv> for V1DeserializedSection<'adv, M> { + type Iterator = DataElementParsingIterator<'adv>; + + fn iter_data_elements(&self) -> Self::Iterator { + match self { + V1DeserializedSection::Plaintext(p) => p.iter_data_elements(), + V1DeserializedSection::Decrypted(d) => d.contents().iter_data_elements(), + } + } +} + /// The level of integrity protection in an encrypted section #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum VerificationMode {
diff --git a/nearby/presence/np_adv/src/extended/deserialize/section/intermediate/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/section/intermediate/mod.rs index f998299..707b593 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/section/intermediate/mod.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/section/intermediate/mod.rs
@@ -29,14 +29,13 @@ DataElementParsingIterator, Section, SectionMic, }, salt::MultiSalt, - NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, + NP_V1_ADV_MAX_SECTION_COUNT, }, header::V1AdvHeader, }; use crypto_provider::CryptoProvider; use nom::{branch, bytes, combinator, error, multi}; -use crate::extended::deserialize::data_element::DataElementParseError; #[cfg(feature = "devtools")] use crate::{ credential::v1::V1DiscoveryCryptoMaterial, deserialization_arena::DeserializationArenaAllocator, @@ -55,14 +54,14 @@ adv_header: V1AdvHeader, adv_body: &[u8], ) -> Result< - ArrayVecOption<IntermediateSection, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT>, + ArrayVecOption<IntermediateSection, NP_V1_ADV_MAX_SECTION_COUNT>, nom::Err<error::Error<&[u8]>>, > { combinator::all_consuming(branch::alt(( // Public advertisement multi::fold_many_m_n( 1, - NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, + NP_V1_ADV_MAX_SECTION_COUNT, IntermediateSection::parser_unencrypted_section, ArrayVecOption::default, |mut acc, item| { @@ -73,7 +72,7 @@ // Encrypted advertisement multi::fold_many_m_n( 1, - NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, + NP_V1_ADV_MAX_SECTION_COUNT, IntermediateSection::parser_encrypted_with_header(adv_header), ArrayVecOption::default, |mut acc, item| { @@ -225,7 +224,7 @@ } } -impl<'adv> Section<'adv, DataElementParseError> for PlaintextSection<'adv> { +impl<'adv> Section<'adv> for PlaintextSection<'adv> { type Iterator = DataElementParsingIterator<'adv>; fn iter_data_elements(&self) -> Self::Iterator {
diff --git a/nearby/presence/np_adv/src/extended/deserialize/section/intermediate/tests.rs b/nearby/presence/np_adv/src/extended/deserialize/section/intermediate/tests.rs index 84ce763..313a906 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/section/intermediate/tests.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/section/intermediate/tests.rs
@@ -32,7 +32,7 @@ }, salt::{ShortV1Salt, SHORT_SALT_LEN}, serialize::{AdvBuilder, AdvertisementType}, - NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, V1_ENCODING_ENCRYPTED_MIC_WITH_EXTENDED_SALT_AND_TOKEN, + NP_V1_ADV_MAX_SECTION_COUNT, V1_ENCODING_ENCRYPTED_MIC_WITH_EXTENDED_SALT_AND_TOKEN, V1_ENCODING_ENCRYPTED_MIC_WITH_SHORT_SALT_AND_TOKEN, V1_ENCODING_ENCRYPTED_SIGNATURE_WITH_EXTENDED_SALT_AND_TOKEN, V1_ENCODING_UNENCRYPTED, V1_IDENTITY_TOKEN_LEN, @@ -92,7 +92,7 @@ #[test] fn do_deserialize_max_number_of_public_sections() { let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); - for _ in 0..NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT { + for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT { let mut section_builder = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap(); section_builder @@ -110,13 +110,13 @@ panic!("incorrect header"); }; let sections = parse_sections(v1_header, remaining).unwrap(); - assert_eq!(NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, sections.len()); + assert_eq!(NP_V1_ADV_MAX_SECTION_COUNT, sections.len()); } #[test] fn max_number_encrypted_sections_mic() { let mut adv_body = vec![]; - for _ in 0..NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT { + for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT { let _ = add_mic_short_salt_section_to_adv(&mut adv_body); } let adv_header = V1AdvHeader::new(0x20); @@ -126,7 +126,7 @@ #[test] fn max_number_encrypted_sections_sig() { let mut adv_body = vec![]; - for _ in 0..NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT { + for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT { let _ = add_sig_encrpyted_section(&mut adv_body, 5, &[0x55; EXTENDED_SALT_LEN]); } let adv_header = V1AdvHeader::new(0x20); @@ -346,26 +346,23 @@ // 2 sections let mut adv_body = vec![]; - // section 1 - plaintext - 9 bytes - adv_body.push(V1_ENCODING_UNENCRYPTED); - adv_body.push(6 + 3); // section len - // de 1 byte header, type 5, len 5 - adv_body.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]); - // de 2 byte header, type 6, len 1 - adv_body.extend_from_slice(&[0x81, 0x06, 0x01]); - - // section 2 - plaintext - 10 bytes - adv_body.push(V1_ENCODING_UNENCRYPTED); - adv_body.push(6 + 3); // section len - // de 1 byte header, type 5, len 5 - adv_body.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]); - // de 2 byte header, type 6, len 1 - adv_body.extend_from_slice(&[0x81, 0x06, 0x01]); + for _ in 0..=NP_V1_ADV_MAX_SECTION_COUNT { + // section 1..=MAX+1 - plaintext - 11 bytes + adv_body.push(V1_ENCODING_UNENCRYPTED); + adv_body.push(6 + 3); // section len + // de 1 byte header, type 5, len 5 + adv_body.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]); + // de 2 byte header, type 6, len 1 + adv_body.extend_from_slice(&[0x81, 0x06, 0x01]); + } let adv_header = V1AdvHeader::new(0x20); assert_eq!( - nom::Err::Error(error::Error { input: &adv_body[11..], code: error::ErrorKind::Eof }), + nom::Err::Error(error::Error { + input: &adv_body[(NP_V1_ADV_MAX_SECTION_COUNT * 11)..], + code: error::ErrorKind::Eof + }), parse_sections(adv_header, &adv_body).unwrap_err() ); } @@ -374,7 +371,7 @@ fn parse_adv_too_many_encrypted() { // 3 sections let mut adv_body = vec![]; - for _ in 0..NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT + 1 { + for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT + 1 { let _ = add_mic_short_salt_section_to_adv(&mut adv_body); } let adv_header = V1AdvHeader::new(0x20);
diff --git a/nearby/presence/np_adv/src/extended/mod.rs b/nearby/presence/np_adv/src/extended/mod.rs index 4a13ea5..128fbf1 100644 --- a/nearby/presence/np_adv/src/extended/mod.rs +++ b/nearby/presence/np_adv/src/extended/mod.rs
@@ -33,10 +33,7 @@ - 2; /// Maximum number of sections in an advertisement -pub const NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT: usize = 8; - -/// Maximum number of public sections in an advertisement -pub const NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT: usize = 1; +pub const NP_V1_ADV_MAX_SECTION_COUNT: usize = 8; /// Maximum size of a NP section, including its length header byte pub const NP_ADV_MAX_SECTION_LEN: usize = NP_ADV_MAX_SECTION_CONTENTS_LEN + 1;
diff --git a/nearby/presence/np_adv/src/extended/salt.rs b/nearby/presence/np_adv/src/extended/salt.rs index b35275f..2799173 100644 --- a/nearby/presence/np_adv/src/extended/salt.rs +++ b/nearby/presence/np_adv/src/extended/salt.rs
@@ -17,10 +17,11 @@ use nom::combinator; use crypto_provider::{aes::ctr::AesCtrNonce, CryptoProvider, CryptoRng, FromCryptoRng}; -use np_hkdf::v1_salt::ExtendedV1Salt; use crate::helpers::parse_byte_array; +pub use np_hkdf::v1_salt::ExtendedV1Salt; + /// Common behavior for V1 section salts. pub trait V1Salt: Copy + Into<MultiSalt> { /// Derive the nonce used for section encryption.
diff --git a/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs b/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs index f094992..2d3827e 100644 --- a/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs +++ b/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs
@@ -16,7 +16,6 @@ extern crate std; use super::*; -use crate::extended::serialize::section::header::SectionHeader; use crate::extended::serialize::section_tests::{fill_section_builder, DummyDataElement}; use crate::extended::V1_ENCODING_UNENCRYPTED; use crypto_provider_default::CryptoProviderImpl; @@ -95,29 +94,3 @@ } // TODO tests for other encoding types interacting with maximum possible section len - -/// A placeholder identity with a huge prefix -#[derive(Default, PartialEq, Eq, Debug)] -struct EnormousIdentity {} - -impl SectionEncoder for EnormousIdentity { - const SUFFIX_LEN: usize = 0; - const ADVERTISEMENT_TYPE: AdvertisementType = AdvertisementType::Plaintext; - type DerivedSalt = (); - - fn header(&self) -> SectionHeader { - unimplemented!("Should never be hit") - } - fn postprocess<C: CryptoProvider>( - &mut self, - _section_header_without_length: &mut [u8], - _section_len: u8, - _remaining_content_bytes: &mut [u8], - ) { - panic!("should never be called, just used for its huge prefix") - } - - fn de_salt(&self, _de_offset: DataElementOffset) -> Self::DerivedSalt { - panic!("should never be called, just used for its huge prefix") - } -}
diff --git a/nearby/presence/np_adv/src/extended/serialize/mod.rs b/nearby/presence/np_adv/src/extended/serialize/mod.rs index d94fee2..ad9acab 100644 --- a/nearby/presence/np_adv/src/extended/serialize/mod.rs +++ b/nearby/presence/np_adv/src/extended/serialize/mod.rs
@@ -109,6 +109,10 @@ //! .try_into().expect("array sizes match") //! } //! ``` + +#[cfg(feature = "std")] +extern crate std; + use core::fmt::{self, Display}; use array_view::ArrayView; @@ -118,8 +122,7 @@ use crate::extended::{ de_requires_extended_bit, de_type::DeType, serialize::section::EncodedSection, to_array_view, - DeLength, BLE_5_ADV_SVC_MAX_CONTENT_LEN, NP_ADV_MAX_SECTION_LEN, - NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, + DeLength, BLE_5_ADV_SVC_MAX_CONTENT_LEN, NP_ADV_MAX_SECTION_LEN, NP_V1_ADV_MAX_SECTION_COUNT, }; mod section; @@ -223,7 +226,7 @@ &self, section_encoder: &SE, ) -> Result<(usize, CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>), AddSectionError> { - if self.section_count >= self.advertisement_type.max_sections() { + if self.section_count >= NP_V1_ADV_MAX_SECTION_COUNT { return Err(AddSectionError::MaxSectionCountExceeded); } if self.advertisement_type != SE::ADVERTISEMENT_TYPE { @@ -267,7 +270,7 @@ pub enum AddSectionError { /// The advertisement doesn't have enough space to hold the minimum size of the section InsufficientAdvSpace, - /// The advertisement can only hold a maximum of NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT number of sections + /// The advertisement can only hold a maximum of NP_V1_ADV_MAX_SECTION_COUNT number of sections MaxSectionCountExceeded, /// An incompatible section trying to be added IncompatibleSectionType, @@ -280,7 +283,7 @@ write!(f, "The advertisement (max {BLE_5_ADV_SVC_MAX_CONTENT_LEN} bytes) doesn't have enough remaining space to hold the section") } AddSectionError::MaxSectionCountExceeded => { - write!(f, "The advertisement can only hold a maximum of {NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT} number of sections") + write!(f, "The advertisement can only hold a maximum of {NP_V1_ADV_MAX_SECTION_COUNT} number of sections") } AddSectionError::IncompatibleSectionType => { write!(f, "Public and Encrypted sections cannot be mixed in the same advertisement") @@ -289,6 +292,9 @@ } } +#[cfg(feature = "std")] +impl std::error::Error for AddSectionError {} + /// An encoded NP V1 advertisement, starting with the NP advertisement header byte. #[derive(Debug, PartialEq, Eq)] pub struct EncodedAdvertisement { @@ -316,15 +322,6 @@ Encrypted, } -impl AdvertisementType { - fn max_sections(&self) -> usize { - match self { - AdvertisementType::Plaintext => NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, - AdvertisementType::Encrypted => NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, - } - } -} - /// Derived salt for an individual data element. pub struct DeSalt { salt: ExtendedV1Salt,
diff --git a/nearby/presence/np_adv/src/extended/serialize/section/mod.rs b/nearby/presence/np_adv/src/extended/serialize/section/mod.rs index 8fb76ab..1b8a331 100644 --- a/nearby/presence/np_adv/src/extended/serialize/section/mod.rs +++ b/nearby/presence/np_adv/src/extended/serialize/section/mod.rs
@@ -12,7 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "std")] +extern crate std; + use array_view::ArrayView; +use core::{convert, fmt}; use crypto_provider::CryptoProvider; use np_hkdf::v1_salt::DataElementOffset; use sink::Sink as _; @@ -151,8 +155,8 @@ pub fn add_de<W: WriteDataElement, F: FnOnce(SE::DerivedSalt) -> W>( &mut self, build_de: F, - ) -> Result<(), AddDataElementError<()>> { - self.add_de_res(|derived_salt| Ok::<_, ()>(build_de(derived_salt))) + ) -> Result<(), AddDataElementError<convert::Infallible>> { + self.add_de_res(|derived_salt| Ok::<_, convert::Infallible>(build_de(derived_salt))) } /// Convert a section builder's contents into an encoded section. @@ -200,5 +204,19 @@ InsufficientSectionSpace, } +impl<E: fmt::Display> fmt::Display for AddDataElementError<E> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AddDataElementError::BuildDeError(e) => write!(f, "Build DE error: {}", e), + AddDataElementError::InsufficientSectionSpace => { + write!(f, "Insufficient section space") + } + } + } +} + +#[cfg(feature = "std")] +impl<E: fmt::Debug + fmt::Display> std::error::Error for AddDataElementError<E> {} + /// The encoded form of an advertisement section pub(crate) type EncodedSection = ArrayView<u8, NP_ADV_MAX_SECTION_LEN>;
diff --git a/nearby/presence/np_adv/src/extended/serialize/section_tests.rs b/nearby/presence/np_adv/src/extended/serialize/section_tests.rs index e1e90b7..9fbd039 100644 --- a/nearby/presence/np_adv/src/extended/serialize/section_tests.rs +++ b/nearby/presence/np_adv/src/extended/serialize/section_tests.rs
@@ -604,10 +604,10 @@ #[test] fn serialize_max_number_of_public_sections() { let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); - for _ in 0..NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT { + for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT { let mut section_builder = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap(); section_builder - .add_de(|_| DummyDataElement { de_type: 100_u32.into(), data: vec![0; 98] }) + .add_de(|_| DummyDataElement { de_type: 100_u32.into(), data: vec![0; 27] }) .unwrap(); section_builder.add_to_advertisement::<CryptoProviderImpl>(); }
diff --git a/nearby/presence/np_adv/src/legacy/data_elements/mod.rs b/nearby/presence/np_adv/src/legacy/data_elements/mod.rs index 5dd0587..762d752 100644 --- a/nearby/presence/np_adv/src/legacy/data_elements/mod.rs +++ b/nearby/presence/np_adv/src/legacy/data_elements/mod.rs
@@ -81,6 +81,8 @@ /// The invalid length len: DeEncodedLength, }, + /// The same de type code was encountered more than once in an advertisement + DuplicateDeTypes, /// Other parse error, e.g. the adv is truncated InvalidStructure, }
diff --git a/nearby/presence/np_adv/src/legacy/data_elements/tests.rs b/nearby/presence/np_adv/src/legacy/data_elements/tests.rs index d3f3061..a5e8fbf 100644 --- a/nearby/presence/np_adv/src/legacy/data_elements/tests.rs +++ b/nearby/presence/np_adv/src/legacy/data_elements/tests.rs
@@ -156,9 +156,11 @@ DataElementDeserializeError, DataElementSerializationBuffer, DataElementSerializeError, DeserializeDataElement, LengthMapper, SerializeDataElement, }; - use crate::legacy::deserialize::{DataElementDeserializer, LengthError, RawDataElement}; + use crate::legacy::deserialize::{ + DataElementDeserializer, Deserialized, LengthError, RawDataElement, + }; use crate::legacy::serialize::tests::helpers::{LongDataElement, ShortDataElement}; - use crate::legacy::{PacketFlavor, NP_MAX_DE_CONTENT_LEN}; + use crate::legacy::{PacketFlavor, Plaintext, NP_MAX_DE_CONTENT_LEN}; use crate::private::Sealed; /// A [DataElementDeserializer] that can deserialize the test stubs [ShortDataElement] and @@ -214,6 +216,19 @@ Long(LongDataElement), } + impl Deserialized for TestDataElement { + fn de_type_code(&self) -> DeTypeCode { + match self { + TestDataElement::Short(s) => { + <ShortDataElement as SerializeDataElement<Plaintext>>::de_type_code(s) + } + TestDataElement::Long(l) => { + <LongDataElement as SerializeDataElement<Plaintext>>::de_type_code(l) + } + } + } + } + impl From<ShortDataElement> for TestDataElement { fn from(value: ShortDataElement) -> Self { Self::Short(value)
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs index d305b57..d2ceab6 100644 --- a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs +++ b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
@@ -44,6 +44,7 @@ use crate::credential::matched::HasIdentityMatch; use crate::legacy::data_elements::actions::ActionsDataElement; use crate::legacy::data_elements::de_type::{DataElementType, DeActualLength}; +use crate::legacy::data_elements::DataElementDeserializeError::DuplicateDeTypes; use crate::legacy::Plaintext; /// exposed because the unencrypted case isn't just for intermediate: no further processing is needed pub use intermediate::UnencryptedAdvContents; @@ -121,6 +122,44 @@ } } +struct DeTypeBitFieldPosition(u16); +impl DeTypeBitFieldPosition { + fn as_u16(&self) -> u16 { + self.0 + } +} + +impl From<u16> for DeTypeBitFieldPosition { + fn from(value: u16) -> Self { + DeTypeBitFieldPosition(value) + } +} + +impl From<DeTypeCode> for DeTypeBitFieldPosition { + fn from(value: DeTypeCode) -> Self { + match value.as_u8() { + 0 => 0.into(), + 1 => 0b00000000_00000001.into(), + 2 => 0b00000000_00000010.into(), + 3 => 0b00000000_00000100.into(), + 4 => 0b00000000_00001000.into(), + 5 => 0b00000000_00010000.into(), + 6 => 0b00000000_00100000.into(), + 7 => 0b00000000_01000000.into(), + 8 => 0b00000000_10000000.into(), + 9 => 0b00000001_00000000.into(), + 10 => 0b00000010_00000000.into(), + 11 => 0b00000100_00000000.into(), + 12 => 0b00001000_00000000.into(), + 13 => 0b00010000_00000000.into(), + 14 => 0b00100000_00000000.into(), + 15 => 0b01000000_00000000.into(), + 16 => 0b10000000_00000000.into(), + _ => panic!("Invalid v0 De type code"), + } + } +} + /// The generified innards of [DeIterator] so that it's possible to also use test-only /// deserializers. #[derive(Clone, Debug, PartialEq, Eq)] @@ -128,13 +167,21 @@ /// Data to be parsed, containing a sequence of data elements in serialized /// form. data: &'d [u8], + /// Keeps track of de_types as we iterate to ensure that multiple des of the same type + /// are not present in an advertisement + already_encountered: u16, _flavor_marker: PhantomData<F>, _deser_marker: PhantomData<D>, } impl<'d, F, D> GenericDeIterator<'d, F, D> { fn new(data: &'d [u8]) -> Self { - Self { data, _flavor_marker: Default::default(), _deser_marker: Default::default() } + Self { + data, + already_encountered: 0u16, + _flavor_marker: Default::default(), + _deser_marker: Default::default(), + } } } @@ -152,6 +199,14 @@ match parse_result.finish() { Ok((rem, de)) => { + if self.already_encountered + & DeTypeBitFieldPosition::from(de.de_type_code()).as_u16() + != 0 + { + return Some(Err(DuplicateDeTypes)); + } + self.already_encountered |= + DeTypeBitFieldPosition::from(de.de_type_code()).as_u16(); self.data = rem; Some(Ok(de)) } @@ -175,6 +230,21 @@ TxPower(TxPowerDataElement), } +impl<F: PacketFlavor> Deserialized for DeserializedDataElement<F> { + fn de_type_code(&self) -> DeTypeCode { + match self { + DeserializedDataElement::Actions(_) => { + DeTypeCode::try_from(ActionsDataElement::<F>::DE_TYPE_CODE.as_u8()) + .expect("Actions type code is valid so this will always succeed") + } + DeserializedDataElement::TxPower(_) => { + DeTypeCode::try_from(TxPowerDataElement::DE_TYPE_CODE.as_u8()) + .expect("TxPower type code is valid so this will always succeed") + } + } + } +} + impl<F: PacketFlavor> DeserializedDataElement<F> { /// Returns the DE type as a u8 #[cfg(feature = "devtools")] @@ -254,13 +324,19 @@ } } +pub(in crate::legacy) trait Deserialized: + fmt::Debug + PartialEq + Eq + Clone +{ + fn de_type_code(&self) -> DeTypeCode; +} + /// Overall strategy for deserializing adv contents (once decrypted, if applicable) into data /// elements pub(in crate::legacy) trait DataElementDeserializer: Sized { - /// Disambiguates the intermediate form of a DE + /// Disambiguate the intermediate form of a DE type DeTypeDisambiguator: Copy; /// The fully deserialized form of a DE - type Deserialized<F: PacketFlavor>: fmt::Debug + PartialEq + Eq + Clone; + type Deserialized<F: PacketFlavor>: Deserialized; /// Map the encoded len found in a DE header to the actual len that should be consumed from the /// advertisement payload
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/tests/error_conditions.rs b/nearby/presence/np_adv/src/legacy/deserialize/tests/error_conditions.rs index 8230548..ace055d 100644 --- a/nearby/presence/np_adv/src/legacy/deserialize/tests/error_conditions.rs +++ b/nearby/presence/np_adv/src/legacy/deserialize/tests/error_conditions.rs
@@ -83,6 +83,26 @@ } #[test] + fn iterate_multiple_de_types_same_value() { + let input = &[0x15, 0x00, 0x15, 0x00]; + let mut it = IntermediateAdvContents::deserialize::<CryptoProviderImpl>( + V0Encoding::Unencrypted, + input, + ) + .unwrap() + .as_unencrypted() + .unwrap() + .data_elements(); + + // first element will be valid + let _ = it.next(); + // second will be an error since it is the same type as the first + let err = it.next().unwrap().unwrap_err(); + + assert_eq!(err, DataElementDeserializeError::DuplicateDeTypes); + } + + #[test] fn iterate_truncated_contents_error() { assert_deser_error( // length 3, but only 2 bytes provided
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/tests/happy_path.rs b/nearby/presence/np_adv/src/legacy/deserialize/tests/happy_path.rs index 1b07ff9..e5ae98f 100644 --- a/nearby/presence/np_adv/src/legacy/deserialize/tests/happy_path.rs +++ b/nearby/presence/np_adv/src/legacy/deserialize/tests/happy_path.rs
@@ -15,7 +15,7 @@ mod unencrypted { extern crate std; - use rand::prelude::SliceRandom; + use rand::seq::IteratorRandom; use std::{prelude::rust_2021::*, vec}; use strum::IntoEnumIterator; @@ -193,8 +193,13 @@ let mut des = Vec::new(); let mut builder = AdvBuilder::new(UnencryptedEncoder); + let mut possible_de_types = de_types.clone(); loop { - let de_type = *de_types.choose(&mut rng).unwrap(); + if possible_de_types.is_empty() { + break; + } + let (i, _) = possible_de_types.iter().enumerate().choose(&mut rng).unwrap(); + let de_type = possible_de_types.remove(i); let de = build_de(de_type, &mut rng); let add_res = add_de(&mut builder, de.clone()); @@ -257,6 +262,7 @@ mod ldt { use crate::credential::matched::HasIdentityMatch; use crate::legacy::data_elements::actions::CallTransfer; + use crate::legacy::data_elements::de_type::MAX_DE_ENCODED_LEN; use crate::{ credential::v0::V0BroadcastCredential, header::V0Encoding, @@ -264,7 +270,7 @@ data_elements::{ actions::{ActionBits, ActionsDataElement, NearbyShare}, de_type::DataElementType, - tests::test_des::{random_test_de, TestDataElementType}, + tests::test_des::TestDataElementType, tests::test_des::{TestDataElement, TestDeDeserializer}, tx_power::TxPowerDataElement, DataElementSerializationBuffer, DynamicSerializeDataElement, SerializeDataElement, @@ -288,7 +294,7 @@ use alloc::vec::Vec; use crypto_provider_default::CryptoProviderImpl; use ldt_np_adv::{V0IdentityToken, V0Salt, V0_IDENTITY_TOKEN_LEN}; - use rand::prelude::SliceRandom; + use rand::seq::IteratorRandom; use rand::Rng; use strum::IntoEnumIterator; @@ -384,8 +390,13 @@ #[test] fn random_test_des_roundtrip() { do_random_roundtrip_test::<TestDeDeserializer, _>( - TestDataElementType::iter().collect(), - random_test_de, + vec![TestDataElementType::Short], + |_, rng| { + let len = rng.gen_range(3_usize..MAX_DE_ENCODED_LEN.into()); + let mut data = vec![0; len]; + rng.fill(&mut data[..]); + TestDataElement::Short(ShortDataElement::new(data)) + }, |builder, de| builder.add_data_element(de), serialized_len, ) @@ -408,12 +419,12 @@ { let mut rng = rand_ext::seeded_rng(); - for _ in 0..10_000 { + for _ in 0..1_000 { let mut added_des = Vec::new(); let mut current_len = 0; let key_seed: [u8; 32] = rng.gen(); - let salt: ldt_np_adv::V0Salt = rng.gen::<[u8; 2]>().into(); + let salt: V0Salt = rng.gen::<[u8; 2]>().into(); let identity_token = V0IdentityToken::from(rng.gen::<[u8; V0_IDENTITY_TOKEN_LEN]>()); let broadcast_cred = V0BroadcastCredential::new(key_seed, identity_token); @@ -421,8 +432,13 @@ let mut builder = AdvBuilder::new(LdtEncoder::<CryptoProviderImpl>::new(salt, &broadcast_cred)); + let mut possible_de_types = de_types.clone(); loop { - let de_type = *de_types.choose(&mut rng).unwrap(); + if possible_de_types.is_empty() { + break; + } + let (i, _) = possible_de_types.iter().enumerate().choose(&mut rng).unwrap(); + let de_type = possible_de_types.remove(i); let de = build_de(de_type, &mut rng); if let Err(e) = add_de(&mut builder, de.clone()) { @@ -432,6 +448,7 @@ < ldt_np_adv::VALID_INPUT_LEN.start - V0_IDENTITY_TOKEN_LEN { // keep trying, not enough for LDT + possible_de_types.push(de_type); continue; } // out of room
diff --git a/nearby/presence/np_adv/src/lib.rs b/nearby/presence/np_adv/src/lib.rs index eb2ef0f..0e958fa 100644 --- a/nearby/presence/np_adv/src/lib.rs +++ b/nearby/presence/np_adv/src/lib.rs
@@ -24,7 +24,12 @@ #[cfg(any(test, feature = "alloc"))] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +use core::fmt; pub use strum; +pub use tinyvec::ArrayVec; use crate::credential::matched::MatchedCredential; use crate::extended::deserialize::{deser_decrypt_v1, V1AdvertisementContents}; @@ -125,7 +130,7 @@ } impl Debug for AdvDeserializationError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AdvDeserializationError::VersionHeaderParseError => { write!(f, "VersionHeaderParseError") @@ -135,6 +140,20 @@ } } +impl fmt::Display for AdvDeserializationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AdvDeserializationError::VersionHeaderParseError => { + write!(f, "VersionHeaderParseError") + } + AdvDeserializationError::ParseError { .. } => write!(f, "ParseError"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for AdvDeserializationError {} + /// Potentially hazardous details about deserialization errors. These error information can /// potentially expose side-channel information about the plaintext of the advertisements and/or /// the keys used to decrypt them. For any place that you avoid exposing the keys directly @@ -167,6 +186,22 @@ } } +// trivial Debug/Display that won't leak anything +impl Debug for AdvDeserializationErrorDetailsHazmat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "AdvDeserializationErrorDetailsHazmat") + } +} + +impl fmt::Display for AdvDeserializationErrorDetailsHazmat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "AdvDeserializationErrorDetailsHazmat") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for AdvDeserializationErrorDetailsHazmat {} + /// DE length is out of range (e.g. > 4 bits for encoded V0, > max DE size for actual V0, >127 for /// V1) or invalid for the relevant DE type. #[derive(Debug, PartialEq, Eq)]
diff --git a/nearby/presence/np_adv/src/tests/deser_v1_tests.rs b/nearby/presence/np_adv/src/tests/deser_v1_tests.rs index d0865c7..78b8dc4 100644 --- a/nearby/presence/np_adv/src/tests/deser_v1_tests.rs +++ b/nearby/presence/np_adv/src/tests/deser_v1_tests.rs
@@ -256,7 +256,7 @@ adv_builder: &mut AdvBuilder, ) -> Vec<SectionConfig<'a>> { let mut expected = Vec::new(); - for _ in 0..rng.gen_range(1..=NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT) { + for _ in 0..rng.gen_range(1..=NP_V1_ADV_MAX_SECTION_COUNT) { let identity = identities.pick_random_identity(&mut rng); let mode: VerificationMode = random(); let res = match mode {
diff --git a/nearby/presence/np_adv/src/tests/deser_v1_tests/error_condition.rs b/nearby/presence/np_adv/src/tests/deser_v1_tests/error_condition.rs index 9f91dbd..57292cf 100644 --- a/nearby/presence/np_adv/src/tests/deser_v1_tests/error_condition.rs +++ b/nearby/presence/np_adv/src/tests/deser_v1_tests/error_condition.rs
@@ -17,34 +17,6 @@ use rand::SeedableRng; #[test] -fn v1_multiple_plaintext_sections() { - let mut rng = StdRng::from_entropy(); - let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); - add_plaintext_section(&mut rng, &mut adv_builder).unwrap(); - - // append an extra plaintext section - let adv = [ - adv_builder.into_advertisement().as_slice(), - &[ - 0x00, // format unencrypted - 0x03, // section len - ], - &[0xDD; 3], // 3 bytes of de contents - ] - .concat(); - - let arena = deserialization_arena!(); - let cred_book = build_empty_cred_book(); - let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book); - assert_eq!( - v1_error, - AdvDeserializationError::ParseError { - details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError - } - ); -} - -#[test] fn v1_plaintext_empty_contents() { let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); let sb = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap(); @@ -197,7 +169,8 @@ // mangle the last 2 bytes of the suffix to invalidate the advertisement. // the identity should still correctly match let adv_len = adv.len(); - adv[adv_len - 2..].copy_from_slice(&[0xFF; 2]); + adv[adv_len - 2] = adv[adv_len - 2].wrapping_add(1); + adv[adv_len - 1] = adv[adv_len - 1].wrapping_sub(1); }, ) } @@ -248,7 +221,6 @@ let mut adv = builder.into_advertisement().as_slice().to_vec(); mangle_adv(&mut adv); - let cred_book = identities.build_cred_book::<CryptoProviderImpl>(); let arena = deserialization_arena!(); let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book);
diff --git a/nearby/presence/np_adv/src/tests/deser_v1_tests/happy_path.rs b/nearby/presence/np_adv/src/tests/deser_v1_tests/happy_path.rs index 2171bd6..ee0077d 100644 --- a/nearby/presence/np_adv/src/tests/deser_v1_tests/happy_path.rs +++ b/nearby/presence/np_adv/src/tests/deser_v1_tests/happy_path.rs
@@ -68,6 +68,30 @@ } #[test] +fn v1_multiple_plaintext_sections() { + let mut rng = StdRng::from_entropy(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + add_plaintext_section(&mut rng, &mut adv_builder).unwrap(); + + // append an extra plaintext section + let adv = [ + adv_builder.into_advertisement().as_slice(), + &[ + 0x00, // format unencrypted + 0x03, // section len + ], + &[0xDD; 3], // 3 bytes of de contents + ] + .concat(); + + let arena = deserialization_arena!(); + let cred_book = build_empty_cred_book(); + let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, &adv, &cred_book); + assert_eq!(0, v1_contents.invalid_sections_count()); + assert_eq!(2, v1_contents.sections().len()); +} + +#[test] fn v1_all_identities_resolvable_ciphertext() { let mut rng = StdRng::from_entropy(); for _ in 0..100 {
diff --git a/nearby/presence/np_adv/tests/examples_v1.rs b/nearby/presence/np_adv/tests/examples_v1.rs index 57e41fa..7b89f8d 100644 --- a/nearby/presence/np_adv/tests/examples_v1.rs +++ b/nearby/presence/np_adv/tests/examples_v1.rs
@@ -35,7 +35,7 @@ AdvBuilder, AdvertisementType, SignedEncryptedSectionEncoder, SingleTypeDataElement, UnencryptedSectionEncoder, }, - NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, + NP_V1_ADV_MAX_SECTION_COUNT, }, shared_data::TxPower, AdvDeserializationError, AdvDeserializationErrorDetailsHazmat, @@ -260,7 +260,7 @@ #[test] fn v1_deser_plaintext_over_max_sections() { let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); - for _ in 0..NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT { + for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT { let mut section_builder = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap(); section_builder .add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(7).unwrap()))
diff --git a/nearby/presence/np_adv_dynamic/Cargo.toml b/nearby/presence/np_adv_dynamic/Cargo.toml index 5eb6863..17ff642 100644 --- a/nearby/presence/np_adv_dynamic/Cargo.toml +++ b/nearby/presence/np_adv_dynamic/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/np_c_ffi/Cargo.toml b/nearby/presence/np_c_ffi/Cargo.toml index 517c2c5..e482cbe 100644 --- a/nearby/presence/np_c_ffi/Cargo.toml +++ b/nearby/presence/np_c_ffi/Cargo.toml
@@ -3,6 +3,7 @@ version = "0.1.0" edition = "2021" publish = false +license = "Apache-2.0" [dependencies] np_ffi_core = {workspace = true, default-features=false}
diff --git a/nearby/presence/np_c_ffi/include/c/np_c_ffi.h b/nearby/presence/np_c_ffi/include/c/np_c_ffi.h index d84a49a..2cef974 100644 --- a/nearby/presence/np_c_ffi/include/c/np_c_ffi.h +++ b/nearby/presence/np_c_ffi/include/c/np_c_ffi.h
@@ -689,7 +689,7 @@ * A representation of a MatchedCredential which is passable across the FFI boundary */ typedef struct { - uint32_t cred_id; + int64_t cred_id; const uint8_t *encrypted_metadata_bytes_buffer; uintptr_t encrypted_metadata_bytes_len; } np_ffi_FfiMatchedCredential; @@ -972,7 +972,7 @@ * The ID of the credential which * matched the deserialized adv */ - uint32_t cred_id; + int64_t cred_id; /** * The 14-byte legacy identity token */ @@ -1123,7 +1123,7 @@ * The ID of the credential which * matched the deserialized section. */ - uint32_t cred_id; + int64_t cred_id; /** * The 16-byte metadata key. */ @@ -1229,18 +1229,10 @@ } np_ffi_FixedSizeArray_2; /** - * A `#[repr(C)]` handle to a value of type `V1AdvertisementBuilderInternals` - */ -typedef struct { - uint64_t handle_id; -} np_ffi_V1AdvertisementBuilderHandle; - -/** * A handle to a builder for V1 advertisements. */ typedef struct { - np_ffi_AdvertisementBuilderKind kind; - np_ffi_V1AdvertisementBuilderHandle handle; + uint64_t handle_id; } np_ffi_V1AdvertisementBuilder; /** @@ -1248,7 +1240,13 @@ * the advertisement builder the section builder was originated from. */ typedef struct { + /** + * The parent advertisement builder for this section + */ np_ffi_V1AdvertisementBuilder adv_builder; + /** + * This section's index in the parent advertisement + */ uint8_t section_index; } np_ffi_V1SectionBuilder;
diff --git a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h index 0206f80..919249d 100644 --- a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h +++ b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h
@@ -457,7 +457,7 @@ /// A representation of a MatchedCredential which is passable across the FFI boundary struct FfiMatchedCredential { - uint32_t cred_id; + int64_t cred_id; const uint8_t *encrypted_metadata_bytes_buffer; uintptr_t encrypted_metadata_bytes_len; }; @@ -704,7 +704,7 @@ struct DeserializedV0IdentityDetails { /// The ID of the credential which /// matched the deserialized adv - uint32_t cred_id; + int64_t cred_id; /// The 14-byte legacy identity token uint8_t identity_token[14]; /// The 2-byte advertisement salt @@ -820,7 +820,7 @@ V1VerificationMode verification_mode; /// The ID of the credential which /// matched the deserialized section. - uint32_t cred_id; + int64_t cred_id; /// The 16-byte metadata key. uint8_t identity_token[16]; }; @@ -900,21 +900,17 @@ uint8_t identity_token[14]; }; -/// A `#[repr(C)]` handle to a value of type `V1AdvertisementBuilderInternals` -struct V1AdvertisementBuilderHandle { - uint64_t handle_id; -}; - /// A handle to a builder for V1 advertisements. struct V1AdvertisementBuilder { - AdvertisementBuilderKind kind; - V1AdvertisementBuilderHandle handle; + uint64_t handle_id; }; /// A handle to a builder for V1 sections. This is not a unique handle; it is the same handle as /// the advertisement builder the section builder was originated from. struct V1SectionBuilder { + /// The parent advertisement builder for this section V1AdvertisementBuilder adv_builder; + /// This section's index in the parent advertisement uint8_t section_index; };
diff --git a/nearby/presence/np_c_ffi/src/credentials.rs b/nearby/presence/np_c_ffi/src/credentials.rs index d03f0f6..0c8a316 100644 --- a/nearby/presence/np_c_ffi/src/credentials.rs +++ b/nearby/presence/np_c_ffi/src/credentials.rs
@@ -153,7 +153,7 @@ /// A representation of a MatchedCredential which is passable across the FFI boundary #[repr(C)] pub struct FfiMatchedCredential { - cred_id: u32, + cred_id: i64, encrypted_metadata_bytes_buffer: *const u8, encrypted_metadata_bytes_len: usize, }
diff --git a/nearby/presence/np_cpp_ffi/tests/v0_encrypted_deserialization_tests.cc b/nearby/presence/np_cpp_ffi/tests/v0_encrypted_deserialization_tests.cc index bd1f771..37d2746 100644 --- a/nearby/presence/np_cpp_ffi/tests/v0_encrypted_deserialization_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/v0_encrypted_deserialization_tests.cc
@@ -166,6 +166,6 @@ // Make sure the correct credential matches auto identity_details = payload.TryGetIdentityDetails(); ASSERT_TRUE(identity_details.ok()); - ASSERT_EQ(identity_details->cred_id, 456u); + ASSERT_EQ(identity_details->cred_id, 456); } // NOLINTEND(readability-magic-numbers)
diff --git a/nearby/presence/np_cpp_ffi/tests/v1_encrypted_deserialization_tests.cc b/nearby/presence/np_cpp_ffi/tests/v1_encrypted_deserialization_tests.cc index 65b9730..69a042d 100644 --- a/nearby/presence/np_cpp_ffi/tests/v1_encrypted_deserialization_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/v1_encrypted_deserialization_tests.cc
@@ -59,7 +59,7 @@ auto identity_details = section->GetIdentityDetails(); ASSERT_TRUE(identity_details.ok()); - ASSERT_EQ(identity_details->cred_id, 123U); + ASSERT_EQ(identity_details->cred_id, 123); ASSERT_EQ(identity_details->verification_mode, nearby_protocol::V1VerificationMode::Signature);
diff --git a/nearby/presence/np_ed25519/Cargo.toml b/nearby/presence/np_ed25519/Cargo.toml index bd42c65..5addbb1 100644 --- a/nearby/presence/np_ed25519/Cargo.toml +++ b/nearby/presence/np_ed25519/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/np_ffi_core/Cargo.toml b/nearby/presence/np_ffi_core/Cargo.toml index 7c3c112..4faebe6 100644 --- a/nearby/presence/np_ffi_core/Cargo.toml +++ b/nearby/presence/np_ffi_core/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true @@ -25,3 +26,5 @@ default = ["rustcrypto"] rustcrypto = ["crypto_provider_default/rustcrypto", "crypto_provider_default/std"] boringssl = ["crypto_provider_default/boringssl"] +testing = ["np_adv/testing"] +std = []
diff --git a/nearby/presence/np_ffi_core/src/common.rs b/nearby/presence/np_ffi_core/src/common.rs index da7cb5c..607a6af 100644 --- a/nearby/presence/np_ffi_core/src/common.rs +++ b/nearby/presence/np_ffi_core/src/common.rs
@@ -219,7 +219,7 @@ /// ArrayView, which is assumed to be trusted to be /// properly initialized, and with a size-bound /// under 255 bytes. - pub(crate) fn from_array_view(array_view: ArrayView<u8, N>) -> Self { + pub fn from_array_view(array_view: ArrayView<u8, N>) -> Self { let (len, bytes) = array_view.into_raw_parts(); let len = len as u8; Self { len, bytes } @@ -267,22 +267,3 @@ /// in an entirely unexpected way. #[derive(Debug)] pub struct InvalidStackDataStructure; - -/// Error raised when attempting to cast an enum to -/// one of its variants, but the value is actually -/// of a different variant than the requested one. -pub struct EnumCastError { - pub(crate) projection_method_name: String, - pub(crate) variant_enum_name: String, - pub(crate) variant_type_name: String, -} - -impl core::fmt::Debug for EnumCastError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "Attempted to cast a non-{} to a {} via {}", - &self.variant_enum_name, &self.variant_type_name, &self.projection_method_name - ) - } -}
diff --git a/nearby/presence/np_ffi_core/src/credentials.rs b/nearby/presence/np_ffi_core/src/credentials.rs index 1ad303f..4f6c3cd 100644 --- a/nearby/presence/np_ffi_core/src/credentials.rs +++ b/nearby/presence/np_ffi_core/src/credentials.rs
@@ -100,24 +100,24 @@ /// the FFI boundary. #[derive(Debug, Clone)] pub struct MatchedCredential { - cred_id: u32, + cred_id: i64, encrypted_metadata_bytes: Arc<[u8]>, } impl MatchedCredential { /// Constructs a new matched credential from the given match-id - /// (some arbitrary `u32` identifier) and encrypted metadata bytes, + /// (some arbitrary `i64` identifier) and encrypted metadata bytes, /// copied from the given slice. - pub fn new(cred_id: u32, encrypted_metadata_bytes: &[u8]) -> Self { + pub fn new(cred_id: i64, encrypted_metadata_bytes: &[u8]) -> Self { Self::from_arc_bytes(cred_id, encrypted_metadata_bytes.to_vec().into()) } /// Constructs a new matched credential from the given match-id /// (some arbitrary `u32` identifier) and encrypted metadata bytes. - pub fn from_arc_bytes(cred_id: u32, encrypted_metadata_bytes: Arc<[u8]>) -> Self { + pub fn from_arc_bytes(cred_id: i64, encrypted_metadata_bytes: Arc<[u8]>) -> Self { Self { cred_id, encrypted_metadata_bytes } } /// Gets the pre-specified numerical identifier for this matched-credential. - pub(crate) fn id(&self) -> u32 { + pub(crate) fn id(&self) -> i64 { self.cred_id } }
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v0.rs b/nearby/presence/np_ffi_core/src/deserialize/v0.rs index 79528a0..67d798d 100644 --- a/nearby/presence/np_ffi_core/src/deserialize/v0.rs +++ b/nearby/presence/np_ffi_core/src/deserialize/v0.rs
@@ -184,7 +184,7 @@ pub struct DeserializedV0IdentityDetails { /// The ID of the credential which /// matched the deserialized adv - cred_id: u32, + cred_id: i64, /// The 14-byte legacy identity token identity_token: [u8; 14], /// The 2-byte advertisement salt @@ -193,7 +193,7 @@ impl DeserializedV0IdentityDetails { pub(crate) fn new( - cred_id: u32, + cred_id: i64, salt: ldt_np_adv::V0Salt, identity_token: ldt_np_adv::V0IdentityToken, ) -> Self { @@ -201,7 +201,7 @@ Self { cred_id, salt, identity_token: identity_token.bytes() } } /// Returns the ID of the credential which matched the deserialized adv - pub fn cred_id(&self) -> u32 { + pub fn cred_id(&self) -> i64 { self.cred_id } /// Returns the 14-byte legacy metadata key
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v1.rs b/nearby/presence/np_ffi_core/src/deserialize/v1.rs index 6007975..849abab 100644 --- a/nearby/presence/np_ffi_core/src/deserialize/v1.rs +++ b/nearby/presence/np_ffi_core/src/deserialize/v1.rs
@@ -545,14 +545,14 @@ verification_mode: V1VerificationMode, /// The ID of the credential which /// matched the deserialized section. - cred_id: u32, + cred_id: i64, /// The 16-byte metadata key. identity_token: [u8; 16], } impl DeserializedV1IdentityDetails { pub(crate) fn new( - cred_id: u32, + cred_id: i64, verification_mode: np_adv::extended::deserialize::VerificationMode, identity_token: np_adv::extended::V1IdentityToken, ) -> Self { @@ -560,7 +560,7 @@ Self { cred_id, verification_mode, identity_token: identity_token.into_bytes() } } /// Returns the ID of the credential which matched the deserialized section. - pub fn cred_id(&self) -> u32 { + pub fn cred_id(&self) -> i64 { self.cred_id } /// Returns the verification mode (MIC/Signature) employed for the decrypted section.
diff --git a/nearby/presence/np_ffi_core/src/lib.rs b/nearby/presence/np_ffi_core/src/lib.rs index 2c03ea5..0fd7f1a 100644 --- a/nearby/presence/np_ffi_core/src/lib.rs +++ b/nearby/presence/np_ffi_core/src/lib.rs
@@ -15,9 +15,6 @@ //! Core functionality common to all NP Rust FFI layers #[macro_use] -extern crate lazy_static; - -#[macro_use] pub mod utils; pub mod common; pub mod credentials;
diff --git a/nearby/presence/np_ffi_core/src/serialize/v1.rs b/nearby/presence/np_ffi_core/src/serialize/v1.rs index c87446f..1191ac1 100644 --- a/nearby/presence/np_ffi_core/src/serialize/v1.rs +++ b/nearby/presence/np_ffi_core/src/serialize/v1.rs
@@ -20,15 +20,25 @@ use crate::v1::V1VerificationMode; use crypto_provider_default::CryptoProviderImpl; use handle_map::{declare_handle_map, HandleLike, HandleMapFullError}; +use np_adv::extended::serialize::AdvertisementType; + +#[cfg(feature = "testing")] +use np_adv::extended::salt::MultiSalt; /// A handle to a builder for V1 advertisements. -#[derive(Clone, Copy)] #[repr(C)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct V1AdvertisementBuilder { - kind: AdvertisementBuilderKind, - handle: V1AdvertisementBuilderHandle, + handle_id: u64, } +declare_handle_map!( + advertisement_builder, + crate::common::default_handle_map_dimensions(), + super::V1AdvertisementBuilder, + super::V1AdvertisementBuilderInternals +); + impl V1AdvertisementBuilder { /// Attempts to create a builder for a new public section within this advertisement, returning /// an owned handle to the newly-created section builder if successful. @@ -41,11 +51,6 @@ self.section_builder_internals(|internals| internals.public_section_builder()) } - /// Gets the kind of advertisement builder (public/encrypted) - pub fn kind(&self) -> AdvertisementBuilderKind { - self.kind - } - /// Attempts to create a builder for a new encrypted section within this advertisement, /// returning an owned handle to the newly-created section builder if successful. /// @@ -69,12 +74,38 @@ }) } + /// Attempts to create a builder for a new encrypted section within this advertisement, + /// returning an owned handle to the newly-created section builder if successful. + /// + /// The identity details for the new section builder may be specified + /// via providing the broadcast credential data, the kind of encrypted + /// identity being broadcast (private/trusted/provisioned). + /// + /// + /// The section will be encrypted with the MIC verification mode using the given salt. + /// + /// This method may fail if there is another currently-active + /// section builder for the same advertisement builder, if the + /// kind of section being added does not match the advertisement + /// type (public/encrypted), or if the section would not manage + /// to fit within the enclosing advertisement. + #[cfg(feature = "testing")] + pub fn mic_custom_salt_section_builder( + &self, + broadcast_cred: V1BroadcastCredential, + salt: MultiSalt, + ) -> CreateV1SectionBuilderResult { + self.section_builder_internals(move |internals| { + internals.mic_custom_salt_section_builder(broadcast_cred, salt) + }) + } + /// Attempts to serialize the contents of the advertisement builder behind this handle to /// bytes. Assuming that the handle is valid, this operation will always take ownership of the /// handle and result in the contents behind the advertisement builder handle being /// deallocated. pub fn into_advertisement(self) -> SerializeV1AdvertisementResult { - match self.handle.deallocate() { + match self.deallocate() { Ok(adv_builder) => adv_builder.into_advertisement(), Err(_) => SerializeV1AdvertisementResult::InvalidAdvertisementBuilderHandle, } @@ -86,7 +117,7 @@ &mut V1AdvertisementBuilderInternals, ) -> Result<usize, SectionBuilderError>, ) -> CreateV1SectionBuilderResult { - match self.handle.get_mut() { + match self.get_mut() { Ok(mut adv_builder_write_guard) => { match builder_supplier(&mut adv_builder_write_guard) { Ok(section_index) => CreateV1SectionBuilderResult::Success(V1SectionBuilder { @@ -157,9 +188,10 @@ pub fn create_v1_advertisement_builder( kind: AdvertisementBuilderKind, ) -> CreateV1AdvertisementBuilderResult { - V1AdvertisementBuilderHandle::allocate(move || V1AdvertisementBuilderInternals::new(kind)) - .map(|handle| V1AdvertisementBuilder { kind, handle }) - .into() + V1AdvertisementBuilder::allocate(move || { + V1AdvertisementBuilderInternals::new(kind.as_internal_v1()) + }) + .into() } pub(crate) enum V1AdvertisementBuilderState { @@ -256,8 +288,7 @@ } impl V1AdvertisementBuilderInternals { - pub(crate) fn new(kind: AdvertisementBuilderKind) -> Self { - let adv_type = kind.as_internal_v1(); + pub(crate) fn new(adv_type: AdvertisementType) -> Self { let builder = np_adv::extended::serialize::AdvBuilder::new(adv_type); let builder = builder.into(); let state = Some(V1AdvertisementBuilderState::Advertisement(builder)); @@ -324,6 +355,21 @@ }; self.section_builder_internal(encoder) } + + #[cfg(feature = "testing")] + pub(crate) fn mic_custom_salt_section_builder( + &mut self, + broadcast_cred: V1BroadcastCredential, + salt: MultiSalt, + ) -> Result<usize, SectionBuilderError> { + let internal_broadcast_cred = broadcast_cred.into_internal(); + let encoder = np_adv::extended::serialize::MicEncryptedSectionEncoder::new_with_salt::< + CryptoProviderImpl, + >(salt, &internal_broadcast_cred); + let encoder = np_adv_dynamic::extended::BoxedEncoder::MicEncrypted(encoder); + self.section_builder_internal(encoder) + } + fn into_advertisement(self) -> SerializeV1AdvertisementResult { match self.state { Some(V1AdvertisementBuilderState::Advertisement(adv_builder)) => { @@ -335,20 +381,6 @@ } } -/// A `#[repr(C)]` handle to a value of type `V1AdvertisementBuilderInternals` -#[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq)] -struct V1AdvertisementBuilderHandle { - handle_id: u64, -} - -declare_handle_map!( - advertisement_builder, - crate::common::default_handle_map_dimensions(), - super::V1AdvertisementBuilderHandle, - super::V1AdvertisementBuilderInternals -); - /// Discriminant for `CreateV1SectionBuilderResult` #[derive(Copy, Clone)] #[repr(u8)] @@ -493,8 +525,10 @@ #[derive(Clone, Copy)] #[repr(C)] pub struct V1SectionBuilder { - adv_builder: V1AdvertisementBuilder, - section_index: u8, + /// The parent advertisement builder for this section + pub adv_builder: V1AdvertisementBuilder, + /// This section's index in the parent advertisement + pub section_index: u8, } impl V1SectionBuilder { @@ -502,7 +536,7 @@ /// to a section builder to the containing advertisement it /// originated from. pub fn add_to_advertisement(self) -> AddV1SectionToAdvertisementResult { - match self.adv_builder.handle.get_mut() { + match self.adv_builder.get_mut() { Ok(mut adv_builder) => { let state = adv_builder.state.take(); match state { @@ -579,7 +613,7 @@ ) -> R, invalid_handle_error: R, ) -> R { - match self.adv_builder.handle.get_mut() { + match self.adv_builder.get_mut() { Ok(mut adv_builder) => { match adv_builder.state.as_mut() { Some(V1AdvertisementBuilderState::Section(ref mut section_builder)) => { @@ -641,8 +675,7 @@ #[allow(clippy::expect_used)] #[test] fn test_build_section_fails_with_outstanding_section() { - let mut adv_builder = - V1AdvertisementBuilderInternals::new(AdvertisementBuilderKind::Encrypted); + let mut adv_builder = V1AdvertisementBuilderInternals::new(AdvertisementType::Encrypted); let adv_builder_state = adv_builder.state.as_ref().expect("Adv builder state should be present.");
diff --git a/nearby/presence/np_hkdf/Cargo.toml b/nearby/presence/np_hkdf/Cargo.toml index cd7fb4c..4923d61 100644 --- a/nearby/presence/np_hkdf/Cargo.toml +++ b/nearby/presence/np_hkdf/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/np_java_ffi/AndroidManifest.xml b/nearby/presence/np_java_ffi/AndroidManifest.xml new file mode 100644 index 0000000..04ad56d --- /dev/null +++ b/nearby/presence/np_java_ffi/AndroidManifest.xml
@@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2024 Google LLC + + Licensed 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.nearby.presence.rust"> + <uses-sdk android:minSdkVersion="33" /> +</manifest>
diff --git a/nearby/presence/np_java_ffi/Cargo.toml b/nearby/presence/np_java_ffi/Cargo.toml index 556b481..134dfbb 100644 --- a/nearby/presence/np_java_ffi/Cargo.toml +++ b/nearby/presence/np_java_ffi/Cargo.toml
@@ -3,11 +3,16 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true +[features] +testing = ["np_ffi_core/testing"] + [dependencies] +array_view.workspace = true handle_map.workspace = true np_adv.workspace = true np_ffi_core.workspace = true
diff --git a/nearby/presence/np_java_ffi/build.gradle.kts b/nearby/presence/np_java_ffi/build.gradle.kts index c502cdb..6fe9e82 100644 --- a/nearby/presence/np_java_ffi/build.gradle.kts +++ b/nearby/presence/np_java_ffi/build.gradle.kts
@@ -14,8 +14,17 @@ * limitations under the License. */ +import net.ltgt.gradle.errorprone.errorprone; + plugins { `java-library` + id("net.ltgt.errorprone") version "4.0.0" +} + +java { + // Gradle JUnit test finder doesn't support Java 21 class files. + sourceCompatibility = JavaVersion.VERSION_1_9 + targetCompatibility = JavaVersion.VERSION_1_9 } repositories { @@ -25,13 +34,14 @@ dependencies { implementation("androidx.annotation:annotation:1.6.0") + implementation("com.google.errorprone:error_prone_core:2.28.0") + implementation("org.checkerframework:checker-qual:3.45.0") // JUnit Test Support - testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") + testImplementation("junit:junit:4.13") testImplementation("com.google.truth:truth:1.1.4") + testImplementation("com.google.code.gson:gson:2.10.1") testImplementation("org.mockito:mockito-core:5.+") - testImplementation("org.mockito:mockito-junit-jupiter:5.+") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") } // Flattened directory layout @@ -49,7 +59,7 @@ } tasks.test { - useJUnitPlatform() + useJUnit() jvmArgs = mutableListOf( // libnp_java_ffi.so "-Djava.library.path=$projectDir/../../target/debug", @@ -57,3 +67,18 @@ "-XX:+EnableDynamicAgentLoading" ) } + +tasks.withType<JavaCompile>().configureEach { + options.errorprone { + error("CheckReturnValue") + error("UnnecessaryStaticImport") + error("WildcardImport") + error("RemoveUnusedImports") + error("ReturnMissingNullable") + error("FieldMissingNullable") + error("AnnotationPosition") + error("CheckedExceptionNotThrown") + error("NonFinalStaticField") + error("InvalidLink") + } +}
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/CooperativeCleaner.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/CooperativeCleaner.java new file mode 100644 index 0000000..e420f71 --- /dev/null +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/CooperativeCleaner.java
@@ -0,0 +1,174 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed 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 com.google.android.nearby.presence.rust; + +import androidx.annotation.GuardedBy; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.HashSet; +import org.checkerframework.checker.initialization.qual.Initialized; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; + +/** + * A similar class to {@link java.lang.ref.Cleaner} that doesn't require a dedicated thread. + * + * <p>To avoid using a thread for the reference queue, clients are expected to call {@link + * Registration#close()} on the object returned from {@link register(Object, Runnable)}. This will + * perform two actions: + * + * <ol> + * <li>Run the cleanup actions for any registered objects that have been finaized without {@code + * close()} being called. + * <li>Run the cleanup action for the object being closed. + * </ol> + * + * <p>The application should keep a reference to {@code this} instance to avoid the cleanup actions + * themselves being garbage collected! + */ +public class CooperativeCleaner { + + /** + * An object registration with this CooperativeCleaner. Clients should call the {@link + * Registration#close()} method when the registered object is ready to be cleaned up. + * + * @see CooperativeCleaner documentation for more details. + */ + public class Registration implements AutoCloseable { + private final CleanableReference ref; + + private Registration(CleanableReference ref) { + this.ref = ref; + } + + @Override + public void close() { + // Cleanup any queued objects first (Cooperation!) + processQueuedObjects(); + + // Run cleanup for the referenced object. + ref.performCleanup(); + } + } + + /** + * Objects that are not closed will be posted to this reference queue by the garbage collector. + */ + @GuardedBy("queue") + private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); + + /** + * Holds strong references to {@link CleanableReference} instances so that the reference objects + * are not garbage collected. + */ + @GuardedBy("refs") + private final HashSet<CleanableReference> refs = new HashSet<>(); + + /** + * Register an object for cleanup. The object will be cleaned up when the returned {@code + * Registration} is closed or sometime after the object has been finalized. The returned value + * should always be closed if possible. If it is closed the {@code cleanupAction} will be run on + * the thread that called {@code close()}; otherwise, it will be run during {@code close()} for a + * different object. In all cases {@code cleanupAction} will be run exactly once. + * + * <p>The given {@code cleanupAction} <b>SHOULD NOT</b> hold a reference to {@code object}. + * Holding such a reference would mean that {@code object} is always reachable and it will never + * be garbaged collected. + */ + public Registration register(@UnknownInitialization Object object, Runnable cleanupAction) { + // The cast here is safe because it is impossible to get object back out of this reference. + // PhantomReference will always return null for {@link PhantomReference#get()}. We don't care + // about the initialization state of the object; rather, we only care about the identity of the + // object. Likewise, an actual reference to object is never stored by this class; that would + // stop the object from ever being garbage collected. + @SuppressWarnings("nullness:cast.unsafe") + CleanableReference ref = new CleanableReference((@Initialized Object) object, cleanupAction); + + // Keep a copy of the reference in {@code refs} to avoid being garbage collected. + synchronized (refs) { + refs.add(ref); + } + + return new Registration(ref); + } + + /** Gets the number of objects that are currently registered with this cleaner. */ + @VisibleForTesting + public int getRegisteredObjectCount() { + synchronized (refs) { + return refs.size(); + } + } + + /** Gets the next {@link CleanableReference} from the queue if there is one. */ + @Nullable + private CleanableReference pollQueue() { + synchronized (queue) { + return (CleanableReference) queue.poll(); + } + } + + /** Process cleanup actions for all objects in the reference queue. */ + @VisibleForTesting + public void processQueuedObjects() { + CleanableReference ref; + while ((ref = pollQueue()) != null) { + ref.performCleanup(); + } + } + + /** + * Stores the {@code cleanupAction} and tracks the lifecycle of {@code object}. {@link + * #performCleanup()} can be called early to perform the cleanup action. + */ + private class CleanableReference extends PhantomReference<Object> { + @Nullable private Runnable cleanupAction; + + private CleanableReference(Object object, Runnable cleanupAction) { + // This reference will be enqueued onto {@code queue} if object becomes phantom-reachable and + // {@link #clear()} hasn't been called. + super(object, queue); + + this.cleanupAction = cleanupAction; + } + + private void performCleanup() { + // Cleanup this object + @Nullable Runnable action; + synchronized (refs) { + // Do not post this to the reference queue if this was called before the object was + // posted. + this.clear(); + + // This object is no longer needed, so we can remove our reference to it so that it may be + // garbaged collected. + refs.remove(this); + + // Make sure that cleanupAction is only run once by taking the value before running it + action = this.cleanupAction; + this.cleanupAction = null; + } + + if (action != null) { + // Run cleanupAction outside of the lock so that multiple cleanups may occur on separate + // threads. + action.run(); + } + } + } +}
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializeResult.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializeResult.java index c0dc922..5be7f61 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializeResult.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializeResult.java
@@ -34,6 +34,7 @@ public final class DeserializeResult<M extends CredentialBook.MatchedMetadata> implements AutoCloseable { + /** The kind of result. Negative values are errors and do not contain advertisement instances. */ @IntDef({ Kind.UNKNOWN_ERROR, Kind.V0_ADVERTISEMENT, @@ -52,8 +53,8 @@ return kind <= 0; } - private final @Kind int kind; - private final @Nullable DeserializedAdvertisement advertisement; + @Kind private final int kind; + @Nullable private final DeserializedAdvertisement advertisement; /** Create a DeserializeResult containing an error code */ /* package */ DeserializeResult(@Kind int errorKind) { @@ -66,13 +67,13 @@ } /** Create a DeserializeResult containing a V0 advertisement */ - /* package */ DeserializeResult(DeserializedV0Advertisement advertisement) { + /* package */ DeserializeResult(DeserializedV0Advertisement<M> advertisement) { this.kind = Kind.V0_ADVERTISEMENT; this.advertisement = advertisement; } /** Create a DeserializeResult containing a V1 advertisement */ - /* package */ DeserializeResult(DeserializedV1Advertisement advertisement) { + /* package */ DeserializeResult(DeserializedV1Advertisement<M> advertisement) { this.kind = Kind.V1_ADVERTISEMENT; this.advertisement = advertisement; } @@ -93,6 +94,7 @@ * * @return the contained V0 advertisement or {@code null} if not present */ + @SuppressWarnings("unchecked") @Nullable public DeserializedV0Advertisement<M> getAsV0() { if (this.kind != Kind.V0_ADVERTISEMENT) { @@ -106,12 +108,13 @@ * * @return the contained V1 advertisement or {@code null} if not present */ + @SuppressWarnings("unchecked") @Nullable public DeserializedV1Advertisement<M> getAsV1() { if (this.kind != Kind.V1_ADVERTISEMENT) { return null; } - return (DeserializedV1Advertisement) this.advertisement; + return (DeserializedV1Advertisement<M>) this.advertisement; } /** Closes the contained advertisement if it exists. */
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV0Advertisement.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV0Advertisement.java index cb557c5..7df838e 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV0Advertisement.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV0Advertisement.java
@@ -19,6 +19,7 @@ import androidx.annotation.Nullable; import com.google.android.nearby.presence.rust.credential.CredentialBook; import java.util.Iterator; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; /** * A deserialized V0 advertisement. This class is backed by native data behind the {@link V0Payload} @@ -32,9 +33,9 @@ } private final int numDataElements; - private final @Nullable V0Payload payload; - private final @IdentityKind int identity; - private final CredentialBook<M> credentialBook; + @Nullable private final V0Payload payload; + @IdentityKind private final int identity; + @Nullable private final CredentialBook<M> credentialBook; /** Create an illegible instance with the given error identity. */ /* package */ DeserializedV0Advertisement(@IdentityKind int illegibleIdentity) { @@ -79,8 +80,9 @@ } /** Throws {@code IllegalStateException} if this advertisement is not legible. */ + @EnsuresNonNull({"payload", "credentialBook"}) private void ensureLegible(String action) { - if (!isLegible()) { + if (!isLegible() || payload == null || credentialBook == null) { throw new IllegalStateException( String.format("Cannot %s for non-legible advertisement", action)); } @@ -122,7 +124,11 @@ /** Gets all the data elements for iteration. */ public Iterable<V0DataElement> getDataElements() { - return () -> new DataElementIterator(payload, numDataElements); + ensureLegible("get data elements"); + return () -> { + ensureLegible("get data element iterator"); + return new DataElementIterator(payload, numDataElements); + }; } /** Visits all the data elements with the given visitor. */ @@ -179,6 +185,7 @@ */ @Nullable public byte[] getDecryptedMetadata() { + ensureLegible("get decrypted metadata"); return payload.getDecryptedMetadata(); } @@ -196,7 +203,7 @@ @Override public boolean hasNext() { - return position < (numDataElements - 1); + return position < numDataElements; } @Override
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV1Advertisement.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV1Advertisement.java index 8f4c3a3..cbe855a 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV1Advertisement.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV1Advertisement.java
@@ -20,7 +20,7 @@ import java.util.Iterator; /** - * A deserialized V0 advertisement. This class is backed by native data behind the {@link + * A deserialized V1 advertisement. This class is backed by native data behind the {@link * LegibleV1Sections} handle. If this class is closed then the underlying handle will be closed too. * Methods on this class should not be called if {@link #close()} has already been called. */ @@ -67,13 +67,13 @@ } /** Get an iterable of this advertisement's legible sections. */ - public Iterable<DeserializedV1Section> getSections() { - return () -> new SectionIterator(numLegibleSections, legibleSections, credentialBook); + public Iterable<DeserializedV1Section<M>> getSections() { + return () -> new SectionIterator<>(numLegibleSections, legibleSections, credentialBook); } /** Iterator instance for sections in DeserializedV1Advertisement. */ private static final class SectionIterator<M extends CredentialBook.MatchedMetadata> - implements Iterator<DeserializedV1Section> { + implements Iterator<DeserializedV1Section<M>> { private final LegibleV1Sections legibleSections; private final int numSections; private final CredentialBook<M> credentialBook; @@ -89,11 +89,11 @@ @Override public boolean hasNext() { - return position < (numSections - 1); + return position < numSections; } @Override - public DeserializedV1Section next() { + public DeserializedV1Section<M> next() { return legibleSections.getSection(position++, credentialBook); } }
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV1Section.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV1Section.java index d320963..3c26e9e 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV1Section.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/DeserializedV1Section.java
@@ -16,27 +16,21 @@ package com.google.android.nearby.presence.rust; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.nearby.presence.rust.credential.CredentialBook; -import java.lang.annotation.Retention; import java.util.Iterator; +/** + * A section from a deserialized V1 advertisement. This object is valid for only as long as the + * containing advertisement object is valid; it is backed by the same {@link LegibleV1Sections} + * instance. + */ public final class DeserializedV1Section<M extends CredentialBook.MatchedMetadata> { - @IntDef({VerificationMode.MIC, VerificationMode.SIGNATURE}) - @Retention(SOURCE) - public @interface VerificationMode { - public static final int MIC = 0; - public static final int SIGNATURE = 1; - } - private final LegibleV1Sections legibleSections; private final int legibleSectionsIndex; private final int numDataElements; - private final @IdentityKind int identityTag; + @IdentityKind private final int identityTag; private final CredentialBook<M> credentialBook; /* package */ DeserializedV1Section( @@ -66,7 +60,6 @@ /** * Gets the data element at the given {@code index} in this advertisement. * - * @throws IllegalStateException if the advertisement is not legible ({@link #isLegible()}). * @throws IndexOutOfBoundsException if the index is invalid */ public V1DataElement getDataElement(int index) { @@ -148,7 +141,7 @@ @Override public boolean hasNext() { - return position < (numDataElements - 1); + return position < numDataElements; } @Override
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/LegibleV1Sections.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/LegibleV1Sections.java index 4a089c6..a5ac931 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/LegibleV1Sections.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/LegibleV1Sections.java
@@ -16,11 +16,8 @@ package com.google.android.nearby.presence.rust; -import static com.google.android.nearby.presence.rust.DeserializedV1Section.VerificationMode; - import androidx.annotation.Nullable; import com.google.android.nearby.presence.rust.credential.CredentialBook; -import java.lang.ref.Cleaner; import java.util.Arrays; /** Internal handle for a V1 deserialized advertisement. */ @@ -71,7 +68,7 @@ } /** Create a LegibleV1Sections handle from the raw handle id. */ - /* package-visible */ LegibleV1Sections(long handleId, Cleaner cleaner) { + private LegibleV1Sections(long handleId, CooperativeCleaner cleaner) { super(handleId, cleaner, LegibleV1Sections::deallocate); } @@ -84,11 +81,11 @@ */ public <M extends CredentialBook.MatchedMetadata> DeserializedV1Section<M> getSection( int index, CredentialBook<M> credentialBook) { - DeserializedV1Section section = nativeGetSection(index, credentialBook); + DeserializedV1Section<M> section = nativeGetSection(index, credentialBook); if (section == null) { throw new IndexOutOfBoundsException(); } - return (DeserializedV1Section<M>) section; + return section; } /** @@ -118,7 +115,8 @@ } @Nullable - private native DeserializedV1Section nativeGetSection(int index, CredentialBook credentialBook); + private native <M extends CredentialBook.MatchedMetadata> + DeserializedV1Section<M> nativeGetSection(int index, CredentialBook<M> credentialBook); @Nullable private native V1DataElement nativeGetSectionDataElement(int sectionIndex, int deIndex);
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/NpAdv.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/NpAdv.java index a349ebc..b7639ab 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/NpAdv.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/NpAdv.java
@@ -18,14 +18,10 @@ import androidx.annotation.Nullable; import com.google.android.nearby.presence.rust.credential.CredentialBook; -import java.lang.ref.Cleaner; /** * The main entrypoint to the library. * - * <p>On Android call {@link #setCleaner} with a {@code SystemCleaner} instance before any other - * method to avoid creating a new cleaner thread. - * * <h3>Supported Features:</h3> * * <ul> @@ -43,7 +39,12 @@ System.loadLibrary(LIBRARY_NAME); } - private static @Nullable Cleaner CLEANER = null; + // This is effectively an injected variable, but without depending on a DI implementation. + @SuppressWarnings("NonFinalStaticField") + @Nullable + private static CooperativeCleaner cleaner = null; + + private NpAdv() {} /** * Deserialize a Nearby Presence advertisement from its service data bytes. @@ -55,40 +56,26 @@ public static <M extends CredentialBook.MatchedMetadata> DeserializeResult<M> deserializeAdvertisement( byte[] serviceData, CredentialBook<M> credentialBook) { - DeserializeResult result = nativeDeserializeAdvertisement(serviceData, credentialBook); + DeserializeResult<M> result = nativeDeserializeAdvertisement(serviceData, credentialBook); if (result == null) { - result = new DeserializeResult(DeserializeResult.Kind.UNKNOWN_ERROR); + result = new DeserializeResult<M>(DeserializeResult.Kind.UNKNOWN_ERROR); } return result; } /** - * Get the currently configured cleaner. If a cleaner is not configured, a new one will be created - * via the {@link Cleaner#create()} factory function. + * Get the currently configured cleaner. If a cleaner is not configured, a new one will be + * created. */ - public static synchronized Cleaner getCleaner() { - if (CLEANER == null) { - CLEANER = Cleaner.create(); + public static synchronized CooperativeCleaner getCleaner() { + if (cleaner == null) { + cleaner = new CooperativeCleaner(); } - return CLEANER; - } - - /** - * Configure a {@link Cleaner} to be used by this library. This cleaner will be used to ensure - * that {@link OwnedHandle} instances are properly freed. Since each {@code Cleaner} instance - * requires its own thread; this can be used to share a {@code Cleaner} instance to reduce the - * number of threads used. - * - * <p>On Android the {@code SystemCleaner} should be provided. - */ - @Nullable - public static synchronized Cleaner setCleaner(Cleaner cleaner) { - Cleaner old = CLEANER; - CLEANER = cleaner; - return old; + return cleaner; } @Nullable - private static native DeserializeResult nativeDeserializeAdvertisement( - byte[] serviceData, CredentialBook credentialBook); + private static native <M extends CredentialBook.MatchedMetadata> + DeserializeResult<M> nativeDeserializeAdvertisement( + byte[] serviceData, CredentialBook<M> credentialBook); }
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/OwnedHandle.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/OwnedHandle.java index c505f81..a1c1a79 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/OwnedHandle.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/OwnedHandle.java
@@ -16,8 +16,8 @@ package com.google.android.nearby.presence.rust; +import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; -import java.lang.ref.Cleaner; /** * A handle to natively-allocated object with lifetime control. This is a {@code Handle} that also @@ -25,7 +25,8 @@ * * <p>Users should call {@link OwnedHandle#close()} when finished with this handle to free the * native resources. This can be automatically done when using try-with-resources. If neither are - * use the handle will still be closed when it is garbage collected. + * use the handle will still be freed sometime after this object has been garbage collected by a + * subsequent call to {@link OwnedHandle#close()} on another {@code OwnedHandle} instance. */ public abstract class OwnedHandle extends Handle implements AutoCloseable { @@ -34,6 +35,11 @@ * * <p>This MUST not hold a reference to the {@link OwnedHandle} instance. Do not implement this on * your subclass; however, it may be implemented by a method reference to a static method. + * + * <p>This destructor will be run unless {@link OwnedHandle#leak()} is called. It will be run on + * the thread that calls {@link OwnedHandle#close()} if the handle is closed. If neither leak nor + * close are called and this handle is garbage collected, then it will be run on an unspecified + * thread. See {@link CooperativeCleaner}. */ public interface Destructor { void deallocate(long handleId); @@ -47,6 +53,7 @@ } private final CleanupAction cleanupAction; + private final CooperativeCleaner.Registration cleanerRegistration; /** * Create a new instance and register it with the given cleaner. @@ -55,22 +62,23 @@ * @param cleaner The cleaner thread to register with for GC cleanup * @param destructor The destructor to run when this handle is closed */ - protected OwnedHandle(long handleId, Cleaner cleaner, Destructor destructor) { + protected OwnedHandle(long handleId, CooperativeCleaner cleaner, Destructor destructor) { super(handleId); this.cleanupAction = new CleanupAction(handleId, destructor); - cleaner.register(this, this.cleanupAction); + cleanerRegistration = cleaner.register(this, this.cleanupAction); } /** Leak this handle. The associated native object will not be deallocated. */ protected final void leak() { - this.cleanupAction.leak(); + cleanupAction.leak(); + close(); } /** Implement AutoCloseable for try-with-resources support */ @Override public final void close() { - this.cleanupAction.cleanupFromCloseable(); + cleanerRegistration.close(); } /** @@ -79,8 +87,10 @@ */ private static final class CleanupAction implements Runnable { private final long handleId; - private @Nullable Destructor destructor; - private boolean freed = false; + + @GuardedBy("this") + @Nullable + private Destructor destructor; public CleanupAction(long handleId, Destructor destructor) { this.handleId = handleId; @@ -89,7 +99,9 @@ /** Skip performing cleanup and leak the object instead */ private void leak() { - this.destructor = null; + synchronized (this) { + this.destructor = null; + } } /** @@ -101,29 +113,26 @@ * @return {@code true} if the destructor was called. */ private boolean deallocate() { - if (this.destructor != null) { - this.destructor.deallocate(this.handleId); - this.destructor = null; + // Take the destructor if present + Destructor destructor = null; + synchronized (this) { + if (this.destructor != null) { + destructor = this.destructor; + this.destructor = null; + } + } + + // Run destructor if it was present + if (destructor != null) { + destructor.deallocate(handleId); return true; } return false; } - /** - * Perform the cleanup action. This is separate from {@link #run()} so that we can track if the - * handle was manually closed or if it was cleaned up via the {@link Cleaner}. - */ - public void cleanupFromCloseable() { - if (!deallocate()) { - // FUTURE: log that OwnedHandle#close() was called multiple times. - } - } - @Override public void run() { - if (deallocate()) { - // FUTURE: log that OwnedHandle#close() was not called. - } + boolean unused = deallocate(); } } }
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/SerializationException.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/SerializationException.java index 4fa4f4f..741b030 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/SerializationException.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/SerializationException.java
@@ -29,6 +29,19 @@ } } + public static final class UnclosedActiveSectionException extends RuntimeException { + public UnclosedActiveSectionException() { + super( + "There is currently an active section builder which must be finished before continuing"); + } + } + + public static final class InvalidSectionKindException extends RuntimeException { + public InvalidSectionKindException() { + super("A section of this kind is not allowed in this advertisement"); + } + } + public static final class InsufficientSpaceException extends SerializationException { public InsufficientSpaceException() { super("There isn't enough space remaining in the advertisement");
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0AdvertisementBuilder.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0AdvertisementBuilder.java index e597303..0061fff 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0AdvertisementBuilder.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0AdvertisementBuilder.java
@@ -18,12 +18,15 @@ import com.google.android.nearby.presence.rust.SerializationException.InsufficientSpaceException; import com.google.android.nearby.presence.rust.SerializationException.InvalidDataElementException; +import com.google.android.nearby.presence.rust.V0DataElement.TxPower; +import com.google.android.nearby.presence.rust.V0DataElement.V0Actions; import com.google.android.nearby.presence.rust.credential.V0BroadcastCredential; -import java.lang.ref.Cleaner; +import com.google.errorprone.annotations.CanIgnoreReturnValue; /** - * A builder for V0 advertisements. Create a new instance with {@link #newPublic()} for a public - * advertisement or {@link #newEncrypted()} for an encrypted advertisement. + * A builder for V0 advertisements. Create a new instance with {@link + * V0AdvertisementBuilder#newPublic()} for a public advertisement or {@link + * V0AdvertisementBuilder#newEncrypted()} for an encrypted advertisement. */ public final class V0AdvertisementBuilder implements AutoCloseable { @@ -32,19 +35,19 @@ return newPublic(NpAdv.getCleaner()); } + /** Create a builder for a public advertisement with a specific cleaner. */ + private static V0AdvertisementBuilder newPublic(CooperativeCleaner cleaner) { + return new V0AdvertisementBuilder(new V0BuilderHandle(cleaner)); + } + /** Create a builder for an encrypted advertisement. */ public static V0AdvertisementBuilder newEncrypted(V0BroadcastCredential credential, byte[] salt) { return newEncrypted(NpAdv.getCleaner(), credential, salt); } - /** Create a builder for a public advertisement with a specific Cleaner. */ - public static V0AdvertisementBuilder newPublic(Cleaner cleaner) { - return new V0AdvertisementBuilder(new V0BuilderHandle(cleaner)); - } - - /** Create a builder for an encrypted advertisement with a specific Cleaner. */ - public static V0AdvertisementBuilder newEncrypted( - Cleaner cleaner, V0BroadcastCredential credential, byte[] salt) { + /** Create a builder for an encrypted advertisement with a specific cleaner. */ + private static V0AdvertisementBuilder newEncrypted( + CooperativeCleaner cleaner, V0BroadcastCredential credential, byte[] salt) { return new V0AdvertisementBuilder(new V0BuilderHandle(cleaner, credential, salt)); } @@ -62,8 +65,11 @@ * of range) * @throws InsufficientSpaceException when the data element will not fit in the remaining space. */ - public void addDataElement(V0DataElement dataElement) throws InsufficientSpaceException { + @CanIgnoreReturnValue + public V0AdvertisementBuilder addDataElement(V0DataElement dataElement) + throws InsufficientSpaceException { builder.addDataElement(dataElement); + return this; } /** @@ -89,26 +95,59 @@ System.loadLibrary(NpAdv.LIBRARY_NAME); } - public V0BuilderHandle(Cleaner cleaner) { + /** Create a public builder. */ + public V0BuilderHandle(CooperativeCleaner cleaner) { super(allocatePublic(), cleaner, V0BuilderHandle::deallocate); } - public V0BuilderHandle(Cleaner cleaner, V0BroadcastCredential credential, byte[] salt) { + /** Create an encrypted builder. */ + public V0BuilderHandle( + CooperativeCleaner cleaner, V0BroadcastCredential credential, byte[] salt) { super(allocatePrivate(credential, salt), cleaner, V0BuilderHandle::deallocate); } - public void addDataElement(V0DataElement dataElement) { - // Call the appropriate native add call based on the data element type. - dataElement.visit( - new V0DataElement.Visitor() { - public void visitTxPower(V0DataElement.TxPower txPower) { - nativeAddTxPowerDataElement(txPower); - } + private class AddDataElementVisitor implements V0DataElement.Visitor { + @Override + public void visitTxPower(TxPower txPower) { + try { + nativeAddTxPowerDataElement(txPower); + } catch (InsufficientSpaceException ise) { + throw new SmuggledInsufficientSpaceException(ise); + } + } - public void visitV0Actions(V0DataElement.V0Actions v0Actions) { - nativeAddV0ActionsDataElement(v0Actions); - } - }); + @Override + public void visitV0Actions(V0Actions actions) { + try { + nativeAddV0ActionsDataElement(actions); + } catch (InsufficientSpaceException ise) { + throw new SmuggledInsufficientSpaceException(ise); + } + } + } + + /** + * Helper to smuggle {@link InsufficientSpaceException} (a checked exception) through APIs that + * don't support checked exceptions. + */ + private static class SmuggledInsufficientSpaceException extends RuntimeException { + private final InsufficientSpaceException ise; + + public SmuggledInsufficientSpaceException(InsufficientSpaceException ise) { + this.ise = ise; + } + + public void throwChecked() throws InsufficientSpaceException { + throw ise; + } + } + + public void addDataElement(V0DataElement dataElement) throws InsufficientSpaceException { + try { + dataElement.visit(new AddDataElementVisitor()); + } catch (SmuggledInsufficientSpaceException ex) { + ex.throwChecked(); + } } public byte[] build() @@ -123,9 +162,11 @@ private static native long allocatePrivate(V0BroadcastCredential credential, byte[] salt); - private native void nativeAddTxPowerDataElement(V0DataElement.TxPower txPower); + private native void nativeAddTxPowerDataElement(TxPower txPower) + throws InsufficientSpaceException; - private native void nativeAddV0ActionsDataElement(V0DataElement.V0Actions v0Actions); + private native void nativeAddV0ActionsDataElement(V0Actions v0Actions) + throws InsufficientSpaceException; private native byte[] nativeBuild() throws SerializationException.LdtEncryptionException,
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0DataElement.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0DataElement.java index c2c6325..0ee2157 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0DataElement.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0DataElement.java
@@ -52,6 +52,7 @@ return txPower; } + @Override public void visit(Visitor v) { v.visitTxPower(this); } @@ -68,7 +69,7 @@ }) @Retention(SOURCE) public @interface V0ActionType { - // NOTE: Copied from `np_ffi_core::v0::BooleanActionType`. + // NOTE: Copied from `np_ffi_core::v0::ActionType`. public static final int CROSS_DEV_SDK = 1; public static final int CALL_TRANSFER = 4; public static final int ACTIVE_UNLOCK = 8; @@ -83,7 +84,7 @@ System.loadLibrary(NpAdv.LIBRARY_NAME); } - private final @IdentityKind int identityKind; + @IdentityKind private final int identityKind; private final int actionBits; /** @@ -99,8 +100,8 @@ this(identityKind, nativeMergeActions(identityKind, actions)); } - /** Used by native code. */ - V0Actions(@IdentityKind int identityKind, int actionBits) { + /** Used by native code. This must be private to avoid being confused with the above method. */ + private V0Actions(@IdentityKind int identityKind, int actionBits) { this.identityKind = identityKind; this.actionBits = actionBits; } @@ -119,6 +120,7 @@ return nativeHasAction(identityKind, actionBits, action); } + @Override public void visit(Visitor v) { v.visitV0Actions(this); }
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0Payload.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0Payload.java index e6abea8..588a2ca 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0Payload.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V0Payload.java
@@ -17,7 +17,6 @@ package com.google.android.nearby.presence.rust; import androidx.annotation.Nullable; -import java.lang.ref.Cleaner; import java.util.Arrays; /** @@ -68,7 +67,7 @@ } /** Create a V0Payload handle from the raw handle id. */ - /* package-visible */ V0Payload(long handleId, Cleaner cleaner) { + private V0Payload(long handleId, CooperativeCleaner cleaner) { super(handleId, cleaner, V0Payload::deallocate); }
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1AdvertisementBuilder.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1AdvertisementBuilder.java new file mode 100644 index 0000000..058494b --- /dev/null +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1AdvertisementBuilder.java
@@ -0,0 +1,196 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed 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 com.google.android.nearby.presence.rust; + +import androidx.annotation.VisibleForTesting; +import com.google.android.nearby.presence.rust.SerializationException.InsufficientSpaceException; +import com.google.android.nearby.presence.rust.credential.V1BroadcastCredential; + +/** + * A builder for V1 advertisements. Create a new instance with {@link + * V1AdvertisementBuilder#newPublic()} for a public advertisement or {@link + * V1AdvertisementBuilder#newEncrypted()} for an encrypted advertisement. + * + * <p>Public advertisements may only contain public sections. Encrypted advertisements may only + * contain encrypted sections. Use {@link V1AdvertisementBuilder#addPublicSection()} and {@link + * V1AdvertisementBuilder#addEncryptedSection()} to add sections to the advertisement. Only one + * section may be built at a time. + */ +public final class V1AdvertisementBuilder implements AutoCloseable { + + /** Create a builder for a public advertisement. */ + public static V1AdvertisementBuilder newPublic() { + return newPublic(NpAdv.getCleaner()); + } + + /** Create a builder for a public advertisement with a specific cleaner. */ + private static V1AdvertisementBuilder newPublic(CooperativeCleaner cleaner) { + return new V1AdvertisementBuilder(new V1BuilderHandle(cleaner, false)); + } + + /** Create a builder for an encrypted advertisement. */ + public static V1AdvertisementBuilder newEncrypted() { + return newEncrypted(NpAdv.getCleaner()); + } + + /** Create a builder for an encrypted advertisement with a specific cleaner. */ + private static V1AdvertisementBuilder newEncrypted(CooperativeCleaner cleaner) { + return new V1AdvertisementBuilder(new V1BuilderHandle(cleaner, true)); + } + + @VisibleForTesting final V1BuilderHandle builder; + + private V1AdvertisementBuilder(V1BuilderHandle builder) { + this.builder = builder; + } + + /** + * Adds a public section to this advertisement. This is only valid on public advertisements. + * + * @throws UnclosedActiveSectionException when there is already an active section builder. + * @throws InvalidHandleException if this builder has already been closed. + * @throws InvalidSectionKindException if this advertisement builder is not a public advertisement + * builder. + * @throws InsufficientSpaceException if this advertisement does not have enough space for a new + * section + */ + public V1SectionBuilder addPublicSection() throws InsufficientSpaceException { + return builder.addPublicSection(); + } + + /** + * Adds an encrypted section to this advertisement. This is only valid on encrypted + * advertisements. + * + * @throws UnclosedActiveSectionException when there is already an active section builder. + * @throws InvalidHandleException if this builder has already been closed. + * @throws InvalidSectionKindException if this advertisement builder is not an encrypted + * advertisement builder. + * @throws InsufficientSpaceException if this advertisement does not have enough space for a new + * section + */ + public V1SectionBuilder addEncryptedSection( + V1BroadcastCredential credential, @VerificationMode int verificationMode) + throws InsufficientSpaceException { + return builder.addEncryptedSection(credential, verificationMode); + } + + /** + * Build this advertisement into a byte buffer. This will consume the builder when called. + * + * @throws UnclosedActiveSectionException when there is an active section builder. + */ + public byte[] build() { + return builder.build(); + } + + /** Deallocates this section builder. */ + @Override + public void close() { + builder.close(); + } + + /** Internal builder handle object. */ + static final class V1BuilderHandle extends OwnedHandle { + static { + System.loadLibrary(NpAdv.LIBRARY_NAME); + } + + public V1BuilderHandle(CooperativeCleaner cleaner, boolean encrypted) { + super(allocate(encrypted), cleaner, V1BuilderHandle::deallocate); + } + + public V1SectionBuilder addPublicSection() throws InsufficientSpaceException { + return nativeAddPublicSection(); + } + + public V1SectionBuilder addEncryptedSection( + V1BroadcastCredential credential, @VerificationMode int verificationMode) + throws InsufficientSpaceException { + return nativeAddEncryptedSection(credential, verificationMode); + } + + private class AddDataElementVisitor implements V1DataElement.Visitor { + private final int section; + + private AddDataElementVisitor(int section) { + this.section = section; + } + + @Override + public void visitGeneric(V1DataElement.Generic generic) { + try { + nativeAddGenericDataElement(section, generic); + } catch (InsufficientSpaceException ise) { + throw new SmuggledInsufficientSpaceException(ise); + } + } + } + + /** + * Helper to smuggle {@link InsufficientSpaceException} (a checked exception) through APIs that + * don't support checked exceptions. + */ + private static class SmuggledInsufficientSpaceException extends RuntimeException { + private final InsufficientSpaceException ise; + + public SmuggledInsufficientSpaceException(InsufficientSpaceException ise) { + this.ise = ise; + } + + public void throwChecked() throws InsufficientSpaceException { + throw ise; + } + } + + public void addDataElement(int section, V1DataElement dataElement) + throws InsufficientSpaceException { + try { + dataElement.visit(new AddDataElementVisitor(section)); + } catch (SmuggledInsufficientSpaceException ex) { + ex.throwChecked(); + } + } + + public void finishSection(int section) { + nativeFinishSection(section); + } + + public byte[] build() { + // `nativeBuild` takes ownership so we leak the Java side here. + leak(); + return nativeBuild(); + } + + private static native long allocate(boolean encrypted); + + private native V1SectionBuilder nativeAddPublicSection() throws InsufficientSpaceException; + + private native V1SectionBuilder nativeAddEncryptedSection( + V1BroadcastCredential credential, @VerificationMode int verificationMode) + throws InsufficientSpaceException; + + private native void nativeAddGenericDataElement(int section, V1DataElement.Generic generic) + throws InsufficientSpaceException; + + private native void nativeFinishSection(int section); + + private native byte[] nativeBuild(); + + private static native void deallocate(long handleId); + } +}
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1DataElement.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1DataElement.java index b52c7bb..e7d921e 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1DataElement.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1DataElement.java
@@ -56,6 +56,7 @@ return Arrays.copyOf(data, data.length); } + @Override public void visit(Visitor v) { v.visitGeneric(this); }
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1SectionBuilder.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1SectionBuilder.java new file mode 100644 index 0000000..d2d62cc --- /dev/null +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/V1SectionBuilder.java
@@ -0,0 +1,83 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed 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 com.google.android.nearby.presence.rust; + +import com.google.android.nearby.presence.rust.SerializationException.InsufficientSpaceException; +import com.google.android.nearby.presence.rust.V1AdvertisementBuilder.V1BuilderHandle; +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +/** + * A builder for V1 advertisement sections. Create a new instance with {@link + * V1AdvertisementBuilder#addPublicSection()} for a public section or {@link + * V1AdvertisementBuilder#addEncryptedSection()} for an encrypted section. + */ +public final class V1SectionBuilder implements AutoCloseable { + + private final V1BuilderHandle builder; + private final int section; + + /** + * Whether {@link #finishSection()} has already been called by the client. This is used to avoid + * double-finishing when used in a try-with-resources block. + */ + private boolean finishCalled = false; + + // Native used + @SuppressWarnings("UnusedMethod") + private V1SectionBuilder(V1BuilderHandle builder, int section) { + this.builder = builder; + this.section = section; + } + + /** + * Add a data element to the advertisement. If it cannot be added an exception will be thrown. A + * thrown exception will not invalidate this builder. + * + * @throws InvalidDataElementException when the given data element is not valid (e.g. value out of + * range, too large, etc.) + * @throws InsufficientSpaceException when the data element will not fit in the remaining space. + */ + @CanIgnoreReturnValue + public V1SectionBuilder addDataElement(V1DataElement dataElement) + throws InsufficientSpaceException { + builder.addDataElement(section, dataElement); + return this; + } + + /** + * Finishes writing the current seciton. This will consume the section builder when called. + * + * @throws InvalidHandleException if called multiple times or if the parent advertisement has been + * closed. + */ + public void finishSection() { + builder.finishSection(section); + finishCalled = true; + } + + /** + * Close this section builder. This will {@link #finishSection()} if the builder hasn't been + * finished yet. + */ + @Override + public void close() { + // Finish the section if the user has not already done so explicitly + if (!finishCalled) { + finishSection(); + } + } +}
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/VerificationMode.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/VerificationMode.java new file mode 100644 index 0000000..ba7953b --- /dev/null +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/VerificationMode.java
@@ -0,0 +1,29 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed 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 com.google.android.nearby.presence.rust; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import androidx.annotation.IntDef; +import java.lang.annotation.Retention; + +@IntDef({VerificationMode.MIC, VerificationMode.SIGNATURE}) +@Retention(SOURCE) +public @interface VerificationMode { + public static final int MIC = 0; + public static final int SIGNATURE = 1; +}
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/CredentialBook.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/CredentialBook.java index 953c6e9..e264f13 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/CredentialBook.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/CredentialBook.java
@@ -17,11 +17,17 @@ package com.google.android.nearby.presence.rust.credential; import androidx.annotation.Nullable; +import com.google.android.nearby.presence.rust.CooperativeCleaner; import com.google.android.nearby.presence.rust.NpAdv; import com.google.android.nearby.presence.rust.OwnedHandle; -import java.lang.ref.Cleaner; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; +/** + * A set of credentials used for deserialization. Create a credential book using the builder from + * {@link #builder()}. An empty credential book ({@link #empty()}) can be used to deserialize public + * advertisements and sections. + */ public final class CredentialBook<M extends CredentialBook.MatchedMetadata> extends OwnedHandle { static { @@ -68,7 +74,7 @@ * {@code getMatchedMetadata()} method on their {@code DeserializedAdvertisement} instance. */ public static final class Builder<M extends MatchedMetadata> { - private Cleaner cleaner; + private CooperativeCleaner cleaner; private CredentialSlab slab; /** @@ -79,19 +85,22 @@ /** * Create a builder instance. The {@link CredentialBook#builder()} factory method can be used to - * create a {@code Builder} with the default {@link Cleaner}. This can fail if there isn't room - * to create a new {@code CredentialSlab} handle. + * create a {@code Builder} with the default {@link CooperativeCleaner}. + * + * <p>This can fail if there isn't room to create a new {@code CredentialSlab} handle with + * {@link OwnedHandle.NoSpaceLeftException}. * * @param cleaner The cleaner instance to use for the {@link CredentialSlab} and {@code * CredentialBook}. */ - public Builder(Cleaner cleaner) { + private Builder(CooperativeCleaner cleaner) { this.cleaner = cleaner; this.slab = new CredentialSlab(cleaner); this.matchDataList = new ArrayList<>(); } /** Add a {@link V0DiscoveryCredential} to the book. */ + @CanIgnoreReturnValue public Builder<M> addDiscoveryCredential(V0DiscoveryCredential credential, M matchData) { int credIdx = matchDataList.size(); matchDataList.add(matchData); @@ -104,6 +113,7 @@ * CredentialBook.InvalidPublicKeyException} if the key inside {@code credential} is improperly * formatted. */ + @CanIgnoreReturnValue public Builder<M> addDiscoveryCredential(V1DiscoveryCredential credential, M matchData) { int credIdx = matchDataList.size(); matchDataList.add(matchData); @@ -116,7 +126,7 @@ * CredentialBook} handle. */ public CredentialBook<M> build() { - return new CredentialBook(slab, matchDataList, cleaner); + return new CredentialBook<>(slab, matchDataList, cleaner); } } @@ -140,7 +150,8 @@ * from {@code Builder}. {@code matchData} is formatted so that each credential is given an id of * the index of its metadata in {@code matchData}. */ - private CredentialBook(CredentialSlab slab, ArrayList<M> matchData, Cleaner cleaner) { + @SuppressWarnings("NonApiType") + private CredentialBook(CredentialSlab slab, ArrayList<M> matchData, CooperativeCleaner cleaner) { super(allocate(slab.move()), cleaner, CredentialBook::deallocate); this.matchData = matchData; }
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/CredentialSlab.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/CredentialSlab.java index 35fc610..29f2a08 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/CredentialSlab.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/CredentialSlab.java
@@ -16,9 +16,9 @@ package com.google.android.nearby.presence.rust.credential; +import com.google.android.nearby.presence.rust.CooperativeCleaner; import com.google.android.nearby.presence.rust.NpAdv; import com.google.android.nearby.presence.rust.OwnedHandle; -import java.lang.ref.Cleaner; /** * A {@code CredentialSlab} handle that is used to build the native-side structures for a {@link @@ -30,14 +30,16 @@ } /** Create a new {@code CredentialSlab} with the given {@code cleaner}. */ - public CredentialSlab(Cleaner cleaner) { + public CredentialSlab(CooperativeCleaner cleaner) { super(allocate(), cleaner, CredentialSlab::deallocate); } /** Add a V0 discovery credential to this slab. */ public void addDiscoveryCredential( V0DiscoveryCredential credential, int credId, byte[] encryptedMetadataBytes) { - nativeAddV0DiscoveryCredential(handleId, credential, credId, encryptedMetadataBytes); + if (!nativeAddV0DiscoveryCredential(handleId, credential, credId, encryptedMetadataBytes)) { + throw new IllegalStateException("Failed to add credential to slab"); + } } /** @@ -47,7 +49,9 @@ */ public void addDiscoveryCredential( V1DiscoveryCredential credential, int credId, byte[] encryptedMetadataBytes) { - nativeAddV1DiscoveryCredential(handleId, credential, credId, encryptedMetadataBytes); + if (!nativeAddV1DiscoveryCredential(handleId, credential, credId, encryptedMetadataBytes)) { + throw new IllegalStateException("Failed to add credential to slab"); + } } /**
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/Utils.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/Utils.java index 4f070e4..a7b8f3d 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/Utils.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/Utils.java
@@ -21,6 +21,8 @@ /** Util functions used by multiple files. */ final class Utils { + private Utils() {} + /** * Create a copy of a {@code n}-byte array of data. Will throw {@code IllegalArgumentException} if * the array is not exactly {@code n} bytes.
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V0BroadcastCredential.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V0BroadcastCredential.java index 9e8474b..d47a15a 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V0BroadcastCredential.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V0BroadcastCredential.java
@@ -19,6 +19,7 @@ import static com.google.android.nearby.presence.rust.credential.Utils.copyBytes; /** A V0 broadcast credential in a format that is ready to be passed to native code. */ +@SuppressWarnings("UnusedVariable") public final class V0BroadcastCredential { private final byte[] keySeed; private final byte[] identityToken;
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V0DiscoveryCredential.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V0DiscoveryCredential.java index 522b7d8..f2d1a41 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V0DiscoveryCredential.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V0DiscoveryCredential.java
@@ -19,6 +19,7 @@ import static com.google.android.nearby.presence.rust.credential.Utils.copyBytes; /** A V0 discovery credential in a format that is ready to be passed to native code. */ +@SuppressWarnings("UnusedVariable") public final class V0DiscoveryCredential { private final byte[] keySeed; private final byte[] identityTokenHmac;
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V1BroadcastCredential.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V1BroadcastCredential.java index 5c2547a..d9108a3 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V1BroadcastCredential.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V1BroadcastCredential.java
@@ -19,6 +19,7 @@ import static com.google.android.nearby.presence.rust.credential.Utils.copyBytes; /** A V1 broadcast credential in a format that is ready to be passed to native code. */ +@SuppressWarnings("UnusedVariable") public final class V1BroadcastCredential { private final byte[] keySeed; private final byte[] identityToken;
diff --git a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V1DiscoveryCredential.java b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V1DiscoveryCredential.java index 2fbf608..53cfcfe 100644 --- a/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V1DiscoveryCredential.java +++ b/nearby/presence/np_java_ffi/java/com/google/android/nearby/presence/rust/credential/V1DiscoveryCredential.java
@@ -19,6 +19,7 @@ import static com.google.android.nearby.presence.rust.credential.Utils.copyBytes; /** A V1 discovery credential in a format that is ready to be passed to native code. */ +@SuppressWarnings("UnusedVariable") public final class V1DiscoveryCredential { private final byte[] keySeed; private final byte[] expectedMicShortSaltIdentityTokenHmac;
diff --git a/nearby/presence/np_java_ffi/src/class.rs b/nearby/presence/np_java_ffi/src/class.rs index ea53df7..d8742cb 100644 --- a/nearby/presence/np_java_ffi/src/class.rs +++ b/nearby/presence/np_java_ffi/src/class.rs
@@ -47,8 +47,10 @@ mod v0_broadcast_credential; mod v0_discovery_credential; mod v0_payload; +mod v1_advertisement_builder; mod v1_broadcast_credential; mod v1_discovery_credential; +mod verification_mode; pub mod v0_data_element; pub mod v1_data_element; @@ -58,16 +60,18 @@ pub use deserialize_result::{DeserializeResult, DeserializeResultError}; pub use deserialized_v0_advertisement::{DeserializedV0Advertisement, V0AdvertisementError}; pub use deserialized_v1_advertisement::DeserializedV1Advertisement; -pub use deserialized_v1_section::{DeserializedV1Section, VerificationMode}; +pub use deserialized_v1_section::DeserializedV1Section; pub use handle::InvalidHandleException; pub use identity_kind::IdentityKind; pub use legible_v1_sections::LegibleV1Sections; pub use owned_handle::NoSpaceLeftException; pub use serialization_exception::{ - InsufficientSpaceException, InvalidDataElementException, LdtEncryptionException, - UnencryptedSizeException, + InsufficientSpaceException, InvalidDataElementException, InvalidSectionKindException, + LdtEncryptionException, UnclosedActiveSectionException, UnencryptedSizeException, }; pub use v0_broadcast_credential::V0BroadcastCredential; pub use v0_discovery_credential::V0DiscoveryCredential; +pub use v1_advertisement_builder::{V1BuilderHandle, V1SectionBuilder}; pub use v1_broadcast_credential::V1BroadcastCredential; pub use v1_discovery_credential::V1DiscoveryCredential; +pub use verification_mode::VerificationMode;
diff --git a/nearby/presence/np_java_ffi/src/class/credential_slab.rs b/nearby/presence/np_java_ffi/src/class/credential_slab.rs index 7e8f37d..d22dea1 100644 --- a/nearby/presence/np_java_ffi/src/class/credential_slab.rs +++ b/nearby/presence/np_java_ffi/src/class/credential_slab.rs
@@ -86,7 +86,7 @@ let core_cred = credential.get_as_core(&mut env)?; let match_data = MatchedCredential::from_arc_bytes( - cred_id as u32, + cred_id as i64, env.convert_byte_array(&encrypted_metadata_bytes)?.into(), ); @@ -122,7 +122,7 @@ let core_cred = credential.get_as_core(&mut env)?; let match_data = MatchedCredential::from_arc_bytes( - cred_id as u32, + cred_id as i64, env.convert_byte_array(&encrypted_metadata_bytes)?.into(), );
diff --git a/nearby/presence/np_java_ffi/src/class/deserialized_v1_section.rs b/nearby/presence/np_java_ffi/src/class/deserialized_v1_section.rs index 3b6b5ac..96b514b 100644 --- a/nearby/presence/np_java_ffi/src/class/deserialized_v1_section.rs +++ b/nearby/presence/np_java_ffi/src/class/deserialized_v1_section.rs
@@ -13,9 +13,8 @@ // limitations under the License. use jni::{objects::JObject, sys::jint, JNIEnv}; -use np_ffi_core::{deserialize::v1::DeserializedV1IdentityKind, v1::V1VerificationMode}; -use pourover::desc::{ClassDesc, StaticFieldDesc}; -use std::sync::RwLock; +use np_ffi_core::deserialize::v1::DeserializedV1IdentityKind; +use pourover::desc::ClassDesc; use crate::class::{CredentialBook, IdentityKind, LegibleV1Sections}; @@ -51,70 +50,3 @@ .map(Self) } } - -static VERIFICATION_MODE_CLASS: ClassDesc = ClassDesc::new( - "com/google/android/nearby/presence/rust/DeserializedV1Section$VerificationMode", -); - -/// Rust representation of `@VerificationMode`. These are `jints` on the Java side, so this type can't -/// be instantiated. -pub enum VerificationMode {} - -impl VerificationMode { - /// Convert a Rust verification mode enum to the Java `jint` representation. - pub fn value_for<'local>( - env: &mut JNIEnv<'local>, - mode: V1VerificationMode, - ) -> jni::errors::Result<jint> { - match mode { - V1VerificationMode::Mic => Self::mic(env), - V1VerificationMode::Signature => Self::signature(env), - } - } - - /// Fetch the `SIGNATURE` constant - pub fn signature<'local>(env: &mut JNIEnv<'local>) -> jni::errors::Result<jint> { - static SIGNATURE: StaticFieldDesc = VERIFICATION_MODE_CLASS.static_field("SIGNATURE", "I"); - static VALUE: RwLock<Option<jint>> = RwLock::new(None); - Self::lookup_static_value(env, &SIGNATURE, &VALUE) - } - - /// Fetch the `MIC` constant - pub fn mic<'local>(env: &mut JNIEnv<'local>) -> jni::errors::Result<jint> { - static MIC: StaticFieldDesc = VERIFICATION_MODE_CLASS.static_field("MIC", "I"); - static VALUE: RwLock<Option<jint>> = RwLock::new(None); - Self::lookup_static_value(env, &MIC, &VALUE) - } - - /// Look up the given field and cache it in the given cache. The lookup will only be performed - /// once if successful. This uses `RwLock` instead of `OnceCell` since the fallible `OnceCell` - /// APIs are nightly only. - fn lookup_static_value<'local>( - env: &mut JNIEnv<'local>, - field: &StaticFieldDesc, - cache: &RwLock<Option<jint>>, - ) -> jni::errors::Result<jint> { - use jni::signature::{JavaType, Primitive}; - - // Read from cache - if let Some(value) = *cache.read().unwrap_or_else(|poison| poison.into_inner()) { - return Ok(value); - } - - // Get exclusive access to the cache for the lookup - let mut guard = cache.write().unwrap_or_else(|poison| poison.into_inner()); - - // In case of races, only lookup the value once - if let Some(value) = *guard { - return Ok(value); - } - - let value = env - .get_static_field_unchecked(field.cls(), field, JavaType::Primitive(Primitive::Int)) - .and_then(|ret| ret.i())?; - - *guard = Some(value); - - Ok(value) - } -}
diff --git a/nearby/presence/np_java_ffi/src/class/legible_v1_sections.rs b/nearby/presence/np_java_ffi/src/class/legible_v1_sections.rs index 6bc9225..0e089ab 100644 --- a/nearby/presence/np_java_ffi/src/class/legible_v1_sections.rs +++ b/nearby/presence/np_java_ffi/src/class/legible_v1_sections.rs
@@ -102,10 +102,10 @@ pub fn construct_from_parts( env: &mut JNIEnv<'local>, verification_mode: V1VerificationMode, - credential_id: u32, + credential_id: i64, identity_token: [u8; 16], ) -> jni::errors::Result<Self> { - let verification_mode = VerificationMode::value_for(env, verification_mode)?; + let verification_mode = VerificationMode::from(verification_mode).to_java(env)?; let credential_id = credential_id as jint; let identity_token = env.byte_array_from_slice(&identity_token)?;
diff --git a/nearby/presence/np_java_ffi/src/class/serialization_exception.rs b/nearby/presence/np_java_ffi/src/class/serialization_exception.rs index 3e3235e..e021927 100644 --- a/nearby/presence/np_java_ffi/src/class/serialization_exception.rs +++ b/nearby/presence/np_java_ffi/src/class/serialization_exception.rs
@@ -57,83 +57,52 @@ } } -static INSUFFICIENT_SPACE_EXCEPTION: ClassDesc = ClassDesc::new( - "com/google/android/nearby/presence/rust/SerializationException$InsufficientSpaceException", -); +/// Helper to generate wrapper types for exception classes with no-arg constructors +macro_rules! exception_wrapper { + ($(#[$docs:meta])* $name:ident, $cls:literal) => { + $(#[$docs])* + #[repr(transparent)] + pub struct $name<Obj>(pub Obj); -/// Rust representation of `class SerializationException.InsufficientSpaceException`. -#[repr(transparent)] -pub struct InsufficientSpaceException<Obj>(pub Obj); + impl<'local> $name<JObject<'local>> { + /// Create a new instance. + pub fn construct(env: &mut JNIEnv<'local>) -> jni::errors::Result<Self> { + static CLS: ClassDesc = ClassDesc::new($cls); + pourover::call_constructor!(env, &CLS, "()V",).map(Self) + } -impl<'local> InsufficientSpaceException<JObject<'local>> { - /// Create a new instance. - pub fn construct(env: &mut JNIEnv<'local>) -> jni::errors::Result<Self> { - pourover::call_constructor!(env, &INSUFFICIENT_SPACE_EXCEPTION, "()V",).map(Self) - } + /// Create a new instance and throw it. + pub fn throw_new(env: &mut JNIEnv<'local>) -> jni::errors::Result<()> { + Self::construct(env)?.throw(env) + } + } - /// Create a new instance and throw it. - pub fn throw_new(env: &mut JNIEnv<'local>) -> jni::errors::Result<()> { - Self::construct(env)?.throw(env) + impl<'local, Obj: AsRef<JObject<'local>>> $name<Obj> { + /// Throw this exception. + pub fn throw<'env>(&self, env: &mut JNIEnv<'env>) -> jni::errors::Result<()> { + env.throw(<&JThrowable>::from(self.0.as_ref())) + } + } + } } -impl<'local, Obj: AsRef<JObject<'local>>> InsufficientSpaceException<Obj> { - /// Throw this exception. - pub fn throw<'env>(&self, env: &mut JNIEnv<'env>) -> jni::errors::Result<()> { - env.throw(<&JThrowable>::from(self.0.as_ref())) - } -} +exception_wrapper!( + /// Rust representation of `class SerializationException.UnclosedActiveSectionException`. + UnclosedActiveSectionException, "com/google/android/nearby/presence/rust/SerializationException$UnclosedActiveSectionException"); -static LDT_ENCRYPTION_EXCEPTION_CLASS: ClassDesc = ClassDesc::new( - "com/google/android/nearby/presence/rust/SerializationException$LdtEncryptionException", -); +exception_wrapper!( + /// Rust representation of `class SerializationException.InvalidSectionKindException`. + InvalidSectionKindException, "com/google/android/nearby/presence/rust/SerializationException$InvalidSectionKindException"); -/// Rust representation of `class SerializationException.LdtEncryptionException`. -#[repr(transparent)] -pub struct LdtEncryptionException<Obj>(pub Obj); +exception_wrapper!( + /// Rust representation of `class SerializationException.InsufficientSpaceException`. + InsufficientSpaceException, "com/google/android/nearby/presence/rust/SerializationException$InsufficientSpaceException"); -impl<'local> LdtEncryptionException<JObject<'local>> { - /// Create a new instance. - pub fn construct(env: &mut JNIEnv<'local>) -> jni::errors::Result<Self> { - pourover::call_constructor!(env, &LDT_ENCRYPTION_EXCEPTION_CLASS, "()V").map(Self) - } +exception_wrapper!( + /// Rust representation of `class SerializationException.LdtEncryptionException`. + LdtEncryptionException, "com/google/android/nearby/presence/rust/SerializationException$LdtEncryptionException"); - /// Create a new instance and throw it. - pub fn throw_new(env: &mut JNIEnv<'local>) -> jni::errors::Result<()> { - Self::construct(env)?.throw(env) - } -} - -impl<'local, Obj: AsRef<JObject<'local>>> LdtEncryptionException<Obj> { - /// Throw this exception. - pub fn throw<'env>(&self, env: &mut JNIEnv<'env>) -> jni::errors::Result<()> { - env.throw(<&JThrowable>::from(self.0.as_ref())) - } -} - -static UNENCRYPTED_SIZE_EXCEPTION_CLASS: ClassDesc = ClassDesc::new( - "com/google/android/nearby/presence/rust/SerializationException$UnencryptedSizeException", -); - -/// Rust representation of `class SerializationException.UnencryptedSizeException`. -#[repr(transparent)] -pub struct UnencryptedSizeException<Obj>(pub Obj); - -impl<'local> UnencryptedSizeException<JObject<'local>> { - /// Create a new instance. - pub fn construct(env: &mut JNIEnv<'local>) -> jni::errors::Result<Self> { - pourover::call_constructor!(env, &UNENCRYPTED_SIZE_EXCEPTION_CLASS, "()V").map(Self) - } - - /// Create a new instance and throw it. - pub fn throw_new(env: &mut JNIEnv<'local>) -> jni::errors::Result<()> { - Self::construct(env)?.throw(env) - } -} - -impl<'local, Obj: AsRef<JObject<'local>>> UnencryptedSizeException<Obj> { - /// Throw this exception. - pub fn throw<'env>(&self, env: &mut JNIEnv<'env>) -> jni::errors::Result<()> { - env.throw(<&JThrowable>::from(self.0.as_ref())) - } -} +exception_wrapper!( + /// Rust representation of `class SerializationException.UnencryptedSizeException`. + UnencryptedSizeException, "com/google/android/nearby/presence/rust/SerializationException$UnencryptedSizeException");
diff --git a/nearby/presence/np_java_ffi/src/class/v0_advertisement_builder.rs b/nearby/presence/np_java_ffi/src/class/v0_advertisement_builder.rs index 1ba39b6..ccb3a9e 100644 --- a/nearby/presence/np_java_ffi/src/class/v0_advertisement_builder.rs +++ b/nearby/presence/np_java_ffi/src/class/v0_advertisement_builder.rs
@@ -71,8 +71,13 @@ data_element: V0DataElement, ) -> jni::errors::Result<()> { let builder = self.as_rust_handle(env)?; - #[allow(clippy::expect_used)] - let res = builder.add_de(data_element).expect("valid data structure (created in Rust)"); + let Ok(res) = builder.add_de(data_element) else { + let _ = env.throw_new( + "java/lang/IllegalStateException", + "V0Actions is not validly constructed", + ); + return Err(jni::errors::Error::JavaException); + }; match res { AddV0DEResult::Success => {}
diff --git a/nearby/presence/np_java_ffi/src/class/v0_data_element.rs b/nearby/presence/np_java_ffi/src/class/v0_data_element.rs index 44f0636..4d05a6a 100644 --- a/nearby/presence/np_java_ffi/src/class/v0_data_element.rs +++ b/nearby/presence/np_java_ffi/src/class/v0_data_element.rs
@@ -283,5 +283,5 @@ }; } - 0 + v0_actions.as_u32() as jint }
diff --git a/nearby/presence/np_java_ffi/src/class/v0_payload.rs b/nearby/presence/np_java_ffi/src/class/v0_payload.rs index e962f5e..c54de0d 100644 --- a/nearby/presence/np_java_ffi/src/class/v0_payload.rs +++ b/nearby/presence/np_java_ffi/src/class/v0_payload.rs
@@ -43,7 +43,7 @@ /// Create an IdentityDetails instance pub fn construct_from_parts( env: &mut JNIEnv<'local>, - credential_id: u32, + credential_id: i64, identity_token: [u8; 14], salt: [u8; 2], ) -> jni::errors::Result<Self> {
diff --git a/nearby/presence/np_java_ffi/src/class/v1_advertisement_builder.rs b/nearby/presence/np_java_ffi/src/class/v1_advertisement_builder.rs new file mode 100644 index 0000000..ba1a3ed --- /dev/null +++ b/nearby/presence/np_java_ffi/src/class/v1_advertisement_builder.rs
@@ -0,0 +1,354 @@ +// Copyright 2024 Google LLC +// +// Licensed 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 handle_map::{Handle, HandleLike}; +use jni::{ + objects::{JClass, JObject}, + sys::{jboolean, jint, jlong, JNI_TRUE}, + JNIEnv, +}; +use np_ffi_core::{ + serialize::v1::{ + self, create_v1_advertisement_builder, AddV1DEResult, AddV1SectionToAdvertisementResult, + CreateV1AdvertisementBuilderResult, CreateV1SectionBuilderResult, + SerializeV1AdvertisementResult, V1AdvertisementBuilder, + }, + serialize::AdvertisementBuilderKind, +}; +use pourover::{desc::ClassDesc, jni_method}; + +use crate::class::{ + v1_data_element::Generic, InsufficientSpaceException, InvalidDataElementException, + InvalidHandleException, InvalidSectionKindException, NoSpaceLeftException, + UnclosedActiveSectionException, V1BroadcastCredential, VerificationMode, +}; + +static V1_BUILDER_HANDLE_CLASS: ClassDesc = ClassDesc::new( + "com/google/android/nearby/presence/rust/V1AdvertisementBuilder$V1BuilderHandle", +); + +/// Rust representation of `class V1AdvertisementBuilder.V1BuilderHandle`. +#[repr(transparent)] +pub struct V1BuilderHandle<Obj>(pub Obj); + +impl<'local, Obj: AsRef<JObject<'local>>> V1BuilderHandle<Obj> { + /// Get a reference to the inner `jni` crate [`JObject`]. + pub fn as_obj(&self) -> &JObject<'local> { + self.0.as_ref() + } + + /// Get the Rust [`HandleLike`] representation from this Java object. + pub fn as_rust_handle<'env>( + &self, + env: &mut JNIEnv<'env>, + ) -> jni::errors::Result<V1AdvertisementBuilder> { + let handle_id = self.get_handle_id(env)?; + Ok(V1AdvertisementBuilder::from_handle(Handle::from_id(handle_id as u64))) + } + + /// Get `long handleId` from the Java object + fn get_handle_id<'env_local>( + &self, + env: &mut JNIEnv<'env_local>, + ) -> jni::errors::Result<jlong> { + use V1_BUILDER_HANDLE_CLASS as CLS; + pourover::call_method!(env, &CLS, "getId", "()J", self.as_obj()) + } +} + +static V1_SECTION_BUILDER: ClassDesc = + ClassDesc::new("com/google/android/nearby/presence/rust/V1SectionBuilder"); + +/// Rust representation of `class V1SectionBuilder`. +#[repr(transparent)] +pub struct V1SectionBuilder<Obj>(pub Obj); + +impl<'local> V1SectionBuilder<JObject<'local>> { + /// Create a new V1SectionBuilder instance. + pub fn construct<'h>( + env: &mut JNIEnv<'local>, + handle: V1BuilderHandle<impl AsRef<JObject<'h>>>, + section: jint, + ) -> jni::errors::Result<Self> { + pourover::call_constructor!( + env, + &V1_SECTION_BUILDER, + "(Lcom/google/android/nearby/presence/rust/V1AdvertisementBuilder$V1BuilderHandle;I)V", + handle.as_obj(), + section, + ) + .map(Self) + } +} + +#[jni_method( + package = "com.google.android.nearby.presence.rust", + class = "V1AdvertisementBuilder.V1BuilderHandle" +)] +extern "system" fn allocate<'local>( + mut env: JNIEnv<'local>, + _cls: JClass<'local>, + encrypted: jboolean, +) -> jlong { + let kind = if encrypted == JNI_TRUE { + AdvertisementBuilderKind::Encrypted + } else { + AdvertisementBuilderKind::Public + }; + match create_v1_advertisement_builder(kind) { + CreateV1AdvertisementBuilderResult::Success(builder) => { + builder.get_as_handle().get_id() as jlong + } + CreateV1AdvertisementBuilderResult::NoSpaceLeft => { + let _ = NoSpaceLeftException::throw_new(&mut env); + 0 + } + } +} + +#[jni_method( + package = "com.google.android.nearby.presence.rust", + class = "V1AdvertisementBuilder.V1BuilderHandle" +)] +extern "system" fn nativeAddPublicSection<'local>( + mut env: JNIEnv<'local>, + this: V1BuilderHandle<JObject<'local>>, +) -> JObject<'local> { + let Ok(handle) = this.as_rust_handle(&mut env) else { + return JObject::null(); + }; + + let builder = match handle.public_section_builder() { + CreateV1SectionBuilderResult::Success(builder) => builder, + CreateV1SectionBuilderResult::UnclosedActiveSection => { + let _ = UnclosedActiveSectionException::throw_new(&mut env); + return JObject::null(); + } + CreateV1SectionBuilderResult::InvalidAdvertisementBuilderHandle => { + let _ = InvalidHandleException::throw_new(&mut env); + return JObject::null(); + } + CreateV1SectionBuilderResult::IdentityKindMismatch => { + let _ = InvalidSectionKindException::throw_new(&mut env); + return JObject::null(); + } + CreateV1SectionBuilderResult::NoSpaceLeft => { + let _ = InsufficientSpaceException::throw_new(&mut env); + return JObject::null(); + } + }; + + assert_eq!( + handle.get_as_handle().get_id(), + builder.adv_builder.get_as_handle().get_id(), + "Section builder must be the same handle so that we can reuse the Java handle object below" + ); + + let Ok(java_builder) = + V1SectionBuilder::construct(&mut env, this, builder.section_index.into()) + else { + return JObject::null(); + }; + + java_builder.0 +} + +#[jni_method( + package = "com.google.android.nearby.presence.rust", + class = "V1AdvertisementBuilder.V1BuilderHandle" +)] +extern "system" fn nativeAddEncryptedSection<'local>( + mut env: JNIEnv<'local>, + this: V1BuilderHandle<JObject<'local>>, + credential: V1BroadcastCredential<JObject<'local>>, + verification_mode: jint, +) -> JObject<'local> { + let Ok(handle) = this.as_rust_handle(&mut env) else { + return JObject::null(); + }; + + let Ok(credential) = credential.get_as_core(&mut env) else { + return JObject::null(); + }; + + let verification_mode = match VerificationMode::from_java(&mut env, verification_mode) { + Ok(Some(verification_mode)) => verification_mode.into(), + Ok(None) => { + let _ = env + .throw_new("java/lang/IllegalArgumentException", "Invalid @VerificationMode value"); + return JObject::null(); + } + Err(_) => return JObject::null(), + }; + + let builder = match handle.encrypted_section_builder(credential, verification_mode) { + CreateV1SectionBuilderResult::Success(builder) => builder, + CreateV1SectionBuilderResult::UnclosedActiveSection => { + let _ = UnclosedActiveSectionException::throw_new(&mut env); + return JObject::null(); + } + CreateV1SectionBuilderResult::InvalidAdvertisementBuilderHandle => { + let _ = InvalidHandleException::throw_new(&mut env); + return JObject::null(); + } + CreateV1SectionBuilderResult::IdentityKindMismatch => { + let _ = InvalidSectionKindException::throw_new(&mut env); + return JObject::null(); + } + CreateV1SectionBuilderResult::NoSpaceLeft => { + let _ = InsufficientSpaceException::throw_new(&mut env); + return JObject::null(); + } + }; + + assert_eq!( + handle.get_as_handle().get_id(), + builder.adv_builder.get_as_handle().get_id(), + "Section builder must be the same handle so that we can reuse the Java handle object below" + ); + + let Ok(java_builder) = + V1SectionBuilder::construct(&mut env, this, builder.section_index.into()) + else { + return JObject::null(); + }; + + java_builder.0 +} + +#[jni_method( + package = "com.google.android.nearby.presence.rust", + class = "V1AdvertisementBuilder.V1BuilderHandle" +)] +extern "system" fn nativeAddGenericDataElement<'local>( + mut env: JNIEnv<'local>, + this: V1BuilderHandle<JObject<'local>>, + section_index: jint, + generic_de: Generic<JObject<'local>>, +) { + let Ok(adv_builder) = this.as_rust_handle(&mut env) else { + return; + }; + + let Ok(section_index) = u8::try_from(section_index) else { + return; + }; + + let section_builder = v1::V1SectionBuilder { adv_builder, section_index }; + + let de = match generic_de.as_core_byte_buffer_de(&mut env) { + Ok(Some(de)) => de, + Ok(None) => { + let _ = env + .new_string("Data is too long") + .map(|s| env.auto_local(s)) + .and_then(|reason| InvalidDataElementException::throw_new(&mut env, &reason)); + return; + } + Err(_jni_err) => { + return; + } + }; + + match section_builder.add_127_byte_buffer_de(de) { + AddV1DEResult::Success => {} + AddV1DEResult::InvalidSectionHandle => { + let _ = InvalidHandleException::throw_new(&mut env); + } + AddV1DEResult::InsufficientSectionSpace => { + let _ = InsufficientSpaceException::throw_new(&mut env); + } + AddV1DEResult::InvalidDataElement => { + let _ = env + .new_string("Invalid generic data element") + .map(|string| env.auto_local(string)) + .and_then(|reason| InvalidDataElementException::throw_new(&mut env, &reason)); + } + } +} + +#[jni_method( + package = "com.google.android.nearby.presence.rust", + class = "V1AdvertisementBuilder.V1BuilderHandle" +)] +extern "system" fn nativeFinishSection<'local>( + mut env: JNIEnv<'local>, + this: V1BuilderHandle<JObject<'local>>, + section_index: jint, +) { + let Ok(adv_builder) = this.as_rust_handle(&mut env) else { + return; + }; + + let Ok(section_index) = u8::try_from(section_index) else { + return; + }; + + let section_builder = v1::V1SectionBuilder { adv_builder, section_index }; + + match section_builder.add_to_advertisement() { + AddV1SectionToAdvertisementResult::Success => { + // It worked. + } + AddV1SectionToAdvertisementResult::Error => { + // This case covers: + // * The handle is invalid + // * The handle is in an incorrect state (no open section) + // * The section builder is not for the currently opened section + let _ = InvalidHandleException::throw_new(&mut env); + } + } +} + +#[jni_method( + package = "com.google.android.nearby.presence.rust", + class = "V1AdvertisementBuilder.V1BuilderHandle" +)] +extern "system" fn nativeBuild<'local>( + mut env: JNIEnv<'local>, + this: V1BuilderHandle<JObject<'local>>, +) -> JObject<'local> { + let Ok(handle) = this.as_rust_handle(&mut env) else { + return JObject::null(); + }; + + match handle.into_advertisement() { + SerializeV1AdvertisementResult::Success(bytes) => { + #[allow(clippy::expect_used)] + let adv_bytes = bytes.as_slice().expect("should never be malformed from core"); + env.byte_array_from_slice(adv_bytes).map_or(JObject::null(), JObject::from) + } + SerializeV1AdvertisementResult::InvalidBuilderState => { + let _ = UnclosedActiveSectionException::throw_new(&mut env); + JObject::null() + } + SerializeV1AdvertisementResult::InvalidAdvertisementBuilderHandle => { + let _ = InvalidHandleException::throw_new(&mut env); + JObject::null() + } + } +} + +#[jni_method( + package = "com.google.android.nearby.presence.rust", + class = "V1AdvertisementBuilder.V1BuilderHandle" +)] +extern "system" fn deallocate<'local>( + _env: JNIEnv<'local>, + _cls: JClass<'local>, + handle_id: jlong, +) { + let handle = V1AdvertisementBuilder::from_handle(Handle::from_id(handle_id as u64)); + let _ = handle.deallocate(); +}
diff --git a/nearby/presence/np_java_ffi/src/class/v1_data_element.rs b/nearby/presence/np_java_ffi/src/class/v1_data_element.rs index 6401165..c404502 100644 --- a/nearby/presence/np_java_ffi/src/class/v1_data_element.rs +++ b/nearby/presence/np_java_ffi/src/class/v1_data_element.rs
@@ -14,11 +14,13 @@ //! Data Elements for v1 advertisements. See `class V1DataElement`. +use array_view::ArrayView; use jni::{ objects::{JByteArray, JObject}, sys::jlong, JNIEnv, }; +use np_ffi_core::{common::ByteBuffer, serialize::v1::V1DE127ByteBuffer}; use pourover::desc::ClassDesc; static GENERIC_CLASS: ClassDesc = @@ -48,4 +50,48 @@ ) -> jni::errors::Result<Option<Self>> { Ok(env.is_instance_of(obj.as_ref(), &GENERIC_CLASS)?.then(|| Self(obj))) } + + /// Get a reference to the inner `jni` crate [`JObject`]. + pub fn as_obj(&self) -> &JObject<'local> { + self.0.as_ref() + } + + /// Get the data element's type + pub fn get_type<'env>(&self, env: &mut JNIEnv<'env>) -> jni::errors::Result<jlong> { + pourover::call_method!(env, &GENERIC_CLASS, "getType", "()J", self.as_obj()) + } + + /// Get the data element's data. Returns `None` if the data is too long. + pub fn get_data<'env>( + &self, + env: &mut JNIEnv<'env>, + ) -> jni::errors::Result<Option<ByteBuffer<127>>> { + let data = pourover::call_method!(env, &GENERIC_CLASS, "getData", "()[B", self.as_obj())?; + let len = env.get_array_length(&data)? as usize; + + if len > 127 { + return Ok(None); + } + + // Length is validated above + #[allow(clippy::unwrap_used)] + { + let mut buffer = [0; 127]; + env.get_byte_array_region(&data, 0, buffer.get_mut(..len).unwrap())?; + let buffer = buffer.map(|b| b as u8); + Ok(Some(ByteBuffer::from_array_view(ArrayView::try_from_array(buffer, len).unwrap()))) + } + } + + /// Get the data element as a `np_ffi_core` byte buffer. Returns `None` if the data element is + /// not valid. + pub fn as_core_byte_buffer_de<'env>( + &self, + env: &mut JNIEnv<'env>, + ) -> jni::errors::Result<Option<V1DE127ByteBuffer>> { + let Some(payload) = self.get_data(env)? else { + return Ok(None); + }; + Ok(Some(V1DE127ByteBuffer { de_type: self.get_type(env)? as u32, payload })) + } }
diff --git a/nearby/presence/np_java_ffi/src/class/verification_mode.rs b/nearby/presence/np_java_ffi/src/class/verification_mode.rs new file mode 100644 index 0000000..f217207 --- /dev/null +++ b/nearby/presence/np_java_ffi/src/class/verification_mode.rs
@@ -0,0 +1,119 @@ +// Copyright 2024 Google LLC +// +// Licensed 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 jni::{sys::jint, JNIEnv}; +use np_ffi_core::v1::V1VerificationMode; +use pourover::desc::{ClassDesc, StaticFieldDesc}; +use std::sync::RwLock; + +static VERIFICATION_MODE_CLASS: ClassDesc = + ClassDesc::new("com/google/android/nearby/presence/rust/VerificationMode"); + +/// Rust representation of `@VerificationMode`. +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum VerificationMode { + /// Verification is done using the Mic scheme. + Mic, + /// Verification is done using the Signature scheme. + Signature, +} + +impl VerificationMode { + /// Convert a Java `int` to the Rust version. Will return `None` if the given `value` is not + /// valid. + pub fn from_java<'local>( + env: &mut JNIEnv<'local>, + value: jint, + ) -> jni::errors::Result<Option<Self>> { + if value == Self::mic(env)? { + Ok(Some(Self::Mic)) + } else if value == Self::signature(env)? { + Ok(Some(Self::Signature)) + } else { + Ok(None) + } + } + + /// Convert to a Java `int` value. + pub fn to_java<'local>(&self, env: &mut JNIEnv<'local>) -> jni::errors::Result<jint> { + match self { + Self::Mic => Self::mic(env), + Self::Signature => Self::signature(env), + } + } + + /// Fetch the `SIGNATURE` constant + fn signature<'local>(env: &mut JNIEnv<'local>) -> jni::errors::Result<jint> { + static SIGNATURE: StaticFieldDesc = VERIFICATION_MODE_CLASS.static_field("SIGNATURE", "I"); + static VALUE: RwLock<Option<jint>> = RwLock::new(None); + Self::lookup_static_value(env, &SIGNATURE, &VALUE) + } + + /// Fetch the `MIC` constant + fn mic<'local>(env: &mut JNIEnv<'local>) -> jni::errors::Result<jint> { + static MIC: StaticFieldDesc = VERIFICATION_MODE_CLASS.static_field("MIC", "I"); + static VALUE: RwLock<Option<jint>> = RwLock::new(None); + Self::lookup_static_value(env, &MIC, &VALUE) + } + + /// Look up the given field and cache it in the given cache. The lookup will only be performed + /// once if successful. This uses `RwLock` instead of `OnceCell` since the fallible `OnceCell` + /// APIs are nightly only. + fn lookup_static_value<'local>( + env: &mut JNIEnv<'local>, + field: &StaticFieldDesc, + cache: &RwLock<Option<jint>>, + ) -> jni::errors::Result<jint> { + use jni::signature::{JavaType, Primitive}; + + // Read from cache + if let Some(value) = *cache.read().unwrap_or_else(|poison| poison.into_inner()) { + return Ok(value); + } + + // Get exclusive access to the cache for the lookup + let mut guard = cache.write().unwrap_or_else(|poison| poison.into_inner()); + + // In case of races, only lookup the value once + if let Some(value) = *guard { + return Ok(value); + } + + let value = env + .get_static_field_unchecked(field.cls(), field, JavaType::Primitive(Primitive::Int)) + .and_then(|ret| ret.i())?; + + *guard = Some(value); + + Ok(value) + } +} + +impl From<V1VerificationMode> for VerificationMode { + fn from(mode: V1VerificationMode) -> Self { + match mode { + V1VerificationMode::Mic => Self::Mic, + V1VerificationMode::Signature => Self::Signature, + } + } +} + +impl From<VerificationMode> for V1VerificationMode { + fn from(mode: VerificationMode) -> Self { + match mode { + VerificationMode::Mic => Self::Mic, + VerificationMode::Signature => Self::Signature, + } + } +}
diff --git a/nearby/presence/np_java_ffi/src/lib.rs b/nearby/presence/np_java_ffi/src/lib.rs index 7a688e8..bd03c81 100644 --- a/nearby/presence/np_java_ffi/src/lib.rs +++ b/nearby/presence/np_java_ffi/src/lib.rs
@@ -19,3 +19,5 @@ #![allow(clippy::needless_lifetimes)] pub mod class; +#[cfg(feature = "testing")] +pub mod testing;
diff --git a/common/derive_fuzztest/fuzz/src/bin/integer_add.rs b/nearby/presence/np_java_ffi/src/testing.rs similarity index 74% rename from common/derive_fuzztest/fuzz/src/bin/integer_add.rs rename to nearby/presence/np_java_ffi/src/testing.rs index 38b8172..5717c7c 100644 --- a/common/derive_fuzztest/fuzz/src/bin/integer_add.rs +++ b/nearby/presence/np_java_ffi/src/testing.rs
@@ -12,12 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg_attr(fuzzing, no_main)] +//! Java bindings for test-only APIs -use derive_fuzztest::fuzztest; - -#[fuzztest] -pub fn test(a: u8, b: u8) { - let _ = a.checked_add(b); - // a + b; // This fails because a + b can overflow. -} +mod test_vectors;
diff --git a/nearby/presence/np_java_ffi/src/testing/test_vectors.rs b/nearby/presence/np_java_ffi/src/testing/test_vectors.rs new file mode 100644 index 0000000..f2d3079 --- /dev/null +++ b/nearby/presence/np_java_ffi/src/testing/test_vectors.rs
@@ -0,0 +1,103 @@ +// Copyright 2024 Google LLC +// +// Licensed 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 jni::{ + objects::{JByteArray, JClass, JObject}, + JNIEnv, +}; +use np_adv::extended::salt::{ExtendedV1Salt, MultiSalt, ShortV1Salt}; +use np_ffi_core::serialize::v1::CreateV1SectionBuilderResult; +use pourover::jni_method; + +use crate::class::{ + InsufficientSpaceException, InvalidHandleException, InvalidSectionKindException, + UnclosedActiveSectionException, V1BroadcastCredential, V1BuilderHandle, V1SectionBuilder, +}; + +#[jni_method(package = "com.google.android.nearby.presence.rust", class = "TestVectors")] +extern "system" fn nativeAddSaltedSection<'local>( + mut env: JNIEnv<'local>, + _cls: JClass<'local>, + j_handle: V1BuilderHandle<JObject<'local>>, + credential: V1BroadcastCredential<JObject<'local>>, + salt: JByteArray<'local>, +) -> JObject<'local> { + let Ok(handle) = j_handle.as_rust_handle(&mut env) else { + return JObject::null(); + }; + + let Ok(credential) = credential.get_as_core(&mut env) else { + return JObject::null(); + }; + + let salt = { + match env.get_array_length(&salt) { + Ok(2) => { + let mut buf = [0; 2]; + let Ok(_) = env.get_byte_array_region(&salt, 0, &mut buf[..]) else { + return JObject::null(); + }; + let buf = buf.map(|b| b as u8); + MultiSalt::Short(ShortV1Salt::from(buf)) + } + Ok(16) => { + let mut buf = [0; 16]; + let Ok(_) = env.get_byte_array_region(&salt, 0, &mut buf[..]) else { + return JObject::null(); + }; + let buf = buf.map(|b| b as u8); + MultiSalt::Extended(ExtendedV1Salt::from(buf)) + } + Ok(_) => { + let _ = env.throw_new("java/lang/RuntimeException", "Invalid salt length"); + return JObject::null(); + } + Err(_jni_err) => return JObject::null(), + } + }; + + let builder = match handle.mic_custom_salt_section_builder(credential, salt) { + CreateV1SectionBuilderResult::Success(builder) => builder, + CreateV1SectionBuilderResult::UnclosedActiveSection => { + let _ = UnclosedActiveSectionException::throw_new(&mut env); + return JObject::null(); + } + CreateV1SectionBuilderResult::InvalidAdvertisementBuilderHandle => { + let _ = InvalidHandleException::throw_new(&mut env); + return JObject::null(); + } + CreateV1SectionBuilderResult::IdentityKindMismatch => { + let _ = InvalidSectionKindException::throw_new(&mut env); + return JObject::null(); + } + CreateV1SectionBuilderResult::NoSpaceLeft => { + let _ = InsufficientSpaceException::throw_new(&mut env); + return JObject::null(); + } + }; + + assert_eq!( + handle.get_as_handle().get_id(), + builder.adv_builder.get_as_handle().get_id(), + "Section builder must be the same handle so that we can reuse the Java handle object below" + ); + + let Ok(java_builder) = + V1SectionBuilder::construct(&mut env, j_handle, builder.section_index.into()) + else { + return JObject::null(); + }; + + java_builder.0 +}
diff --git a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/DecryptTests.java b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/DecryptTests.java index f9a7ede..d951cbf 100644 --- a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/DecryptTests.java +++ b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/DecryptTests.java
@@ -16,12 +16,22 @@ package com.google.android.nearby.presence.rust; -import static com.google.android.nearby.presence.rust.TestData.*; +import static com.google.android.nearby.presence.rust.TestData.ALICE_METADATA; +import static com.google.android.nearby.presence.rust.TestData.V0_CRED; +import static com.google.android.nearby.presence.rust.TestData.V0_ENCRYPTED_ALICE_METADATA; +import static com.google.android.nearby.presence.rust.TestData.V0_PRIVATE; +import static com.google.android.nearby.presence.rust.TestData.V1_ALICE_METADATA; +import static com.google.android.nearby.presence.rust.TestData.V1_CRED; +import static com.google.android.nearby.presence.rust.TestData.V1_ENCRYPTED_ALICE_METADATA; +import static com.google.android.nearby.presence.rust.TestData.V1_PRIVATE; import static com.google.common.truth.Truth.assertThat; import com.google.android.nearby.presence.rust.credential.CredentialBook; -import org.junit.jupiter.api.Test; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +@RunWith(JUnit4.class) public class DecryptTests { public static final class TestMetadata implements CredentialBook.MatchedMetadata { @@ -55,11 +65,11 @@ } @Test - void deserializeAdvertisement_v0_canParsePrivate() { - try (DeserializeResult result = parsePrivateAdv(V0_PRIVATE)) { + public void deserializeAdvertisement_v0_canParsePrivate() { + try (DeserializeResult<TestMetadata> result = parsePrivateAdv(V0_PRIVATE)) { assertThat(result.getKind()).isEqualTo(DeserializeResult.Kind.V0_ADVERTISEMENT); - DeserializedV0Advertisement adv = result.getAsV0(); + DeserializedV0Advertisement<TestMetadata> adv = result.getAsV0(); assertThat(adv).isNotNull(); assertThat(adv.isLegible()).isTrue(); @@ -70,20 +80,20 @@ } @Test - void deserializeAdvertisement_v1_canParsePrivate() { - try (DeserializeResult result = parsePrivateAdv(V1_PRIVATE)) { + public void deserializeAdvertisement_v1_canParsePrivate() { + try (DeserializeResult<TestMetadata> result = parsePrivateAdv(V1_PRIVATE)) { assertThat(result.getKind()).isEqualTo(DeserializeResult.Kind.V1_ADVERTISEMENT); - DeserializedV1Advertisement adv = result.getAsV1(); + DeserializedV1Advertisement<TestMetadata> adv = result.getAsV1(); assertThat(adv).isNotNull(); assertThat(adv.getNumLegibleSections()).isEqualTo(1); assertThat(adv.getNumUndecryptableSections()).isEqualTo(0); - DeserializedV1Section section = adv.getSection(0); + DeserializedV1Section<TestMetadata> section = adv.getSection(0); assertThat(section.getIdentityKind()).isEqualTo(IdentityKind.DECRYPTED); assertThat(section.getMatchedMetadata()).isSameInstanceAs(V1_METADATA); - assertThat(section.getDecryptedMetadata()).isEqualTo(ALICE_METADATA); + assertThat(section.getDecryptedMetadata()).isEqualTo(V1_ALICE_METADATA); } } }
diff --git a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/DeserializeTests.java b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/DeserializeTests.java index 7457edb..b4370d2 100644 --- a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/DeserializeTests.java +++ b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/DeserializeTests.java
@@ -16,24 +16,34 @@ package com.google.android.nearby.presence.rust; -import static com.google.android.nearby.presence.rust.DeserializedV1Section.VerificationMode; -import static com.google.android.nearby.presence.rust.TestData.*; -import static com.google.android.nearby.presence.rust.credential.CredentialBook.NoMetadata; +import static com.google.android.nearby.presence.rust.TestData.V0_CRED; +import static com.google.android.nearby.presence.rust.TestData.V0_IDENTITY_TOKEN; +import static com.google.android.nearby.presence.rust.TestData.V0_PRIVATE; +import static com.google.android.nearby.presence.rust.TestData.V0_PUBLIC; +import static com.google.android.nearby.presence.rust.TestData.V1_CRED; +import static com.google.android.nearby.presence.rust.TestData.V1_IDENTITY_TOKEN; +import static com.google.android.nearby.presence.rust.TestData.V1_PRIVATE; +import static com.google.android.nearby.presence.rust.TestData.V1_PUBLIC; import static com.google.common.truth.Truth.assertThat; import com.google.android.nearby.presence.rust.credential.CredentialBook; -import org.junit.jupiter.api.Test; +import com.google.android.nearby.presence.rust.credential.CredentialBook.NoMetadata; +import java.util.ArrayList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +@RunWith(JUnit4.class) public class DeserializeTests { - DeserializeResult parsePublicAdv(byte[] bytes) { + DeserializeResult<NoMetadata> parsePublicAdv(byte[] bytes) { // Call parse with an empty CredentialBook try (CredentialBook<NoMetadata> book = CredentialBook.empty()) { return NpAdv.deserializeAdvertisement(bytes, book); } } - DeserializeResult parsePrivateAdv(byte[] bytes) { + DeserializeResult<NoMetadata> parsePrivateAdv(byte[] bytes) { try (CredentialBook<NoMetadata> book = CredentialBook.<NoMetadata>builder() .addDiscoveryCredential(V0_CRED, NoMetadata.INSTANCE) @@ -44,11 +54,11 @@ } @Test - void deserializeAdvertisement_v0_canParsePublic() { - try (DeserializeResult result = parsePublicAdv(V0_PUBLIC)) { + public void deserializeAdvertisement_v0_canParsePublic() { + try (DeserializeResult<NoMetadata> result = parsePublicAdv(V0_PUBLIC)) { assertThat(result.getKind()).isEqualTo(DeserializeResult.Kind.V0_ADVERTISEMENT); - DeserializedV0Advertisement adv = result.getAsV0(); + DeserializedV0Advertisement<NoMetadata> adv = result.getAsV0(); assertThat(adv).isNotNull(); assertThat(adv.isLegible()).isTrue(); @@ -60,11 +70,11 @@ } @Test - void deserializeAdvertisement_v0_canParsePublicWithCreds() { - try (DeserializeResult result = parsePrivateAdv(V0_PUBLIC)) { + public void deserializeAdvertisement_v0_canParsePublicWithCreds() { + try (DeserializeResult<NoMetadata> result = parsePrivateAdv(V0_PUBLIC)) { assertThat(result.getKind()).isEqualTo(DeserializeResult.Kind.V0_ADVERTISEMENT); - DeserializedV0Advertisement adv = result.getAsV0(); + DeserializedV0Advertisement<NoMetadata> adv = result.getAsV0(); assertThat(adv).isNotNull(); assertThat(adv.isLegible()).isTrue(); @@ -76,11 +86,11 @@ } @Test - void deserializeAdvertisement_v0_canParsePrivate() { - try (DeserializeResult result = parsePrivateAdv(V0_PRIVATE)) { + public void deserializeAdvertisement_v0_canParsePrivate() { + try (DeserializeResult<NoMetadata> result = parsePrivateAdv(V0_PRIVATE)) { assertThat(result.getKind()).isEqualTo(DeserializeResult.Kind.V0_ADVERTISEMENT); - DeserializedV0Advertisement adv = result.getAsV0(); + DeserializedV0Advertisement<NoMetadata> adv = result.getAsV0(); assertThat(adv).isNotNull(); assertThat(adv.isLegible()).isTrue(); @@ -88,18 +98,18 @@ assertThat(adv.getDataElementCount()).isEqualTo(1); assertThat(adv.getDataElement(0)).isInstanceOf(V0DataElement.TxPower.class); assertThat(adv.getIdentityToken()).isEqualTo(V0_IDENTITY_TOKEN); - assertThat(adv.getSalt()).asList().containsExactly((byte) 0x22, (byte) 0x22); + assertThat(adv.getSalt()).isEqualTo(new byte[] {(byte) 0x22, (byte) 0x22}); assertThat(adv.getMatchedMetadata()).isSameInstanceAs(NoMetadata.INSTANCE); assertThat(adv.getDecryptedMetadata()).isNull(); } } @Test - void deserializeAdvertisement_v0_cannotParsePrivateWithoutCreds() { - try (DeserializeResult result = parsePublicAdv(V0_PRIVATE)) { + public void deserializeAdvertisement_v0_cannotParsePrivateWithoutCreds() { + try (DeserializeResult<NoMetadata> result = parsePublicAdv(V0_PRIVATE)) { assertThat(result.getKind()).isEqualTo(DeserializeResult.Kind.V0_ADVERTISEMENT); - DeserializedV0Advertisement adv = result.getAsV0(); + DeserializedV0Advertisement<NoMetadata> adv = result.getAsV0(); assertThat(adv).isNotNull(); assertThat(adv.isLegible()).isFalse(); @@ -107,9 +117,9 @@ } @Test - void deserializeAdvertisement_v0_canReadTxPowerDe() { - try (DeserializeResult result = parsePublicAdv(V0_PUBLIC)) { - DeserializedV0Advertisement adv = result.getAsV0(); + public void deserializeAdvertisement_v0_canReadTxPowerDe() { + try (DeserializeResult<NoMetadata> result = parsePublicAdv(V0_PUBLIC)) { + DeserializedV0Advertisement<NoMetadata> adv = result.getAsV0(); V0DataElement de = adv.getDataElement(0); @@ -120,9 +130,37 @@ } @Test - void deserializeAdvertisement_v0_canReadActionsDe() { - try (DeserializeResult result = parsePublicAdv(V0_PUBLIC)) { - DeserializedV0Advertisement adv = result.getAsV0(); + public void deserializeAdvertisement_v0_canIterateDataElements() throws Exception { + final int numDes = 2; + byte[] advBytes; + + try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newPublic()) { + builder.addDataElement( + new V0DataElement.V0Actions( + IdentityKind.PLAINTEXT, V0DataElement.V0ActionType.NEARBY_SHARE)); + builder.addDataElement(new V0DataElement.TxPower(10)); + advBytes = builder.build(); + } + + try (DeserializeResult<NoMetadata> result = parsePublicAdv(advBytes)) { + DeserializedV0Advertisement<NoMetadata> adv = result.getAsV0(); + ArrayList<V0DataElement> deList = new ArrayList<>(); + for (V0DataElement de : adv.getDataElements()) { + deList.add(de); + } + + // Validate de order + assertThat(deList.get(0)).isInstanceOf(V0DataElement.V0Actions.class); + assertThat(deList.get(1)).isInstanceOf(V0DataElement.TxPower.class); + // Validate de count + assertThat(deList.size()).isEqualTo(numDes); + } + } + + @Test + public void deserializeAdvertisement_v0_canReadActionsDe() { + try (DeserializeResult<NoMetadata> result = parsePublicAdv(V0_PUBLIC)) { + DeserializedV0Advertisement<NoMetadata> adv = result.getAsV0(); V0DataElement de = adv.getDataElement(1); @@ -135,11 +173,11 @@ } @Test - void deserializeAdvertisement_v1_canParsePublic() { - try (DeserializeResult result = parsePublicAdv(V1_PUBLIC)) { + public void deserializeAdvertisement_v1_canParsePublic() { + try (DeserializeResult<NoMetadata> result = parsePublicAdv(V1_PUBLIC)) { assertThat(result.getKind()).isEqualTo(DeserializeResult.Kind.V1_ADVERTISEMENT); - DeserializedV1Advertisement adv = result.getAsV1(); + DeserializedV1Advertisement<NoMetadata> adv = result.getAsV1(); assertThat(adv).isNotNull(); assertThat(adv.getNumLegibleSections()).isEqualTo(1); @@ -148,17 +186,17 @@ } @Test - void deserializeAdvertisement_v1_canParsePrivate() { - try (DeserializeResult result = parsePrivateAdv(V1_PRIVATE)) { + public void deserializeAdvertisement_v1_canParsePrivate() { + try (DeserializeResult<NoMetadata> result = parsePrivateAdv(V1_PRIVATE)) { assertThat(result.getKind()).isEqualTo(DeserializeResult.Kind.V1_ADVERTISEMENT); - DeserializedV1Advertisement adv = result.getAsV1(); + DeserializedV1Advertisement<NoMetadata> adv = result.getAsV1(); assertThat(adv).isNotNull(); assertThat(adv.getNumLegibleSections()).isEqualTo(1); assertThat(adv.getNumUndecryptableSections()).isEqualTo(0); - DeserializedV1Section section = adv.getSection(0); + DeserializedV1Section<NoMetadata> section = adv.getSection(0); assertThat(section.getIdentityKind()).isEqualTo(IdentityKind.DECRYPTED); assertThat(section.getDataElementCount()).isEqualTo(1); assertThat(section.getIdentityToken()).isEqualTo(V1_IDENTITY_TOKEN); @@ -169,11 +207,11 @@ } @Test - void deserializeAdvertisement_v1_canParsePrivateWithoutCreds() { - try (DeserializeResult result = parsePublicAdv(V1_PRIVATE)) { + public void deserializeAdvertisement_v1_canParsePrivateWithoutCreds() { + try (DeserializeResult<NoMetadata> result = parsePublicAdv(V1_PRIVATE)) { assertThat(result.getKind()).isEqualTo(DeserializeResult.Kind.V1_ADVERTISEMENT); - DeserializedV1Advertisement adv = result.getAsV1(); + DeserializedV1Advertisement<NoMetadata> adv = result.getAsV1(); assertThat(adv).isNotNull(); assertThat(adv.getNumLegibleSections()).isEqualTo(0); @@ -182,11 +220,11 @@ } @Test - void deserializeAdvertisement_v1_canParsePublicSection() { - try (DeserializeResult result = parsePublicAdv(V1_PUBLIC)) { - DeserializedV1Advertisement adv = result.getAsV1(); + public void deserializeAdvertisement_v1_canParsePublicSection() { + try (DeserializeResult<NoMetadata> result = parsePublicAdv(V1_PUBLIC)) { + DeserializedV1Advertisement<NoMetadata> adv = result.getAsV1(); - DeserializedV1Section section = adv.getSection(0); + DeserializedV1Section<NoMetadata> section = adv.getSection(0); assertThat(section).isNotNull(); assertThat(section.getIdentityKind()).isEqualTo(IdentityKind.PLAINTEXT); @@ -197,10 +235,10 @@ } @Test - void deserializeAdvertisement_v1_canReadGenericDe() { - try (DeserializeResult result = parsePublicAdv(V1_PUBLIC)) { - DeserializedV1Advertisement adv = result.getAsV1(); - DeserializedV1Section section = adv.getSection(0); + public void deserializeAdvertisement_v1_canReadGenericDe() { + try (DeserializeResult<NoMetadata> result = parsePublicAdv(V1_PUBLIC)) { + DeserializedV1Advertisement<NoMetadata> adv = result.getAsV1(); + DeserializedV1Section<NoMetadata> section = adv.getSection(0); V1DataElement de = section.getDataElement(0); @@ -208,7 +246,67 @@ assertThat(de).isInstanceOf(V1DataElement.Generic.class); V1DataElement.Generic generic = (V1DataElement.Generic) de; assertThat(generic.getType()).isEqualTo(0x05 /* V1 TxPower */); - assertThat(generic.getData()).asList().containsExactly((byte) 6); + assertThat(generic.getData()).isEqualTo(new byte[] {(byte) 6}); + } + } + + @Test + public void deserializeAdvertisement_v1_canIterateSections() throws Exception { + final int NUM_SECTIONS = 5; + byte[] advBytes; + + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newPublic()) { + for (int i = 0; i < NUM_SECTIONS; i++) { + builder + .addPublicSection() + .addDataElement(new V1DataElement.Generic(123, new byte[] {(byte) i})) + .finishSection(); + } + advBytes = builder.build(); + } + + try (DeserializeResult<NoMetadata> result = parsePublicAdv(advBytes)) { + DeserializedV1Advertisement<NoMetadata> adv = result.getAsV1(); + + byte i = 0; + for (DeserializedV1Section<NoMetadata> section : adv.getSections()) { + V1DataElement.Generic de = (V1DataElement.Generic) section.getDataElement(0); + assertThat(de.getType()).isEqualTo(123); + // Validate section order + assertThat(de.getData()).isEqualTo(new byte[] {i++}); + } + // Validate section count + assertThat(i).isEqualTo(NUM_SECTIONS); + } + } + + @Test + public void deserializeAdvertisement_v1_canIterateDataElements() throws Exception { + final int numDes = 5; + byte[] advBytes; + + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newPublic()) { + try (V1SectionBuilder section = builder.addPublicSection()) { + for (int i = 0; i < numDes; i++) { + section.addDataElement(new V1DataElement.Generic(123, new byte[] {(byte) i})); + } + } + advBytes = builder.build(); + } + + try (DeserializeResult<NoMetadata> result = parsePublicAdv(advBytes)) { + DeserializedV1Advertisement<NoMetadata> adv = result.getAsV1(); + DeserializedV1Section<NoMetadata> section = adv.getSection(0); + + byte i = 0; + for (V1DataElement de : section.getDataElements()) { + V1DataElement.Generic generic = (V1DataElement.Generic) de; + assertThat(generic.getType()).isEqualTo(123); + // Validate de order + assertThat(generic.getData()).isEqualTo(new byte[] {i++}); + } + // Validate de count + assertThat(i).isEqualTo(numDes); } } }
diff --git a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/OwnedHandleTests.java b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/OwnedHandleTests.java new file mode 100644 index 0000000..978a75b --- /dev/null +++ b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/OwnedHandleTests.java
@@ -0,0 +1,115 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed 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 com.google.android.nearby.presence.rust; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Verifier; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.quality.Strictness; + +@RunWith(JUnit4.class) +public class OwnedHandleTests { + + @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + + @Rule + public Verifier checkCleanerAfterTest = + new Verifier() { + @Override + protected void verify() { + assertThat(cleaner.getRegisteredObjectCount()).isEqualTo(0); + } + }; + + CooperativeCleaner cleaner = new CooperativeCleaner(); + @Mock OwnedHandle.Destructor destructor; + + class MyHandle extends OwnedHandle { + public MyHandle(long handleId) { + super(handleId, cleaner, destructor); + } + + public MyHandle(CooperativeCleaner cleaner, long handleId) { + super(handleId, cleaner, destructor); + } + } + + @Test + public void constructor_registersWithCleaner() { + CooperativeCleaner spyCleaner = spy(cleaner); + try (MyHandle handle = new MyHandle(spyCleaner, 123)) { + verify(spyCleaner).register(same(handle), any()); + } + } + + @Test + public void close_callsDestructor() { + try (MyHandle handle = new MyHandle(123)) {} + verify(destructor).deallocate(eq(123L)); + } + + @Test + public void leaked_willEndUpInCleanerQueue() throws Exception { + { + // leaked + waitForGc(new MyHandle(0xbad)); + } + + cleaner.processQueuedObjects(); + + verify(destructor).deallocate(eq(0xbadL)); + } + + @Test + public void close_callsDestructorOfLeakedObject() throws Exception { + { + // leaked + waitForGc(new MyHandle(0xbad)); + } + + MyHandle handle = new MyHandle(123); + handle.close(); + + verify(destructor).deallocate(eq(0xbadL)); + verify(destructor).deallocate(eq(123L)); + } + + private void waitForGc(Object object) throws InterruptedException { + ReferenceQueue<Object> queue = new ReferenceQueue<>(); + // Need to keep this around to be notified when object is GC'd + PhantomReference<Object> ref = new PhantomReference<>(object, queue); + + object = null; + System.gc(); + + assertThat(queue.remove()).isSameInstanceAs(ref); + } +}
diff --git a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/SerializeTests.java b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/SerializeTests.java index b8e18fd..7c4d88e 100644 --- a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/SerializeTests.java +++ b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/SerializeTests.java
@@ -16,18 +16,27 @@ package com.google.android.nearby.presence.rust; -import static com.google.android.nearby.presence.rust.TestData.*; -import static com.google.android.nearby.presence.rust.V0DataElement.TxPower; -import static com.google.android.nearby.presence.rust.V0DataElement.V0ActionType; -import static com.google.android.nearby.presence.rust.V0DataElement.V0Actions; -import static com.google.android.nearby.presence.rust.credential.CredentialBook.NoMetadata; +import static com.google.android.nearby.presence.rust.TestData.V0_BROADCAST_CRED; +import static com.google.android.nearby.presence.rust.TestData.V0_CRED; +import static com.google.android.nearby.presence.rust.TestData.V1_BROADCAST_CRED; +import static com.google.android.nearby.presence.rust.TestData.V1_CRED; +import static com.google.android.nearby.presence.rust.TestData.V1_PRIVATE; +import static com.google.android.nearby.presence.rust.TestData.V1_PUBLIC; import static com.google.common.truth.Truth.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.Assert.assertThrows; +import com.google.android.nearby.presence.rust.V0DataElement.TxPower; +import com.google.android.nearby.presence.rust.V0DataElement.V0ActionType; +import com.google.android.nearby.presence.rust.V0DataElement.V0Actions; +import com.google.android.nearby.presence.rust.V1DataElement.Generic; import com.google.android.nearby.presence.rust.credential.CredentialBook; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; +import com.google.android.nearby.presence.rust.credential.CredentialBook.NoMetadata; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +@RunWith(JUnit4.class) public class SerializeTests { public static final TxPower TX_POWER = new TxPower(7); @@ -38,10 +47,144 @@ public static final V0Actions PRIVATE_ACTIONS = new V0Actions(IdentityKind.DECRYPTED, V0ActionType.CALL_TRANSFER, V0ActionType.NEARBY_SHARE); + public static final Generic GENERIC_DE = new Generic(1234, new byte[] {0x01, 0x02, 0x03, 0x04}); + + @SuppressWarnings("MutablePublicArray") public static final byte[] SALT = new byte[] {0x12, 0x34}; @Test - void serializeAdvertisement_v0_canSerialize() throws Exception { + public void constructActionsDe_mergedValueIsNonZero() { + assertThat(PUBLIC_ACTIONS.getActionBits()).isNotEqualTo(0); + assertThat(PRIVATE_ACTIONS.getActionBits()).isNotEqualTo(0); + } + + @Test + public void serializeAdvertisement_v1_canCreatePubSection() throws Exception { + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newPublic()) { + try (V1SectionBuilder sectionBuilder = builder.addPublicSection()) { + sectionBuilder.addDataElement(GENERIC_DE); + } + byte[] adv = builder.build(); + + assertThat(adv).isNotNull(); + assertThat(adv).isNotEmpty(); + } + } + + @Test + public void serializeAdvertisement_v1_canCreateMicEncryptedSection() throws Exception { + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newEncrypted()) { + try (V1SectionBuilder sectionBuilder = + builder.addEncryptedSection(V1_BROADCAST_CRED, VerificationMode.MIC)) { + sectionBuilder.addDataElement(GENERIC_DE); + } + byte[] adv = builder.build(); + + assertThat(adv).isNotNull(); + assertThat(adv).isNotEmpty(); + } + } + + @Test + public void serializeAdvertisement_v1_canCreateEmptyPublicAdvertisement() throws Exception { + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newPublic()) { + + byte[] adv = builder.build(); + + assertThat(adv).isNotNull(); + } + } + + @Test + public void serializeAdvertisement_v1_canCreateEmptyPublicSection() throws Exception { + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newPublic()) { + builder.addPublicSection().close(); + + byte[] adv = builder.build(); + + assertThat(adv).isNotNull(); + } + } + + @Test + public void serializeAdvertisement_v1_canCreateSignatureEncryptedSection() throws Exception { + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newEncrypted()) { + try (V1SectionBuilder sectionBuilder = + builder.addEncryptedSection(V1_BROADCAST_CRED, VerificationMode.SIGNATURE)) { + sectionBuilder.addDataElement(GENERIC_DE); + } + byte[] adv = builder.build(); + + assertThat(adv).isNotNull(); + assertThat(adv).isNotEmpty(); + } + } + + @Test + public void serializeAdvertisement_v1_canCreateRoundtripEncrypted() throws Exception { + try (CredentialBook<NoMetadata> book = + CredentialBook.<NoMetadata>builder() + .addDiscoveryCredential(V1_CRED, NoMetadata.INSTANCE) + .build(); + DeserializeResult<NoMetadata> original = NpAdv.deserializeAdvertisement(V1_PRIVATE, book); + V1AdvertisementBuilder builder = V1AdvertisementBuilder.newEncrypted()) { + for (DeserializedV1Section<NoMetadata> section : original.getAsV1().getSections()) { + try (V1SectionBuilder sectionBuilder = + builder.addEncryptedSection(V1_BROADCAST_CRED, VerificationMode.SIGNATURE)) { + for (V1DataElement de : section.getDataElements()) { + sectionBuilder.addDataElement(de); + } + } + } + byte[] adv = builder.build(); + + // Can't check contents due to mismatched salts + assertThat(adv.length).isEqualTo(V1_PRIVATE.length); + } + } + + @Test + public void serializeAdvertisement_v1_canCreateRoundtripPublic() throws Exception { + try (CredentialBook<NoMetadata> book = CredentialBook.empty(); + DeserializeResult<NoMetadata> original = NpAdv.deserializeAdvertisement(V1_PUBLIC, book); + V1AdvertisementBuilder builder = V1AdvertisementBuilder.newPublic()) { + for (DeserializedV1Section<NoMetadata> section : original.getAsV1().getSections()) { + try (V1SectionBuilder sectionBuilder = builder.addPublicSection()) { + for (V1DataElement de : section.getDataElements()) { + sectionBuilder.addDataElement(de); + } + } + } + byte[] adv = builder.build(); + + assertThat(adv).isEqualTo(V1_PUBLIC); + } + } + + @Test + public void serializeAdvertisement_v1_cantCreatePubSectionInEncryptedAdv() throws Exception { + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newEncrypted()) { + assertThrows( + SerializationException.InvalidSectionKindException.class, + () -> { + builder.addPublicSection().close(); + }); + } + } + + @Test + public void serializeAdvertisement_v1_cantCreateEncryptedSectionInPublicAdv() throws Exception { + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newPublic()) { + assertThrows( + SerializationException.InvalidSectionKindException.class, + () -> { + builder.addEncryptedSection(V1_BROADCAST_CRED, VerificationMode.SIGNATURE).close(); + }); + } + } + + @Test + public void serializeAdvertisement_v0_canSerialize() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newPublic()) { builder.addDataElement(TX_POWER); builder.addDataElement(PUBLIC_ACTIONS); @@ -51,7 +194,7 @@ } @Test - void serializeAdvertisement_v0_canSerializePrivate() throws Exception { + public void serializeAdvertisement_v0_canSerializePrivate() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newEncrypted(V0_BROADCAST_CRED, SALT)) { builder.addDataElement(TX_POWER); @@ -62,15 +205,15 @@ } @Test - void serializeAdvertisement_v0_canRoundtrip() throws Exception { + public void serializeAdvertisement_v0_canRoundtrip() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newPublic(); - CredentialBook book = CredentialBook.empty()) { + CredentialBook<NoMetadata> book = CredentialBook.empty()) { builder.addDataElement(TX_POWER); builder.addDataElement(PUBLIC_ACTIONS); byte[] advBytes = builder.build(); - DeserializeResult result = NpAdv.deserializeAdvertisement(advBytes, book); - DeserializedV0Advertisement adv = result.getAsV0(); + DeserializeResult<NoMetadata> result = NpAdv.deserializeAdvertisement(advBytes, book); + DeserializedV0Advertisement<NoMetadata> adv = result.getAsV0(); assertThat(adv).isNotNull(); assertThat(adv.getDataElementCount()).isEqualTo(2); @@ -87,17 +230,19 @@ } @Test - void serializeAdvertisement_v0_canRoundtripPrivate() throws Exception { + public void serializeAdvertisement_v0_canRoundtripPrivate() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newEncrypted(V0_BROADCAST_CRED, SALT); - CredentialBook book = - CredentialBook.builder().addDiscoveryCredential(V0_CRED, NoMetadata.INSTANCE).build()) { + CredentialBook<NoMetadata> book = + CredentialBook.<NoMetadata>builder() + .addDiscoveryCredential(V0_CRED, NoMetadata.INSTANCE) + .build()) { builder.addDataElement(TX_POWER); builder.addDataElement(PRIVATE_ACTIONS); byte[] advBytes = builder.build(); - DeserializeResult result = NpAdv.deserializeAdvertisement(advBytes, book); - DeserializedV0Advertisement adv = result.getAsV0(); + DeserializeResult<NoMetadata> result = NpAdv.deserializeAdvertisement(advBytes, book); + DeserializedV0Advertisement<NoMetadata> adv = result.getAsV0(); assertThat(adv).isNotNull(); assertThat(adv.getDataElementCount()).isEqualTo(2); @@ -114,14 +259,14 @@ } @Test - void serializeAdvertisement_v0_emptyIsError() throws Exception { + public void serializeAdvertisement_v0_emptyIsError() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newPublic()) { assertThrows(SerializationException.UnencryptedSizeException.class, () -> builder.build()); } } @Test - void serializeAdvertisement_v0_fullIsError() throws Exception { + public void serializeAdvertisement_v0_fullIsError() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newPublic()) { assertThrows( SerializationException.InsufficientSpaceException.class, @@ -135,7 +280,7 @@ } @Test - void serializeAdvertisement_v0_publicAdvPrivateActionsIsError() throws Exception { + public void serializeAdvertisement_v0_publicAdvPrivateActionsIsError() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newPublic()) { assertThrows( SerializationException.InvalidDataElementException.class, @@ -144,7 +289,7 @@ } @Test - void serializeAdvertisement_v0_privateAdvPublicActionsIsError() throws Exception { + public void serializeAdvertisement_v0_privateAdvPublicActionsIsError() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newEncrypted(V0_BROADCAST_CRED, SALT)) { assertThrows( @@ -154,7 +299,7 @@ } @Test - void serializeAdvertisement_v0_invalidTxPowerIsError() throws Exception { + public void serializeAdvertisement_v0_invalidTxPowerIsError() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newPublic()) { assertThrows( SerializationException.InvalidDataElementException.class, @@ -163,18 +308,18 @@ } @Test - void serializeAdvertisement_v0_handleIsConsumedByBuild() throws Exception { + public void serializeAdvertisement_v0_handleIsConsumedByBuild() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newPublic()) { builder.addDataElement(TX_POWER); - byte[] adv = builder.build(); + byte[] unused = builder.build(); assertThrows(Handle.InvalidHandleException.class, () -> builder.addDataElement(TX_POWER)); assertThrows(Handle.InvalidHandleException.class, () -> builder.build()); } } @Test - @Disabled("b/311225033: Duplicate data element spec change not implemented") - void serializeAdvertisement_v0_deNotAddedTwice() throws Exception { + @Ignore("b/311225033: Duplicate data element spec change not implemented") + public void serializeAdvertisement_v0_deNotAddedTwice() throws Exception { try (V0AdvertisementBuilder builder = V0AdvertisementBuilder.newPublic()) { builder.addDataElement(TX_POWER); assertThrows(Exception.class, () -> builder.addDataElement(TX_POWER));
diff --git a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/TestData.java b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/TestData.java index 122b2a9..7408de6 100644 --- a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/TestData.java +++ b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/TestData.java
@@ -18,10 +18,14 @@ import com.google.android.nearby.presence.rust.credential.V0BroadcastCredential; import com.google.android.nearby.presence.rust.credential.V0DiscoveryCredential; +import com.google.android.nearby.presence.rust.credential.V1BroadcastCredential; import com.google.android.nearby.presence.rust.credential.V1DiscoveryCredential; +@SuppressWarnings("MutablePublicArray") public class TestData { + private TestData() {} + public static final byte[] V0_PUBLIC = { 0x00, // adv header 0x15, 20, // tx power @@ -141,67 +145,6 @@ (byte) 0x68 }; - public static final byte[] V1_ENCRYPTED_ALICE_METADATA = { - (byte) 0x0c, - (byte) 0x27, - (byte) 0x43, - (byte) 0x58, - (byte) 0xb8, - (byte) 0xb9, - (byte) 0x90, - (byte) 0x72, - (byte) 0xb1, - (byte) 0xe8, - (byte) 0xba, - (byte) 0x7d, - (byte) 0x8e, - (byte) 0xdb, - (byte) 0xac, - (byte) 0x7e, - (byte) 0x15, - (byte) 0xd5, - (byte) 0x05, - (byte) 0x30, - (byte) 0x4d, - (byte) 0xb7, - (byte) 0xe0, - (byte) 0xbd, - (byte) 0x8f, - (byte) 0xe9, - (byte) 0xab, - (byte) 0x48, - (byte) 0x66, - (byte) 0xac, - (byte) 0x2c, - (byte) 0x8a, - (byte) 0x24, - (byte) 0x0c, - (byte) 0x02, - (byte) 0xb4, - (byte) 0xd0, - (byte) 0xb7, - (byte) 0x10, - (byte) 0xd6, - (byte) 0x30, - (byte) 0x12, - (byte) 0xd6, - (byte) 0x98, - (byte) 0xdc, - (byte) 0x55, - (byte) 0xae, - (byte) 0xb0, - (byte) 0x9c, - (byte) 0x9a, - (byte) 0x7d, - (byte) 0x2e, - (byte) 0xcb, - (byte) 0xba, - (byte) 0xdd, - (byte) 0x7d, - (byte) 0x4e, - (byte) 0xd1 - }; - public static final byte[] V0_KEY_SEED = { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, @@ -247,12 +190,6 @@ (byte) 0xFD }; - public static final V0DiscoveryCredential V0_CRED = - new V0DiscoveryCredential(V0_KEY_SEED, V0_IDENTITY_TOKEN_HMAC); - - public static final V0BroadcastCredential V0_BROADCAST_CRED = - new V0BroadcastCredential(V0_KEY_SEED, V0_IDENTITY_TOKEN); - public static final byte[] V0_PRIVATE = { 0x04, // adv header 0x22, @@ -275,305 +212,559 @@ (byte) 0xEC // ciphertext for metadata key & txpower DE }; - public static final byte[] V1_IDENTITY_TOKEN = { - (byte) 0x58, - (byte) 0x31, - (byte) 0x00, - (byte) 0x48, - (byte) 0x11, - (byte) 0xe4, - (byte) 0xea, - (byte) 0x43, - (byte) 0xe9, - (byte) 0x01, - (byte) 0x76, - (byte) 0x25, - (byte) 0xd8, - (byte) 0xaf, - (byte) 0xd6, - (byte) 0x92 - }; + public static final V0DiscoveryCredential V0_CRED = + new V0DiscoveryCredential(V0_KEY_SEED, V0_IDENTITY_TOKEN_HMAC); + + public static final V0BroadcastCredential V0_BROADCAST_CRED = + new V0BroadcastCredential(V0_KEY_SEED, V0_IDENTITY_TOKEN); public static final byte[] V1_KEY_SEED = { - (byte) 0xc8, - (byte) 0xdd, - (byte) 0x01, - (byte) 0x4d, - (byte) 0x25, - (byte) 0x01, - (byte) 0xc0, - (byte) 0xbf, - (byte) 0x5b, - (byte) 0x2a, - (byte) 0x05, - (byte) 0x48, - (byte) 0x49, - (byte) 0x8c, - (byte) 0xe6, - (byte) 0xbf, - (byte) 0x48, - (byte) 0x5b, - (byte) 0x89, - (byte) 0xb8, - (byte) 0x47, - (byte) 0x13, - (byte) 0xcc, - (byte) 0xdd, - (byte) 0xa0, - (byte) 0x18, - (byte) 0xac, - (byte) 0xd9, - (byte) 0xef, - (byte) 0x58, - (byte) 0x9f, - (byte) 0x76 - }; - - public static final byte[] V1_MIC_SHORT_HMAC = { - (byte) 0x09, - (byte) 0x48, - (byte) 0x4e, - (byte) 0x8f, + (byte) 0x3B, + (byte) 0x4C, + (byte) 0x0E, (byte) 0x39, - (byte) 0xdc, - (byte) 0x16, - (byte) 0x27, + (byte) 0x95, + (byte) 0x92, + (byte) 0x47, + (byte) 0xC8, (byte) 0x85, - (byte) 0x0a, - (byte) 0xea, - (byte) 0xfc, - (byte) 0x84, - (byte) 0xf6, - (byte) 0x43, - (byte) 0x51, - (byte) 0x62, - (byte) 0x16, - (byte) 0xf1, - (byte) 0x8d, - (byte) 0xda, - (byte) 0xd3, - (byte) 0xbc, - (byte) 0xba, - (byte) 0x43, - (byte) 0xf1, - (byte) 0x62, - (byte) 0x4e, - (byte) 0xa7, - (byte) 0x09, - (byte) 0xda, - (byte) 0xde - }; - - public static final byte[] V1_MIC_LONG_HMAC = { - (byte) 0xb9, - (byte) 0x6a, - (byte) 0xd2, - (byte) 0x3e, - (byte) 0x8e, - (byte) 0x08, - (byte) 0xe0, - (byte) 0xf4, - (byte) 0xe9, - (byte) 0xba, - (byte) 0xe9, - (byte) 0xbb, - (byte) 0x3d, - (byte) 0xe3, - (byte) 0x2f, - (byte) 0xd1, - (byte) 0x14, - (byte) 0x3a, - (byte) 0x51, - (byte) 0x19, - (byte) 0x54, - (byte) 0xf8, - (byte) 0x66, - (byte) 0x9f, - (byte) 0xf6, - (byte) 0xdb, - (byte) 0xf6, - (byte) 0x03, - (byte) 0xf7, - (byte) 0x41, - (byte) 0x20, - (byte) 0xd7 - }; - - public static final byte[] V1_SIG_HMAC = { - (byte) 0xc4, - (byte) 0x19, - (byte) 0x6e, - (byte) 0x84, - (byte) 0x95, - (byte) 0x3a, - (byte) 0x8a, - (byte) 0x97, - (byte) 0xb9, - (byte) 0xed, - (byte) 0xf0, - (byte) 0xba, - (byte) 0xd2, - (byte) 0x5d, - (byte) 0xa4, + (byte) 0xCC, + (byte) 0x87, + (byte) 0x3B, + (byte) 0x2E, + (byte) 0x70, + (byte) 0x75, + (byte) 0x87, + (byte) 0xB5, + (byte) 0x85, + (byte) 0x9C, + (byte) 0x35, + (byte) 0x27, + (byte) 0xD8, + (byte) 0x22, + (byte) 0xFD, (byte) 0x32, - (byte) 0xb1, - (byte) 0xf2, - (byte) 0x1a, - (byte) 0xf7, - (byte) 0x7d, + (byte) 0x0C, + (byte) 0x57, + (byte) 0xB3, + (byte) 0x40, + (byte) 0xE4, + (byte) 0x3E, + (byte) 0xAE + }; + + public static final byte[] V1_IDENTITY_TOKEN = { (byte) 0x95, - (byte) 0x8f, - (byte) 0xeb, - (byte) 0x5f, - (byte) 0xbe, - (byte) 0xfd, - (byte) 0x62, - (byte) 0xa7, - (byte) 0xc0, - (byte) 0x16, - (byte) 0x66 + (byte) 0x71, + (byte) 0x75, + (byte) 0x38, + (byte) 0x49, + (byte) 0xEF, + (byte) 0x1F, + (byte) 0xA7, + (byte) 0x10, + (byte) 0x58, + (byte) 0xDD, + (byte) 0x2C, + (byte) 0xD5, + (byte) 0x7E, + (byte) 0xE6, + (byte) 0x14 + }; + + public static final byte[] V1_PRIVATE_KEY = { + (byte) 0x21, + (byte) 0xD4, + (byte) 0x08, + (byte) 0x74, + (byte) 0x0F, + (byte) 0xD5, + (byte) 0x14, + (byte) 0x29, + (byte) 0xBF, + (byte) 0x52, + (byte) 0x3B, + (byte) 0x10, + (byte) 0x1B, + (byte) 0x23, + (byte) 0x23, + (byte) 0x80, + (byte) 0xCF, + (byte) 0xA6, + (byte) 0x86, + (byte) 0x80, + (byte) 0x8A, + (byte) 0x34, + (byte) 0xAC, + (byte) 0x06, + (byte) 0xC5, + (byte) 0x06, + (byte) 0x43, + (byte) 0x28, + (byte) 0x50, + (byte) 0xD5, + (byte) 0x9C, + (byte) 0x89 }; public static final byte[] V1_PUB_KEY = { - (byte) 0x3c, - (byte) 0x59, - (byte) 0xd7, - (byte) 0x30, - (byte) 0x58, - (byte) 0x8c, - (byte) 0x45, - (byte) 0x26, - (byte) 0x7e, - (byte) 0x52, - (byte) 0x29, + (byte) 0xC0, + (byte) 0x87, + (byte) 0x25, + (byte) 0xE9, + (byte) 0x0F, + (byte) 0x5A, + (byte) 0xD7, + (byte) 0xA4, + (byte) 0xD8, + (byte) 0x7E, + (byte) 0x9E, + (byte) 0x76, + (byte) 0xDF, + (byte) 0x10, + (byte) 0x44, + (byte) 0xE9, + (byte) 0x78, + (byte) 0xF2, + (byte) 0x80, + (byte) 0x0F, + (byte) 0x2B, + (byte) 0x43, + (byte) 0x03, + (byte) 0xCA, + (byte) 0x64, + (byte) 0x0A, + (byte) 0x7A, + (byte) 0xA1, + (byte) 0xE4, + (byte) 0x43, + (byte) 0x63, + (byte) 0xAF + }; + + public static final byte[] V1_MIC_SHORT_HMAC = { + (byte) 0x90, + (byte) 0x8D, + (byte) 0x6D, + (byte) 0x40, + (byte) 0x76, + (byte) 0x0E, + (byte) 0x13, + (byte) 0xC8, + (byte) 0x3C, + (byte) 0x76, + (byte) 0xD2, + (byte) 0xE4, + (byte) 0xED, + (byte) 0x8F, + (byte) 0xBD, + (byte) 0x83, + (byte) 0xED, + (byte) 0xEC, + (byte) 0xFD, + (byte) 0xCE, + (byte) 0x0A, + (byte) 0x90, + (byte) 0x85, + (byte) 0x47, + (byte) 0x5D, + (byte) 0xAA, + (byte) 0x3F, + (byte) 0xAE, + (byte) 0x6C, + (byte) 0x90, + (byte) 0x43, + (byte) 0x15 + }; + + public static final byte[] V1_MIC_LONG_HMAC = { + (byte) 0x57, + (byte) 0x99, (byte) 0x54, - (byte) 0xca, - (byte) 0xc9, - (byte) 0xcb, - (byte) 0xca, + (byte) 0x4E, + (byte) 0xB2, + (byte) 0xB0, + (byte) 0xA1, + (byte) 0x99, + (byte) 0x2F, + (byte) 0x0D, + (byte) 0x13, + (byte) 0x25, + (byte) 0xAE, + (byte) 0xE0, + (byte) 0x8F, + (byte) 0xB3, + (byte) 0xE2, + (byte) 0x8F, + (byte) 0xD9, + (byte) 0x56, + (byte) 0x8B, + (byte) 0x70, + (byte) 0xDE, + (byte) 0x28, + (byte) 0x82, + (byte) 0x60, + (byte) 0xE7, + (byte) 0x71, + (byte) 0xA4, + (byte) 0x57, + (byte) 0xCE, + (byte) 0x9E + }; + + public static final byte[] V1_SIG_HMAC = { + (byte) 0x37, + (byte) 0x53, + (byte) 0x78, + (byte) 0x59, + (byte) 0x3F, + (byte) 0x8A, + (byte) 0x06, + (byte) 0x5B, + (byte) 0xE5, + (byte) 0x03, + (byte) 0x05, + (byte) 0xE7, + (byte) 0xC1, + (byte) 0xD0, + (byte) 0x29, + (byte) 0x3D, + (byte) 0x2C, + (byte) 0x3D, + (byte) 0xDA, + (byte) 0x43, + (byte) 0x8D, + (byte) 0x0A, + (byte) 0x29, + (byte) 0x23, + (byte) 0x58, + (byte) 0xAC, + (byte) 0x98, + (byte) 0x10, + (byte) 0x1A, + (byte) 0x0A, + (byte) 0xA6, + (byte) 0xB2 + }; + + public static final byte[] V1_ALICE_METADATA = { + (byte) 0x7B, + (byte) 0x22, + (byte) 0x75, + (byte) 0x75, + (byte) 0x69, + (byte) 0x64, + (byte) 0x22, + (byte) 0x3A, + (byte) 0x22, + (byte) 0x33, + (byte) 0x37, + (byte) 0x38, + (byte) 0x38, + (byte) 0x34, + (byte) 0x35, + (byte) 0x65, + (byte) 0x31, + (byte) 0x2D, + (byte) 0x32, + (byte) 0x36, + (byte) 0x31, + (byte) 0x36, + (byte) 0x2D, + (byte) 0x34, + (byte) 0x32, + (byte) 0x30, + (byte) 0x64, + (byte) 0x2D, + (byte) 0x38, + (byte) 0x36, + (byte) 0x66, + (byte) 0x35, + (byte) 0x2D, + (byte) 0x36, + (byte) 0x37, + (byte) 0x34, + (byte) 0x31, + (byte) 0x37, + (byte) 0x37, + (byte) 0x61, + (byte) 0x37, + (byte) 0x35, + (byte) 0x30, + (byte) 0x34, + (byte) 0x64, + (byte) 0x22, + (byte) 0x2C, + (byte) 0x22, + (byte) 0x64, + (byte) 0x69, + (byte) 0x73, + (byte) 0x70, + (byte) 0x6C, + (byte) 0x61, + (byte) 0x79, + (byte) 0x5F, + (byte) 0x6E, + (byte) 0x61, + (byte) 0x6D, + (byte) 0x65, + (byte) 0x22, + (byte) 0x3A, + (byte) 0x22, + (byte) 0x41, + (byte) 0x6C, + (byte) 0x69, + (byte) 0x63, + (byte) 0x65, + (byte) 0x22, + (byte) 0x2C, + (byte) 0x22, + (byte) 0x6C, + (byte) 0x6F, + (byte) 0x63, + (byte) 0x61, + (byte) 0x74, + (byte) 0x69, + (byte) 0x6F, + (byte) 0x6E, + (byte) 0x22, + (byte) 0x3A, + (byte) 0x22, + (byte) 0x57, + (byte) 0x6F, + (byte) 0x6E, + (byte) 0x64, + (byte) 0x65, (byte) 0x72, - (byte) 0x94, - (byte) 0x24, - (byte) 0xd8, - (byte) 0xf5, - (byte) 0xa6, - (byte) 0x1e, - (byte) 0xcf, + (byte) 0x6C, + (byte) 0x61, + (byte) 0x6E, + (byte) 0x64, + (byte) 0x22, + (byte) 0x7D + }; + + public static final byte[] V1_ENCRYPTED_ALICE_METADATA = { + (byte) 0xFD, + (byte) 0xEC, + (byte) 0x0B, + (byte) 0xE8, + (byte) 0x79, + (byte) 0x1C, + (byte) 0x10, + (byte) 0x6D, + (byte) 0x33, + (byte) 0x11, + (byte) 0xC7, + (byte) 0xF3, + (byte) 0x9B, + (byte) 0x90, + (byte) 0x8A, + (byte) 0x5A, + (byte) 0x74, + (byte) 0x7F, + (byte) 0x9C, + (byte) 0x3C, + (byte) 0xC9, + (byte) 0xBC, + (byte) 0x90, + (byte) 0xC5, + (byte) 0x88, + (byte) 0xE0, + (byte) 0xED, + (byte) 0x28, + (byte) 0xDC, + (byte) 0x25, + (byte) 0xE5, + (byte) 0xD8, + (byte) 0x9C, + (byte) 0x28, + (byte) 0x0F, + (byte) 0x23, + (byte) 0x9D, + (byte) 0x21, + (byte) 0xC7, + (byte) 0xFF, + (byte) 0xE1, + (byte) 0x60, + (byte) 0x62, + (byte) 0x5B, + (byte) 0xBC, + (byte) 0x22, + (byte) 0xB4, + (byte) 0x3A, + (byte) 0x02, + (byte) 0x9D, + (byte) 0x69, + (byte) 0x53, + (byte) 0x2A, + (byte) 0x62, + (byte) 0xB0, + (byte) 0x82, + (byte) 0xC7, + (byte) 0x79, + (byte) 0x9E, + (byte) 0x30, + (byte) 0x76, + (byte) 0xB0, + (byte) 0x8A, + (byte) 0xC3, + (byte) 0xCD, + (byte) 0x11, + (byte) 0xEA, + (byte) 0x45, + (byte) 0x4A, + (byte) 0x3C, + (byte) 0x25, + (byte) 0x45, + (byte) 0xD9, + (byte) 0x44, + (byte) 0x5B, + (byte) 0xE3, + (byte) 0xA0, + (byte) 0x3E, (byte) 0x04, - (byte) 0x3e, - (byte) 0x8f, - (byte) 0x91, + (byte) 0x79, + (byte) 0x6B, + (byte) 0xC3, + (byte) 0x62, + (byte) 0xA4, + (byte) 0xA1, + (byte) 0xF4, + (byte) 0x76, + (byte) 0x58, + (byte) 0x42, + (byte) 0xDC, + (byte) 0x37, + (byte) 0x93, (byte) 0x81, - (byte) 0x6d, - (byte) 0x19, - (byte) 0x74 + (byte) 0x60, + (byte) 0x8E, + (byte) 0x00, + (byte) 0x6D, + (byte) 0xE0, + (byte) 0x22, + (byte) 0x82, + (byte) 0x98, + (byte) 0x40, + (byte) 0x59, + (byte) 0x41, + (byte) 0xF9, + (byte) 0x88, + (byte) 0xB8, + (byte) 0xFB, + (byte) 0x9E, + (byte) 0xED + }; + + public static final byte[] V1_PRIVATE = { + (byte) 0x20, + (byte) 0x03, + (byte) 0x6C, + (byte) 0x94, + (byte) 0x77, + (byte) 0x9A, + (byte) 0xF9, + (byte) 0x89, + (byte) 0x83, + (byte) 0x9F, + (byte) 0x41, + (byte) 0x98, + (byte) 0x33, + (byte) 0x68, + (byte) 0x24, + (byte) 0x9A, + (byte) 0xCF, + (byte) 0xAC, + (byte) 0x8E, + (byte) 0xC3, + (byte) 0x46, + (byte) 0x02, + (byte) 0x30, + (byte) 0x89, + (byte) 0x68, + (byte) 0x99, + (byte) 0xA2, + (byte) 0xF5, + (byte) 0x32, + (byte) 0x36, + (byte) 0x06, + (byte) 0x18, + (byte) 0x0D, + (byte) 0xF6, + (byte) 0x42, + (byte) 0x52, + (byte) 0xA1, + (byte) 0x59, + (byte) 0x46, + (byte) 0xC8, + (byte) 0xD1, + (byte) 0x27, + (byte) 0x74, + (byte) 0xFF, + (byte) 0xBF, + (byte) 0x53, + (byte) 0xFE, + (byte) 0x51, + (byte) 0xCB, + (byte) 0x39, + (byte) 0x6D, + (byte) 0x28, + (byte) 0x3C, + (byte) 0x7E, + (byte) 0xD9, + (byte) 0x6F, + (byte) 0xB0, + (byte) 0x70, + (byte) 0x82, + (byte) 0x26, + (byte) 0x51, + (byte) 0x11, + (byte) 0xF2, + (byte) 0x90, + (byte) 0xC0, + (byte) 0xBE, + (byte) 0x34, + (byte) 0x96, + (byte) 0x57, + (byte) 0x1F, + (byte) 0x4F, + (byte) 0xC0, + (byte) 0x87, + (byte) 0xF0, + (byte) 0xA8, + (byte) 0x0D, + (byte) 0xD8, + (byte) 0xE3, + (byte) 0xC7, + (byte) 0x8C, + (byte) 0xAA, + (byte) 0x7E, + (byte) 0x41, + (byte) 0x14, + (byte) 0x58, + (byte) 0xF4, + (byte) 0xD3, + (byte) 0x8E, + (byte) 0x8E, + (byte) 0xA0, + (byte) 0x57, + (byte) 0xF4, + (byte) 0x63, + (byte) 0x43, + (byte) 0x74, + (byte) 0x08, + (byte) 0xC7, + (byte) 0xB8, + (byte) 0x51, + (byte) 0x66, + (byte) 0xB9 }; public static final V1DiscoveryCredential V1_CRED = new V1DiscoveryCredential( V1_KEY_SEED, V1_MIC_SHORT_HMAC, V1_MIC_LONG_HMAC, V1_SIG_HMAC, V1_PUB_KEY); - public static final byte[] V1_PRIVATE = { - (byte) 0x20, - (byte) 0x03, - (byte) 0xfc, - (byte) 0x32, - (byte) 0xb7, - (byte) 0x5d, - (byte) 0xdd, - (byte) 0x6a, - (byte) 0xdb, - (byte) 0xb0, - (byte) 0x89, - (byte) 0x7d, - (byte) 0xb9, - (byte) 0xcd, - (byte) 0xa9, - (byte) 0x6e, - (byte) 0x73, - (byte) 0x6d, - (byte) 0x7a, - (byte) 0xfc, - (byte) 0xeb, - (byte) 0x2b, - (byte) 0x0c, - (byte) 0x02, - (byte) 0x3d, - (byte) 0xc8, - (byte) 0xfa, - (byte) 0xc8, - (byte) 0x78, - (byte) 0x83, - (byte) 0x56, - (byte) 0xfa, - (byte) 0x53, - (byte) 0x11, - (byte) 0x42, - (byte) 0x08, - (byte) 0x9e, - (byte) 0xfe, - (byte) 0x70, - (byte) 0xd0, - (byte) 0x68, - (byte) 0x6c, - (byte) 0x7c, - (byte) 0x29, - (byte) 0x86, - (byte) 0xd6, - (byte) 0x76, - (byte) 0x2b, - (byte) 0x03, - (byte) 0xa4, - (byte) 0xc7, - (byte) 0x47, - (byte) 0x5c, - (byte) 0x41, - (byte) 0x9d, - (byte) 0x21, - (byte) 0x15, - (byte) 0x54, - (byte) 0x89, - (byte) 0x43, - (byte) 0x32, - (byte) 0x44, - (byte) 0x47, - (byte) 0x34, - (byte) 0xd7, - (byte) 0xbd, - (byte) 0x4f, - (byte) 0x38, - (byte) 0x83, - (byte) 0x74, - (byte) 0xe4, - (byte) 0xdb, - (byte) 0xcf, - (byte) 0xfe, - (byte) 0xe4, - (byte) 0x7a, - (byte) 0xae, - (byte) 0xa8, - (byte) 0xe2, - (byte) 0xf5, - (byte) 0x69, - (byte) 0xb8, - (byte) 0x42, - (byte) 0xf5, - (byte) 0x67, - (byte) 0x7a, - (byte) 0x34, - (byte) 0x6d, - (byte) 0x86, - (byte) 0x8b, - (byte) 0x4c, - (byte) 0xa9, - (byte) 0x7f, - (byte) 0x45, - (byte) 0x1c, - (byte) 0x37, - (byte) 0xf1, - (byte) 0x6e, - (byte) 0xfc, - (byte) 0xae, - (byte) 0xc6 - }; + public static final V1BroadcastCredential V1_BROADCAST_CRED = + new V1BroadcastCredential(V1_KEY_SEED, V1_IDENTITY_TOKEN, V1_PRIVATE_KEY); }
diff --git a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/TestVectors.java b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/TestVectors.java new file mode 100644 index 0000000..c496023 --- /dev/null +++ b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/TestVectors.java
@@ -0,0 +1,182 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed 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 com.google.android.nearby.presence.rust; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.android.nearby.presence.rust.credential.V1BroadcastCredential; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import java.io.BufferedReader; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.SecureRandom; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TestVectors { + + static final class DataElement { + byte[] contents; + int deType; + } + + static final class TestVector { + byte[] keySeed; + byte[] identityToken; + byte[] advHeaderByte; + byte[] sectionSalt; + DataElement[] dataElements; + byte[] aesKey; + byte[] sectionMicHmacKey; + byte[] nonce; + byte[] encodedSection; + } + + static final class HexArrayDeserializer implements JsonDeserializer<byte[]> { + + private static final byte byteFromHexChar(char c) { + if (c >= '0' && c <= '9') { + return (byte) (c - '0'); + } else if (c >= 'a' && c <= 'f') { + return (byte) (c - 'a' + 0x0a); + } else if (c >= 'A' && c <= 'F') { + return (byte) (c - 'A' + 0x0a); + } else { + throw new IllegalArgumentException("Invalid hex char: " + c); + } + } + + @Override + public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + String hex = json.getAsString(); + int byteLen = hex.length() / 2; + byte[] out = new byte[byteLen]; + + for (int i = 0; i < byteLen; i++) { + int offset = i * 2; + out[i] = + (byte) + ((byteFromHexChar(hex.charAt(offset)) << 4) + | byteFromHexChar(hex.charAt(offset + 1))); + } + + return out; + } + } + + static Gson createJsonParser() { + return new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .registerTypeAdapter(byte[].class, new HexArrayDeserializer()) + .create(); + } + + @Test + public void micExtendedSaltEncryptedTestVectors() throws Exception { + Gson gson = createJsonParser(); + TestVector[] testVectors = null; + try (BufferedReader br = + Files.newBufferedReader( + Paths.get( + "..", + "np_adv", + "resources", + "test", + "mic-extended-salt-encrypted-test-vectors.json"))) { + testVectors = gson.fromJson(br, new TypeToken<TestVector[]>() {}); + } + assertThat(testVectors).isNotNull(); + + for (TestVector tv : testVectors) { + byte[] privateKey = new byte[32]; + SecureRandom.getInstanceStrong().nextBytes(privateKey); + V1BroadcastCredential credential = + new V1BroadcastCredential(tv.keySeed, tv.identityToken, privateKey); + try (V1AdvertisementBuilder builder = V1AdvertisementBuilder.newEncrypted(); + V1SectionBuilder section = + nativeAddSaltedSection(builder.builder, credential, tv.sectionSalt)) { + for (DataElement de : tv.dataElements) { + section.addDataElement(new V1DataElement.Generic(de.deType, de.contents)); + } + section.finishSection(); + + byte[] adv = builder.build(); + byte[] sectionBytes = Arrays.copyOfRange(adv, 1, adv.length); + + assertThat(sectionBytes).isEqualTo(tv.encodedSection); + } + } + } + + @Test + public void createJsonParser_canParseTestVector() { + Gson gson = createJsonParser(); + String testJson = + "{ \"adv_header_byte\": \"20\", \"aes_key\": \"F3DB017C70E08EC5178C92F3AEA0C362\"," + + " \"data_elements\": [ { \"contents\": \"CF75D23EDA8F6E4A23\", \"de_type\": 383 }, {" + + " \"contents\": \"731B76151735869205CC41\", \"de_type\": 73 }, { \"contents\":" + + " \"7C2A8DE86B2CBB997703\", \"de_type\": 228 }, { \"contents\":" + + " \"99F5163DCA0BB9BE89755A6C5AB321\", \"de_type\": 446 } ], \"encoded_section\":" + + " \"6D91100056F596D16E1F87B107EE86102FFC6D5E9002F00C41CDDF533667362CD14AC54A9388FA3D30AA7CA6603071B8B0FA19BF582479F773F1C7D0EADF98E98B9447139F244D571B780475ACD9CB248F33B2C085925213360732D44081C27CA6EB40BD8A626BC776D88C5FDB09\"," + + " \"key_seed\": \"F0ED9126768CE7DC685FF74932AC5A876442C4E42359A43F720A575142A45043\"," + + " \"identity_token\": \"45795EE4C6533A830886E2C5885EB9E5\", \"nonce\":" + + " \"40E95D525FAEA1C1FEE39A8E\", \"section_mic_hmac_key\":" + + " \"A22386E85112EF883218A5B75669B7102E017E9AA149F408A079F60B1D14B4F4\"," + + " \"section_salt\": \"56F596D16E1F87B107EE86102FFC6D5E\" }"; + + TestVector testVector = gson.fromJson(testJson, TestVector.class); + + assertThat(testVector.aesKey) + .isEqualTo( + new byte[] { + (byte) 0xF3, + (byte) 0xDB, + (byte) 0x01, + (byte) 0x7C, + (byte) 0x70, + (byte) 0xE0, + (byte) 0x8E, + (byte) 0xC5, + (byte) 0x17, + (byte) 0x8C, + (byte) 0x92, + (byte) 0xF3, + (byte) 0xAE, + (byte) 0xA0, + (byte) 0xC3, + (byte) 0x62 + }); + } + + // Expose the test-only salted section API + private static native V1SectionBuilder nativeAddSaltedSection( + V1AdvertisementBuilder.V1BuilderHandle builder, + V1BroadcastCredential credential, + byte[] salt); +}
diff --git a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/credential/CredentialBookTests.java b/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/credential/CredentialBookTests.java deleted file mode 100644 index 12302d3..0000000 --- a/nearby/presence/np_java_ffi/test/com/google/android/nearby/presence/rust/credential/CredentialBookTests.java +++ /dev/null
@@ -1,40 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed 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 com.google.android.nearby.presence.rust.credential; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.*; - -import java.lang.ref.Cleaner; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -public class CredentialBookTests { - - @Mock Cleaner cleaner; - - @Test - void CredentialBook_wasRegisteredWithCleaner() { - try (CredentialBook book = new CredentialBook.Builder(cleaner).build()) { - assertThat(book).isNotNull(); - verify(cleaner).register(same(book), any()); - } - } -}
diff --git a/nearby/presence/rand_ext/Cargo.toml b/nearby/presence/rand_ext/Cargo.toml index 79a8ea8..da5744e 100644 --- a/nearby/presence/rand_ext/Cargo.toml +++ b/nearby/presence/rand_ext/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/sink/Cargo.toml b/nearby/presence/sink/Cargo.toml index a479205..60727c0 100644 --- a/nearby/presence/sink/Cargo.toml +++ b/nearby/presence/sink/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/test_helper/Cargo.toml b/nearby/presence/test_helper/Cargo.toml index 340a843..6fd2e3a 100644 --- a/nearby/presence/test_helper/Cargo.toml +++ b/nearby/presence/test_helper/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true @@ -11,3 +12,7 @@ hex.workspace = true serde_json.workspace = true itertools.workspace = true + +[features] +default = ["std"] +std = ["itertools/use_std"]
diff --git a/nearby/presence/test_helper/src/lib.rs b/nearby/presence/test_helper/src/lib.rs index 76dccf4..41446fd 100644 --- a/nearby/presence/test_helper/src/lib.rs +++ b/nearby/presence/test_helper/src/lib.rs
@@ -73,6 +73,7 @@ /// /// assert_eq!("0x12, 0x34", hex_bytes(&[0x12, 0x34])); /// ``` +#[cfg(feature = "std")] pub fn hex_bytes(data: impl AsRef<[u8]>) -> String { hex::encode_upper(data).chars().tuples().map(|(a, b)| format!("0x{}{}", a, b)).join(", ") }
diff --git a/nearby/presence/test_vector_hkdf/Cargo.toml b/nearby/presence/test_vector_hkdf/Cargo.toml index cf5d1a2..76d1e6c 100644 --- a/nearby/presence/test_vector_hkdf/Cargo.toml +++ b/nearby/presence/test_vector_hkdf/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/xts_aes/Cargo.toml b/nearby/presence/xts_aes/Cargo.toml index 86745c4..b003951 100644 --- a/nearby/presence/xts_aes/Cargo.toml +++ b/nearby/presence/xts_aes/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true [lints] workspace = true
diff --git a/nearby/presence/xts_aes/fuzz/Cargo.toml b/nearby/presence/xts_aes/fuzz/Cargo.toml index 5d99173..a231176 100644 --- a/nearby/presence/xts_aes/fuzz/Cargo.toml +++ b/nearby/presence/xts_aes/fuzz/Cargo.toml
@@ -4,6 +4,7 @@ authors = ["Automatically generated"] publish = false edition = "2018" +license = "Apache-2.0" [package.metadata] cargo-fuzz = true @@ -19,6 +20,9 @@ [target.'cfg(fuzzing)'.dependencies] libfuzzer-sys.workspace = true +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + [[bin]] name = "xts_roundtrip" path = "src/bin/xts_roundtrip.rs"
diff --git a/nearby/src/fuzzers.rs b/nearby/src/fuzzers.rs deleted file mode 100644 index e809518..0000000 --- a/nearby/src/fuzzers.rs +++ /dev/null
@@ -1,88 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed 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 cmd_runner::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr}; -use std::{fs, path}; - -pub(crate) fn run_rust_fuzzers(root: &path::Path) -> anyhow::Result<()> { - log::info!("Running rust fuzzers"); - run_cmd_shell_with_color::<YellowStderr>( - &root.join("presence/xts_aes"), - "cargo +nightly fuzz run xts_roundtrip -- -runs=10000 -max_total_time=60", - )?; - run_cmd_shell_with_color::<YellowStderr>( - &root.join("presence/ldt"), - "cargo +nightly fuzz run ldt_roundtrip -- -runs=10000 -max_total_time=60", - )?; - run_cmd_shell_with_color::<YellowStderr>( - &root.join("presence/ldt_np_adv"), - "cargo +nightly fuzz run ldt_np_decrypt -- -runs=10000 -max_total_time=60", - )?; - run_cmd_shell_with_color::<YellowStderr>( - &root.join("presence/ldt_np_adv"), - "cargo +nightly fuzz run ldt_np_roundtrip -- -runs=10000 -max_total_time=60", - )?; - run_cmd_shell_with_color::<YellowStderr>( - &root.join("connections/ukey2/ukey2_connections"), - "cargo +nightly fuzz run fuzz_connection -- -runs=10000 -max_total_time=60", - )?; - run_cmd_shell_with_color::<YellowStderr>( - &root.join("connections/ukey2/ukey2_connections"), - "cargo +nightly fuzz run fuzz_from_saved_session -- -runs=10000 -max_total_time=60", - )?; - run_cmd_shell_with_color::<YellowStderr>( - &root.join("connections/ukey2/ukey2_connections"), - "cargo +nightly fuzz run fuzz_handshake -- -runs=10000 -max_total_time=60", - )?; - run_cmd_shell_with_color::<YellowStderr>( - &root.join("crypto/crypto_provider_test"), - "cargo +nightly fuzz run fuzz_p256 -- -runs=10000 -max_total_time=60", - )?; - run_cmd_shell_with_color::<YellowStderr>( - &root.join("crypto/crypto_provider_test"), - concat!( - "cargo +nightly fuzz run fuzz_p256 --features=boringssl --no-default-features ", - "-- -runs=10000 -max_total_time=60" - ), - )?; - - Ok(()) -} - -// Runs the fuzztest fuzzers as short lived unit tests, compatible with gtest -pub(crate) fn build_fuzztest_unit_tests(root: &path::Path) -> anyhow::Result<()> { - log::info!("Checking fuzztest targets in unit test mode"); - run_cmd_shell(root, "cargo build -p np_c_ffi --release")?; - run_cmd_shell(root, "cargo build -p ldt_np_adv_ffi --release")?; - let build_dir = root.join("cmake-build"); - fs::create_dir_all(&build_dir)?; - run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake -G Ninja -DENABLE_FUZZ=true ..")?; - - for target in ["deserialization_fuzzer", "ldt_fuzzer"] { - run_cmd_shell_with_color::<YellowStderr>( - &build_dir, - format!("cmake --build . --target {}", target), - )?; - } - - run_cmd_shell_with_color::<YellowStderr>( - &build_dir.join("presence/np_cpp_ffi/fuzz/"), - "ctest", - )?; - run_cmd_shell_with_color::<YellowStderr>( - &build_dir.join("presence/ldt_np_adv_ffi/c/fuzz/"), - "ctest", - )?; - Ok(()) -}
diff --git a/remoteauth/Cargo.lock b/remoteauth/Cargo.lock index 7e17954..ab31216 100644 --- a/remoteauth/Cargo.lock +++ b/remoteauth/Cargo.lock
@@ -58,9 +58,9 @@ [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys", ] @@ -77,9 +77,9 @@ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "autocfg" @@ -98,7 +98,7 @@ ] [[package]] -name = "build-scripts" +name = "build_scripts" version = "0.1.0" dependencies = [ "anyhow", @@ -116,9 +116,9 @@ [[package]] name = "cc" -version = "1.0.97" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" [[package]] name = "cfg-if" @@ -142,9 +142,9 @@ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -152,9 +152,9 @@ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -164,9 +164,9 @@ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -176,9 +176,9 @@ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "cmd_runner" @@ -191,6 +191,8 @@ "globset", "log", "owo-colors", + "serde", + "serde_json", "shell-escape", "xshell", ] @@ -222,9 +224,9 @@ [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -259,9 +261,9 @@ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "ctap_protocol" @@ -390,15 +392,15 @@ [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "license" -version = "3.3.1" +version = "3.4.0+3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bba2f02ee1d13cd4bea565658939cd851d70e391f34f7c27b45b2077df3a2e4" +checksum = "a7da1e0d845faf299a9fe5f201a918a0dc0d5fc22c7b9580a6a23fed3a912b37" dependencies = [ "reword", "serde", @@ -413,9 +415,9 @@ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num-traits" @@ -447,9 +449,9 @@ [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -465,9 +467,9 @@ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -477,9 +479,9 @@ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -488,9 +490,9 @@ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "remote_auth_protocol" @@ -525,18 +527,18 @@ [[package]] name = "serde" -version = "1.0.200" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -545,9 +547,9 @@ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" dependencies = [ "itoa", "ryu", @@ -568,9 +570,9 @@ [[package]] name = "syn" -version = "2.0.61" +version = "2.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" dependencies = [ "proc-macro2", "quote", @@ -588,18 +590,18 @@ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", @@ -620,9 +622,9 @@ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "walkdir"
diff --git a/remoteauth/Cargo.toml b/remoteauth/Cargo.toml index d86adc0..68580eb 100644 --- a/remoteauth/Cargo.toml +++ b/remoteauth/Cargo.toml
@@ -1,27 +1,18 @@ [workspace] members = [ + "build_scripts", "ctap_protocol", "platform", "remote_auth_protocol", ] +default-members = ["build_scripts"] +resolver = "2" [workspace.package] version = "0.1.0" edition = "2021" publish = false +license = "Apache-2.0" [workspace.dependencies] anyhow = "1.0.72" - -[package] -name = "build-scripts" -version.workspace = true -edition.workspace = true -publish.workspace = true - -[dependencies] -anyhow.workspace = true -clap = { version = "4.0.25", features = ["derive"] } -cmd_runner = { path = "../common/cmd_runner" } -env_logger = "0.10.0" -log = "0.4.17" \ No newline at end of file
diff --git a/remoteauth/build_scripts/Cargo.toml b/remoteauth/build_scripts/Cargo.toml new file mode 100644 index 0000000..604a617 --- /dev/null +++ b/remoteauth/build_scripts/Cargo.toml
@@ -0,0 +1,13 @@ +[package] +name = "build_scripts" +version.workspace = true +edition.workspace = true +publish.workspace = true +license.workspace = true + +[dependencies] +anyhow.workspace = true +clap = { version = "4.5.13", features = ["derive"] } +cmd_runner = { path = "../../common/cmd_runner" } +env_logger = "0.10.0" +log = "0.4.17"
diff --git a/remoteauth/src/ctap_protocol.rs b/remoteauth/build_scripts/src/ctap_protocol.rs similarity index 100% rename from remoteauth/src/ctap_protocol.rs rename to remoteauth/build_scripts/src/ctap_protocol.rs
diff --git a/remoteauth/src/main.rs b/remoteauth/build_scripts/src/main.rs similarity index 83% rename from remoteauth/src/main.rs rename to remoteauth/build_scripts/src/main.rs index e0851eb..2caf8c0 100644 --- a/remoteauth/src/main.rs +++ b/remoteauth/build_scripts/src/main.rs
@@ -17,7 +17,10 @@ use clap::Parser as _; use cmd_runner::run_cmd_shell; use env_logger::Env; -use std::{env, path}; +use std::{ + env, + path::{self, PathBuf}, +}; mod ctap_protocol; mod platform; @@ -27,17 +30,20 @@ env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let cli: Cli = Cli::parse(); - let root_dir: path::PathBuf = env::var("CARGO_MANIFEST_DIR") - .expect("Must be run via Cargo to establish root directory") - .into(); + let build_scripts_dir = PathBuf::from( + env::var("CARGO_MANIFEST_DIR").expect("Must be run via Cargo to establish root directory"), + ); + let root_dir = build_scripts_dir + .parent() + .expect("build_scripts directory should have a parent"); match cli.subcommand { - Subcommand::CheckEverything(ref options) => check_everything(&root_dir, options)?, - Subcommand::CheckWorkspace(ref options) => check_workspace(&root_dir, options)?, - Subcommand::CheckCtapProtocol => ctap_protocol::check_ctap_protocol(&root_dir)?, - Subcommand::CheckPlatform => platform::check_platform(&root_dir)?, + Subcommand::CheckEverything(ref options) => check_everything(root_dir, options)?, + Subcommand::CheckWorkspace(ref options) => check_workspace(root_dir, options)?, + Subcommand::CheckCtapProtocol => ctap_protocol::check_ctap_protocol(root_dir)?, + Subcommand::CheckPlatform => platform::check_platform(root_dir)?, Subcommand::CheckRemoteAuthProtocol => { - remote_auth_protocol::check_remote_auth_protocol(&root_dir)? + remote_auth_protocol::check_remote_auth_protocol(root_dir)? } }
diff --git a/remoteauth/src/platform.rs b/remoteauth/build_scripts/src/platform.rs similarity index 100% rename from remoteauth/src/platform.rs rename to remoteauth/build_scripts/src/platform.rs
diff --git a/remoteauth/src/remote_auth_protocol.rs b/remoteauth/build_scripts/src/remote_auth_protocol.rs similarity index 100% rename from remoteauth/src/remote_auth_protocol.rs rename to remoteauth/build_scripts/src/remote_auth_protocol.rs
diff --git a/remoteauth/ctap_protocol/Cargo.toml b/remoteauth/ctap_protocol/Cargo.toml index 8a06840..d5ed9c2 100644 --- a/remoteauth/ctap_protocol/Cargo.toml +++ b/remoteauth/ctap_protocol/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
diff --git a/remoteauth/deny.toml b/remoteauth/deny.toml index 2bf5920..7f0eda7 100644 --- a/remoteauth/deny.toml +++ b/remoteauth/deny.toml
@@ -9,24 +9,6 @@ # The values provided in this template are the default values that will be used # when any section or field is not specified in your own configuration -# If 1 or more target triples (and optionally, target_features) are specified, -# only the specified targets will be checked when running `cargo deny check`. -# This means, if a particular package is only ever used as a target specific -# dependency, such as, for example, the `nix` crate only being used via the -# `target_family = "unix"` configuration, that only having windows targets in -# this list would mean the nix crate, as well as any of its exclusive -# dependencies not shared by any other crates, would be ignored, as the target -# list here is effectively saying which targets you are building for. -targets = [ - # The triple can be any string, but only the target triples built in to - # rustc (as of 1.40) can be checked against actual config expressions - #{ triple = "x86_64-unknown-linux-musl" }, - # You can also specify which target_features you promise are enabled for a - # particular target. target_features are currently not validated against - # the actual valid features supported by the target architecture. - #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, -] - # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html @@ -35,16 +17,8 @@ db-path = "~/.cargo/advisory-db" # The url(s) of the advisory databases to use db-urls = ["https://github.com/rustsec/advisory-db"] -# The lint level for security vulnerabilities -vulnerability = "deny" -# The lint level for unmaintained crates -unmaintained = "warn" # The lint level for crates that have been yanked from their source registry yanked = "warn" -# The lint level for crates with security notices. Note that as of -# 2019-12-17 there are no security notice advisories in -# https://github.com/rustsec/advisory-db -notice = "warn" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ @@ -70,8 +44,6 @@ # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] -# The lint level for crates which do not have a detectable license -unlicensed = "deny" unused-allowed-license = "allow" # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses @@ -87,26 +59,6 @@ "OpenSSL", "Unlicense" ] -# List of explicitly disallowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. -deny = [ - #"Nokia", -] -# Lint level for licenses considered copyleft -copyleft = "warn" -# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses -# * both - The license will be approved if it is both OSI-approved *AND* FSF -# * either - The license will be approved if it is either OSI-approved *OR* FSF -# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF -# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved -# * neither - This predicate is ignored and the default lint level is used -allow-osi-fsf-free = "neither" -# Lint level used when no other predicates are matched -# 1. License isn't in the allow or deny lists -# 2. License isn't copyleft -# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" -default = "deny" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. @@ -154,7 +106,7 @@ # published to private registries. # To see how to mark a crate as unpublished (to the official registry), # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. -ignore = true +ignore = false # One or more private registries that you might publish crates to, if a crate # is only published to private registries, and ignore is true, the crate will # not have its license(s) checked
diff --git a/remoteauth/platform/Cargo.toml b/remoteauth/platform/Cargo.toml index 0136c27..23b2074 100644 --- a/remoteauth/platform/Cargo.toml +++ b/remoteauth/platform/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
diff --git a/remoteauth/remote_auth_protocol/Cargo.toml b/remoteauth/remote_auth_protocol/Cargo.toml index e9fd6d2..e0fcad5 100644 --- a/remoteauth/remote_auth_protocol/Cargo.toml +++ b/remoteauth/remote_auth_protocol/Cargo.toml
@@ -3,6 +3,7 @@ version.workspace = true edition.workspace = true publish.workspace = true +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html