commit 19ca0b6bac25bf02474c8635b666ba9adb5e598a parent ff59a8d7ba1b759cd7f22a7350f465df0ad92fc5 Author: Bastian Gruber <foreach@me.com> Date: Wed, 22 Oct 2025 16:20:02 +0000 Bug 1993579 - Use latest application-services with viaduct OHTTP, r=bdk,markh,toolkit-telemetry-reviewers,TravisLong Differential Revision: https://phabricator.services.mozilla.com/D268203 Diffstat:
33 files changed, 1355 insertions(+), 88 deletions(-)
diff --git a/.cargo/config.toml.in b/.cargo/config.toml.in @@ -80,9 +80,9 @@ git = "https://github.com/martinthomson/ohttp.git" rev = "bf6a983845cc0b540effb3a615e92d914dfcfd0b" replace-with = "vendored-sources" -[source."git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c"] +[source."git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d"] git = "https://github.com/mozilla/application-services" -rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" replace-with = "vendored-sources" [source."git+https://github.com/mozilla/audioipc?rev=82fe7fa7e3aaa35468137239a0e4c2f867457214"] diff --git a/Cargo.lock b/Cargo.lock @@ -972,7 +972,7 @@ dependencies = [ [[package]] name = "context_id" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "chrono", "error-support", @@ -1897,7 +1897,7 @@ dependencies = [ [[package]] name = "error-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "env_logger", "error-support-macros", @@ -1912,7 +1912,7 @@ dependencies = [ [[package]] name = "error-support-macros" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "proc-macro2", "quote", @@ -2007,7 +2007,7 @@ dependencies = [ [[package]] name = "filter_adult" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "base64 0.22.1", "error-support", @@ -2043,7 +2043,7 @@ dependencies = [ [[package]] name = "firefox-versioning" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "serde_json", "thiserror 2.0.12", @@ -3410,7 +3410,7 @@ dependencies = [ [[package]] name = "init_rust_components" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "nss", "uniffi", @@ -3419,7 +3419,7 @@ dependencies = [ [[package]] name = "interrupt-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "lazy_static", "parking_lot", @@ -3600,7 +3600,7 @@ dependencies = [ [[package]] name = "jwcrypto" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "base64 0.21.999", "error-support", @@ -3924,7 +3924,7 @@ checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "logins" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "anyhow", "async-trait", @@ -4972,7 +4972,7 @@ dependencies = [ [[package]] name = "nss" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "base64 0.21.999", "error-support", @@ -5001,12 +5001,12 @@ dependencies = [ [[package]] name = "nss_build_common" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" [[package]] name = "nss_sys" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "libsqlite3-sys", "nss_build_common", @@ -5279,7 +5279,7 @@ checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "payload-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "serde", "serde_derive", @@ -5802,7 +5802,7 @@ dependencies = [ [[package]] name = "rc_crypto" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "base64 0.21.999", "error-support", @@ -5872,7 +5872,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relevancy" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "anyhow", "base64 0.21.999", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "remote_settings" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "anyhow", "camino", @@ -6188,7 +6188,7 @@ dependencies = [ [[package]] name = "search" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "error-support", "firefox-versioning", @@ -6492,7 +6492,7 @@ dependencies = [ [[package]] name = "sql-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "error-support", "interrupt-support", @@ -6683,7 +6683,7 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "suggest" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "anyhow", "chrono", @@ -6736,7 +6736,7 @@ dependencies = [ [[package]] name = "sync-guid" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "base64 0.21.999", "rand", @@ -6747,7 +6747,7 @@ dependencies = [ [[package]] name = "sync15" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "anyhow", "base16", @@ -6791,7 +6791,7 @@ dependencies = [ [[package]] name = "tabs" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "anyhow", "error-support", @@ -7104,7 +7104,7 @@ dependencies = [ [[package]] name = "tracing-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "parking_lot", "serde_json", @@ -7170,7 +7170,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "types" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "rusqlite 0.37.0", "serde", @@ -7568,7 +7568,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "viaduct" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "async-trait", "error-support", @@ -7733,7 +7733,7 @@ dependencies = [ [[package]] name = "webext-storage" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=cd04a4d1a74294ab08434d7a5a8152d25d25e53c#cd04a4d1a74294ab08434d7a5a8152d25d25e53c" +source = "git+https://github.com/mozilla/application-services?rev=7ee874e18f461a83f5c8c8927039cbde3225b60d#7ee874e18f461a83f5c8c8927039cbde3225b60d" dependencies = [ "anyhow", "error-support", diff --git a/Cargo.toml b/Cargo.toml @@ -265,22 +265,21 @@ ohttp = { git = "https://github.com/martinthomson/ohttp.git", rev = "bf6a983845c bhttp = { git = "https://github.com/martinthomson/ohttp.git", rev = "bf6a983845cc0b540effb3a615e92d914dfcfd0b" } # application-services overrides to make updating them all simpler. -context_id = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -error-support = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -filter_adult = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -relevancy = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -search = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -sql-support = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -suggest = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -sync15 = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -tabs = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -tracing-support = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -viaduct = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -webext-storage = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c" } -logins = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c", features = ["keydb"] } -init_rust_components = { git = "https://github.com/mozilla/application-services", rev = "cd04a4d1a74294ab08434d7a5a8152d25d25e53c", features = ["keydb"] } - +context_id = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +error-support = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +filter_adult = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +relevancy = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +search = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +sql-support = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +suggest = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +sync15 = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +tabs = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +tracing-support = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +viaduct = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +webext-storage = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +logins = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } +init_rust_components = { git = "https://github.com/mozilla/application-services", rev = "7ee874e18f461a83f5c8c8927039cbde3225b60d" } # Patched version of zip 2.4.2 to allow for reading omnijars. zip = { path = "third_party/rust/zip" } diff --git a/docs/rust-components/api/js/viaduct.md b/docs/rust-components/api/js/viaduct.md @@ -23,6 +23,26 @@ :members: :exclude-members: NonTlsUrl ``` +```{js:autoclass} RustViaduct.sys.OhttpChannelNotConfigured + :members: + :exclude-members: OhttpChannelNotConfigured +``` +```{js:autoclass} RustViaduct.sys.OhttpConfigFetchFailed + :members: + :exclude-members: OhttpConfigFetchFailed +``` +```{js:autoclass} RustViaduct.sys.OhttpNotSupported + :members: + :exclude-members: OhttpNotSupported +``` +```{js:autoclass} RustViaduct.sys.OhttpRequestError + :members: + :exclude-members: OhttpRequestError +``` +```{js:autoclass} RustViaduct.sys.OhttpResponseError + :members: + :exclude-members: OhttpResponseError +``` ```{js:autoclass} RustViaduct.sys.RequestHeaderError :members: :exclude-members: RequestHeaderError diff --git a/third_party/rust/error-support/.cargo-checksum.json b/third_party/rust/error-support/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"379d09265c4ad36b0e8aa71fab2a82c71946a33dc00314ff0a9efd07431b8642","README.md":"d820e387ac36a98c8e554fcaba13b76eb935413b535019444f6d448240e4d07e","src/handling.rs":"4b1183afe0716653918515299bc877a4d461fe8d2bb114a8c25f303203a35fdb","src/lib.rs":"206a0ec43780be42a4e6a6652237ab2f882a87b2a15454ac87a547d9c9b57548","src/macros.rs":"27366e0424e4d700605c34bd96295cd35fa41aed8b49f30e0b6e0c59b870fe73","src/redact.rs":"c9a4df1a87be68b15d583587bda941d4c60a1d0449e2d43ff99f3611a290a863","src/reporting.rs":"eda5580fabe633bd4fe7ac69ea8056874e8adfc093a74942294edcfbaa48824f","uniffi.toml":"af91bcd8e7b1fa3f475a5e556979ff23c57b338395e0b65abc1cb1a0ee823e23"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"379d09265c4ad36b0e8aa71fab2a82c71946a33dc00314ff0a9efd07431b8642","README.md":"d820e387ac36a98c8e554fcaba13b76eb935413b535019444f6d448240e4d07e","metrics.yaml":"d7404186be19150cfba00c7ffb4261479fd91f3b674151ed88d28d15172283b2","pings.yaml":"80e8cae15ec4b9369d92ffc2fd038931a108cfb1edcacd6f27e529a809979043","src/error_tracing.rs":"1a8738a90c5fd605f7d40a6cff2634989d06d18103157ccfe75fdb59cad8f432","src/handling.rs":"4b1183afe0716653918515299bc877a4d461fe8d2bb114a8c25f303203a35fdb","src/lib.rs":"f3860585d0cf613fe2974c5119f0738601c65c74aaba2c79311e440903dde254","src/macros.rs":"27366e0424e4d700605c34bd96295cd35fa41aed8b49f30e0b6e0c59b870fe73","src/redact.rs":"c9a4df1a87be68b15d583587bda941d4c60a1d0449e2d43ff99f3611a290a863","src/reporting.rs":"eda5580fabe633bd4fe7ac69ea8056874e8adfc093a74942294edcfbaa48824f","uniffi.toml":"af91bcd8e7b1fa3f475a5e556979ff23c57b338395e0b65abc1cb1a0ee823e23"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/error-support/metrics.yaml b/third_party/rust/error-support/metrics.yaml @@ -0,0 +1,59 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +--- +$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 + +rust_component_errors: + error_type: + type: string + description: > + Component supplied error type name. Used to aggregate error pings. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1991442 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1992235 + data_sensitivity: + - technical + notification_emails: + - synced-client-integrations@mozilla.com + - bdk@mozilla.com + expires: never + send_in_pings: + - rust-component-errors + details: + type: string + description: > + Components supplied error details. + Components are responsible for avoiding personal identifying information in this field, + use the `error_support::redact` crate to help with this. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1991442 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1992235 + data_sensitivity: + - technical + notification_emails: + - synced-client-integrations@mozilla.com + - bdk@mozilla.com + expires: never + send_in_pings: + - rust-component-errors + breadcrumbs: + type: string_list + description: > + Most recent 20 breadcrubs recorded by any component. + Like `description`, components are responsible for avoiding PII in this data. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1991442 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1992235 + data_sensitivity: + - technical + notification_emails: + - synced-client-integrations@mozilla.com + - bdk@mozilla.com + expires: never + send_in_pings: + - rust-component-errors diff --git a/third_party/rust/error-support/pings.yaml b/third_party/rust/error-support/pings.yaml @@ -0,0 +1,19 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +--- + +$schema: moz://mozilla.org/schemas/glean/pings/2-0-0 + +rust-component-errors: + description: > + Ping containing details about an error in a Rust component. + The Rust code rate limits error pings are limited to 20/component/day. + notification_emails: + - sync-team@mozilla.com + - bdk@mozilla.com + include_client_id: true + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1991442 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1992235 diff --git a/third_party/rust/error-support/src/error_tracing.rs b/third_party/rust/error-support/src/error_tracing.rs @@ -0,0 +1,190 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use parking_lot::Mutex; + +static RECENT_BREADCRUMBS: Mutex<BreadcrumbRingBuffer> = Mutex::new(BreadcrumbRingBuffer::new()); + +pub fn report_error_to_app(type_name: String, message: String) { + // Report errors by sending a tracing event to the `app-services-error-reporter::error` target. + // + // Applications should register for these events and send a glean error ping when they occur. + // + // breadcrumbs will be sent in the `breadcrumbs` field as a single string, with each individual + // breadcrumb joined by newlines. + let breadcrumbs = RECENT_BREADCRUMBS.lock().get_breadcrumbs().join("\n"); + tracing_support::error!(target: "app-services-error-reporter::error", message, type_name, breadcrumbs); +} + +pub fn report_breadcrumb(message: String, module: String, line: u32, column: u32) { + // When we see a breadcrumb: + // - Push it to the `RECENT_BREADCRUMBS` list + // - Send out the `app-services-error-reporter::breadcrumb`. Applications can register for + // these events and log them. + RECENT_BREADCRUMBS.lock().push(message.clone()); + tracing_support::info!(target: "app-services-error-reporter::breadcrumb", message, module, line, column); +} + +/// Ring buffer implementation that we use to store the most recent 20 breadcrumbs +#[derive(Default)] +struct BreadcrumbRingBuffer { + breadcrumbs: Vec<String>, + pos: usize, +} + +impl BreadcrumbRingBuffer { + const MAX_ITEMS: usize = 20; + + const fn new() -> Self { + Self { + breadcrumbs: Vec::new(), + pos: 0, + } + } + + fn push(&mut self, breadcrumb: impl Into<String>) { + let breadcrumb = breadcrumb.into(); + if self.breadcrumbs.len() < Self::MAX_ITEMS { + self.breadcrumbs.push(breadcrumb); + } else { + self.breadcrumbs[self.pos] = breadcrumb; + self.pos = (self.pos + 1) % Self::MAX_ITEMS; + } + } + + fn get_breadcrumbs(&self) -> Vec<String> { + let mut breadcrumbs = Vec::from(&self.breadcrumbs[self.pos..]); + breadcrumbs.extend(self.breadcrumbs[..self.pos].iter().map(|s| s.to_string())); + breadcrumbs + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_buffer() { + let mut buf = BreadcrumbRingBuffer::default(); + buf.push("00"); + buf.push("01"); + buf.push("02"); + buf.push("03"); + buf.push("04"); + buf.push("05"); + buf.push("06"); + buf.push("07"); + buf.push("08"); + buf.push("09"); + assert_eq!( + buf.get_breadcrumbs(), + vec![ + "00".to_string(), + "01".to_string(), + "02".to_string(), + "03".to_string(), + "04".to_string(), + "05".to_string(), + "06".to_string(), + "07".to_string(), + "08".to_string(), + "09".to_string(), + ] + ); + + buf.push("10"); + buf.push("11"); + buf.push("12"); + buf.push("13"); + buf.push("14"); + buf.push("15"); + buf.push("16"); + buf.push("17"); + buf.push("18"); + buf.push("19"); + assert_eq!( + buf.get_breadcrumbs(), + vec![ + "00".to_string(), + "01".to_string(), + "02".to_string(), + "03".to_string(), + "04".to_string(), + "05".to_string(), + "06".to_string(), + "07".to_string(), + "08".to_string(), + "09".to_string(), + "10".to_string(), + "11".to_string(), + "12".to_string(), + "13".to_string(), + "14".to_string(), + "15".to_string(), + "16".to_string(), + "17".to_string(), + "18".to_string(), + "19".to_string(), + ] + ); + + buf.push("20"); + assert_eq!( + buf.get_breadcrumbs(), + vec![ + "01".to_string(), + "02".to_string(), + "03".to_string(), + "04".to_string(), + "05".to_string(), + "06".to_string(), + "07".to_string(), + "08".to_string(), + "09".to_string(), + "10".to_string(), + "11".to_string(), + "12".to_string(), + "13".to_string(), + "14".to_string(), + "15".to_string(), + "16".to_string(), + "17".to_string(), + "18".to_string(), + "19".to_string(), + "20".to_string(), + ] + ); + + buf.push("21"); + buf.push("22"); + buf.push("23"); + buf.push("24"); + buf.push("25"); + assert_eq!( + buf.get_breadcrumbs(), + vec![ + "06".to_string(), + "07".to_string(), + "08".to_string(), + "09".to_string(), + "10".to_string(), + "11".to_string(), + "12".to_string(), + "13".to_string(), + "14".to_string(), + "15".to_string(), + "16".to_string(), + "17".to_string(), + "18".to_string(), + "19".to_string(), + "20".to_string(), + "21".to_string(), + "22".to_string(), + "23".to_string(), + "24".to_string(), + "25".to_string(), + ] + ); + } +} diff --git a/third_party/rust/error-support/src/lib.rs b/third_party/rust/error-support/src/lib.rs @@ -71,21 +71,14 @@ pub use redact::*; mod reporting; #[cfg(not(feature = "tracing-reporting"))] pub use reporting::{ - set_application_error_reporter, unset_application_error_reporter, ApplicationErrorReporter, + report_breadcrumb, report_error_to_app, set_application_error_reporter, + unset_application_error_reporter, ApplicationErrorReporter, }; #[cfg(feature = "tracing-reporting")] -mod reporting { - pub fn report_error_to_app(type_name: String, message: String) { - tracing_support::error!(target: "app-services-error-reporter::error", message, type_name); - } - - pub fn report_breadcrumb(message: String, module: String, line: u32, column: u32) { - tracing_support::info!(target: "app-services-error-reporter::breadcrumb", message, module, line, column); - } -} - -pub use reporting::{report_breadcrumb, report_error_to_app}; +mod error_tracing; +#[cfg(feature = "tracing-reporting")] +pub use error_tracing::{report_breadcrumb, report_error_to_app}; pub use error_support_macros::handle_error; diff --git a/third_party/rust/init_rust_components/.cargo-checksum.json b/third_party/rust/init_rust_components/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"4fec7d9fed833f35258bbde30b032257bc1967ab8ddb9987c307c8a9c1ee323f","README.md":"4c4ba1a04d445dca0507eef3ff8cd79e0954474d2a75feadd98946187fd8374f","src/lib.rs":"a296a8e15e029e513501fe40f3df5ebc59d3817b4a3242d5eb5e4f7b59d2d832","uniffi.toml":"2e98a909732dc83db0c0b41438b0935c1fd5f869cea327e3ef5501fae5fa5d01"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"6d78f7622d841fb3a75edbf01d731f853e9ba4c20bf46bb2744e25ce632501b4","README.md":"4c4ba1a04d445dca0507eef3ff8cd79e0954474d2a75feadd98946187fd8374f","src/lib.rs":"421e93bbc63bdcc41131b2ef740be98d00c4151775ef164857671430666f7ebd","uniffi.toml":"2e98a909732dc83db0c0b41438b0935c1fd5f869cea327e3ef5501fae5fa5d01"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/init_rust_components/Cargo.toml b/third_party/rust/init_rust_components/Cargo.toml @@ -25,7 +25,12 @@ readme = "README.md" license = "MPL-2.0" [features] +default = [] keydb = ["nss/keydb"] +ohttp = [ + "dep:viaduct", + "viaduct/ohttp", +] [lib] name = "init_rust_components" @@ -36,3 +41,7 @@ path = "../support/rc_crypto/nss" [dependencies.uniffi] version = "0.29.0" + +[dependencies.viaduct] +path = "../viaduct" +optional = true diff --git a/third_party/rust/init_rust_components/src/lib.rs b/third_party/rust/init_rust_components/src/lib.rs @@ -7,7 +7,8 @@ use nss::ensure_initialized as ensure_nss_initialized; #[cfg(feature = "keydb")] use nss::ensure_initialized_with_profile_dir as ensure_nss_initialized_with_profile_dir; - +#[cfg(feature = "ohttp")] +use viaduct::ohttp::configure_default_ohttp_channels; uniffi::setup_scaffolding!(); /// Global initialization routines for Rust components. Must be called before any other calls to @@ -20,6 +21,12 @@ uniffi::setup_scaffolding!(); #[uniffi::export] pub fn initialize() { ensure_nss_initialized(); + + #[cfg(feature = "ohttp")] + { + configure_default_ohttp_channels() + .expect("We pass down hard coded Strings for the relays, if this fails, we have a typo in the config we pass down."); + } } /// Global initialization routines for Rust components, when `logins/keydb` feature is activated. Must be @@ -34,4 +41,10 @@ pub fn initialize() { #[uniffi::export] pub fn initialize(profile_path: String) { ensure_nss_initialized_with_profile_dir(profile_path); + + #[cfg(feature = "ohttp")] + { + configure_default_ohttp_channels() + .expect("We pass down hard coded Strings for the relays, if this fails, we have a typo in the config we pass down."); + } } diff --git a/third_party/rust/logins/.cargo-checksum.json b/third_party/rust/logins/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"932aaba3c932b809da6c86cc2c808cbba983011b923545674e62dc3a164867eb","README.md":"2c25808f8371b5d9ceb58e51f71304a270d5b33dad5a280a6e95e06e606f4492","build.rs":"63ff52215682b7d516679e7aaeaaaf5d3ac98ebdf0c08361193c34c99506bfdf","fixtures/profile/.gitignore":"611878d7fde9ba090134c1d0caffe525e69d63a46b7f6060ce8ca061b8524e59","fixtures/profile/README.md":"a801e141cf954b809aed59d7a0f3a573dce23231035dbab95c6350117c346a75","fixtures/profile/key4.db":"065522fb32423422a99ed096bee267f28068881b186a9258c8f0efc19f7bd89b","metrics.yaml":"a875db3d9d759935f43131bd5c830307f8fe5d42df0e47768deb91e4568d0f6b","src/db.rs":"0c52daac61d9d91d2197e3033e19fea0c62545776e92ba138d5e2708ebe13825","src/encryption.rs":"30d4683ba394b61f52713c05bc96296333f5eda0e841820b7b4f82be7bbd9381","src/error.rs":"236b8d9b5b497204cbe7a528adb07ceed16c421706a36ebeb88e1bfe259d0ded","src/lib.rs":"31e8775f54fc3871b9096140df80df538bbe13b3f7263841affe365c7862ae8f","src/login.rs":"ef152c51b38be6ae6edb2890fd5972dd1fe66d7fdb5af803f8709375e3860fa4","src/logins.udl":"92efe79861974a15e42a889e7ea2bc0d44f1089b78d23122f490fd2cf9020828","src/schema.rs":"a652eec0b5ada90b8ed9ac481616d070f212ebd1b4238b0bc1b7768700c55ffc","src/store.rs":"772bba97f3f8fc579be850e6647d4c9b36f8d0a8b9488c06a6be93c3e02ebe77","src/sync/engine.rs":"62f4812b68962b7c55857314afd30adc3e00bd18335b71b40e110284679c6c29","src/sync/merge.rs":"6469f8bd582358c96bc35579ac228c95ad4bd64e84a29dda0cde0d87d2dcc5ed","src/sync/mod.rs":"00eb3bdd5fcac411fb314400a3d66aea874aa117db1003caf75ea43d385e372e","src/sync/payload.rs":"202f56cf0af9ffc3cc3d57c72ad61e4fa006bf5104386d9ef58a2611d539accb","src/sync/update_plan.rs":"c1d45f5972237785e274b1ecbd350cef7c1d7f530e66b9068d707eb1dfa1304e","src/util.rs":"1ced78e185164640e9859b0316f4798ccacd30bad9d6c549f9f650350a5f97b6","uniffi.toml":"1b1ea1a488fc051b9fe5a289e474462f7c676bcd02ca176713d885d280414bf6"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"932aaba3c932b809da6c86cc2c808cbba983011b923545674e62dc3a164867eb","README.md":"2c25808f8371b5d9ceb58e51f71304a270d5b33dad5a280a6e95e06e606f4492","build.rs":"63ff52215682b7d516679e7aaeaaaf5d3ac98ebdf0c08361193c34c99506bfdf","fixtures/profile/.gitignore":"48343efff5004bb5907ff57074cf7bd3e7757661f1f0e8816388102848a41976","fixtures/profile/README.md":"a801e141cf954b809aed59d7a0f3a573dce23231035dbab95c6350117c346a75","fixtures/profile/key4.db":"065522fb32423422a99ed096bee267f28068881b186a9258c8f0efc19f7bd89b","fixtures/profile/logins.db":"561cbb773cf79d0d9934303d9ffc2f46dc690dfd87bcffa2df2f7e9009af2056","metrics.yaml":"a875db3d9d759935f43131bd5c830307f8fe5d42df0e47768deb91e4568d0f6b","src/db.rs":"0c52daac61d9d91d2197e3033e19fea0c62545776e92ba138d5e2708ebe13825","src/encryption.rs":"32bf066d1c7de705175474adcc159e0d9ab06cfc769b96680db96897e8d3afce","src/error.rs":"236b8d9b5b497204cbe7a528adb07ceed16c421706a36ebeb88e1bfe259d0ded","src/lib.rs":"31e8775f54fc3871b9096140df80df538bbe13b3f7263841affe365c7862ae8f","src/login.rs":"ef152c51b38be6ae6edb2890fd5972dd1fe66d7fdb5af803f8709375e3860fa4","src/logins.udl":"92efe79861974a15e42a889e7ea2bc0d44f1089b78d23122f490fd2cf9020828","src/schema.rs":"a652eec0b5ada90b8ed9ac481616d070f212ebd1b4238b0bc1b7768700c55ffc","src/store.rs":"dba75fe478ae871919ae9c6542f6bc547ade8cecd318e776ceb89a0055b871b4","src/sync/engine.rs":"62f4812b68962b7c55857314afd30adc3e00bd18335b71b40e110284679c6c29","src/sync/merge.rs":"6469f8bd582358c96bc35579ac228c95ad4bd64e84a29dda0cde0d87d2dcc5ed","src/sync/mod.rs":"00eb3bdd5fcac411fb314400a3d66aea874aa117db1003caf75ea43d385e372e","src/sync/payload.rs":"202f56cf0af9ffc3cc3d57c72ad61e4fa006bf5104386d9ef58a2611d539accb","src/sync/update_plan.rs":"c1d45f5972237785e274b1ecbd350cef7c1d7f530e66b9068d707eb1dfa1304e","src/util.rs":"1ced78e185164640e9859b0316f4798ccacd30bad9d6c549f9f650350a5f97b6","uniffi.toml":"1b1ea1a488fc051b9fe5a289e474462f7c676bcd02ca176713d885d280414bf6"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/logins/fixtures/profile/.gitignore b/third_party/rust/logins/fixtures/profile/.gitignore @@ -1 +1,2 @@ +!logins.db pkcs11.txt diff --git a/third_party/rust/logins/fixtures/profile/logins.db b/third_party/rust/logins/fixtures/profile/logins.db Binary files differ. diff --git a/third_party/rust/logins/src/encryption.rs b/third_party/rust/logins/src/encryption.rs @@ -493,12 +493,29 @@ mod keydb_test { #[test] fn test_nss_key_manager() { ensure_initialized_with_profile_dir(profile_path()); + // `password` is the primary password of the profile fixture let mock_primary_password_authenticator = MockPrimaryPasswordAuthenticator { password: "password".to_string(), }; let nss_key_manager = NSSKeyManager { primary_password_authenticator: Arc::new(mock_primary_password_authenticator), }; - assert_eq!(nss_key_manager.get_key().unwrap().len(), 63) + // key from fixtures/profile/key4.db + assert_eq!( + nss_key_manager.get_key().unwrap(), + [ + 123, 34, 107, 116, 121, 34, 58, 34, 111, 99, 116, 34, 44, 34, 107, 34, 58, 34, 66, + 74, 104, 84, 108, 103, 51, 118, 56, 49, 65, 66, 51, 118, 87, 50, 71, 122, 54, 104, + 69, 54, 84, 116, 75, 83, 112, 85, 102, 84, 86, 75, 73, 83, 99, 74, 45, 77, 78, 83, + 67, 117, 99, 34, 125 + ] + .to_vec() + ) + } + + #[test] + fn test_primary_password_authentication() { + ensure_initialized_with_profile_dir(profile_path()); + assert!(authenticate_with_primary_password("password").unwrap()); } } diff --git a/third_party/rust/logins/src/store.rs b/third_party/rust/logins/src/store.rs @@ -546,3 +546,53 @@ fn test_send() { fn ensure_send<T: Send>() {} ensure_send::<LoginStore>(); } + +#[cfg(feature = "keydb")] +#[cfg(test)] +mod keydb_test { + use super::*; + use crate::{ManagedEncryptorDecryptor, NSSKeyManager, PrimaryPasswordAuthenticator}; + use async_trait::async_trait; + use nss::ensure_initialized_with_profile_dir; + use std::path::PathBuf; + + struct MockPrimaryPasswordAuthenticator { + password: String, + } + + #[async_trait] + impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator { + async fn get_primary_password(&self) -> ApiResult<String> { + Ok(self.password.clone()) + } + async fn on_authentication_success(&self) -> ApiResult<()> { + Ok(()) + } + async fn on_authentication_failure(&self) -> ApiResult<()> { + Ok(()) + } + } + + fn profile_path() -> PathBuf { + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/profile") + } + + #[test] + fn decrypting_logins_with_primary_password() { + ensure_initialized_with_profile_dir(profile_path()); + // `password` is the primary password of the profile fixture + let primary_password_authenticator = MockPrimaryPasswordAuthenticator { + password: "password".to_string(), + }; + let key_manager = NSSKeyManager::new(Arc::new(primary_password_authenticator)); + let encdec = ManagedEncryptorDecryptor::new(Arc::new(key_manager)); + let store = LoginStore::new(profile_path().join("logins.db"), Arc::new(encdec)) + .expect("store from fixtures"); + let list = store.list().expect("Grabbing list to work"); + assert_eq!(list.len(), 1); + + assert_eq!(list[0].origin, "https://www.example.com"); + assert_eq!(list[0].username, "test"); + assert_eq!(list[0].password, "test"); + } +} diff --git a/third_party/rust/nss_build_common/.cargo-checksum.json b/third_party/rust/nss_build_common/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"59bb44e9fda258667a117c29c7ebe563eba3a69dd5aa63b04a8303e311f0c5c6","src/lib.rs":"0086dca9a1c0f0b3ea67bcb5881583fd877912bfc3657620544d52982d3ebf2d"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"59bb44e9fda258667a117c29c7ebe563eba3a69dd5aa63b04a8303e311f0c5c6","src/lib.rs":"7a092b14da86fb2be28597141956365d471a24f7a08dc7d8b93c35e1bf6dba6f"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/nss_build_common/src/lib.rs b/third_party/rust/nss_build_common/src/lib.rs @@ -92,13 +92,13 @@ fn link_nss_libs(kind: LinkingKind) { let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); if target_arch == "x86_64" && target_os == "android" { let android_home = env::var("ANDROID_HOME").expect("ANDROID_HOME not set"); - const ANDROID_NDK_VERSION: &str = "28.2.13676358"; + const ANDROID_NDK_VERSION: &str = "29.0.14206865"; // One of these will exist, depending on the host platform. const DARWIN_X86_64_LIB_DIR: &str = - "/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/19/lib/linux/"; + "/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/21/lib/linux/"; println!("cargo:rustc-link-search={android_home}/ndk/{ANDROID_NDK_VERSION}/{DARWIN_X86_64_LIB_DIR}"); const LINUX_X86_64_LIB_DIR: &str = - "/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/19/lib/linux/"; + "/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/21/lib/linux/"; println!("cargo:rustc-link-search={android_home}/ndk/{ANDROID_NDK_VERSION}/{LINUX_X86_64_LIB_DIR}"); println!("cargo:rustc-link-lib=static=clang_rt.builtins-x86_64-android"); } diff --git a/third_party/rust/tracing-support/.cargo-checksum.json b/third_party/rust/tracing-support/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"a70b253ae9ed4a9d8f5a87f05f269f0b12775ba5705a638edb6316970f7e5c03","android/build.gradle":"7eb3991c22ae47043c9754db53091493192a76ca4709f040a13aa8b5cd5bc64f","android/src/main/AndroidManifest.xml":"0edcec96c2345be661da3ead2714e059418ff5154123509923c344b66b4890c1","src/filters.rs":"8d15290905b251bd713f9fdb0513b23898e2f8015d65920ffc90e02bfa784ce0","src/layer.rs":"bc447375b405c3e4686f11f61113a42c23371f81472c65123e5d164b0c75c18f","src/lib.rs":"f6c8b4ffbe95488e150a51563ac98d2dba2ba9cd08376a75363503c536c4a85b","uniffi.toml":"357c46607bdec389b533ea0a908d98586b3fa2b6de2fa6263ca6903d13156f30"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"a70b253ae9ed4a9d8f5a87f05f269f0b12775ba5705a638edb6316970f7e5c03","android/build.gradle":"7eb3991c22ae47043c9754db53091493192a76ca4709f040a13aa8b5cd5bc64f","android/src/main/AndroidManifest.xml":"0edcec96c2345be661da3ead2714e059418ff5154123509923c344b66b4890c1","src/filters.rs":"8d15290905b251bd713f9fdb0513b23898e2f8015d65920ffc90e02bfa784ce0","src/layer.rs":"fea35aa34452c5a9c70b4f3e65a981f57c67745ea509936909260b8417354316","src/lib.rs":"f02180d894933c96720d374b9da6414996d81e0d8092bc589e003ba793ac7b6e","uniffi.toml":"357c46607bdec389b533ea0a908d98586b3fa2b6de2fa6263ca6903d13156f30"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/tracing-support/src/layer.rs b/third_party/rust/tracing-support/src/layer.rs @@ -37,7 +37,7 @@ pub fn register_event_sink(target: &str, level: crate::Level, sink: Arc<dyn Even /// Register an event sink that will receive events based on a minimum level /// /// If an event's level is at least `level`, then the event will be sent to this sink. -/// If so, sinks registered with `register_event_sink` will be skiped. +/// If so, sinks registered with `register_event_sink` will still be processed. /// /// There can only be 1 min-level sink registered at once. pub fn register_min_level_event_sink(level: crate::Level, sink: Arc<dyn EventSink>) { @@ -96,7 +96,6 @@ where if let Some(entry) = &*MIN_LEVEL_SINK.read() { if entry.level >= *event.metadata().level() { entry.send_event(event); - return; } } diff --git a/third_party/rust/tracing-support/src/lib.rs b/third_party/rust/tracing-support/src/lib.rs @@ -218,12 +218,12 @@ mod tests { info!(target: "first_target", extra=-1, "event message"); debug!(target: "first_target", extra=-2, "event message (should be filtered)"); - debug!(target: "second_target", extra=-3, "event message2"); + debug!(target: "second_target::submodule", extra=-3, "event message2"); info!(target: "third_target", extra=-4, "event message (should be filtered)"); - // This should only go to the level sink, since it's an error + // This should go to both the level sink and target sink, since it's an error error!(target: "first_target", extra=-5, "event message"); - assert_eq!(sink.events.read().len(), 2); + assert_eq!(sink.events.read().len(), 3); assert_eq!(level_sink.events.read().len(), 1); let event = &sink.events.read()[0]; @@ -233,15 +233,21 @@ mod tests { assert_eq!(event.fields.get("extra").unwrap().as_i64(), Some(-1)); let event2 = &sink.events.read()[1]; - assert_eq!(event2.target, "second_target"); + assert_eq!(event2.target, "second_target::submodule"); assert_eq!(event2.level, Level::Debug); assert_eq!(event2.message, "event message2"); assert_eq!(event2.fields.get("extra").unwrap().as_i64(), Some(-3)); - let event3 = &level_sink.events.read()[0]; + let event3 = &sink.events.read()[2]; assert_eq!(event3.target, "first_target"); assert_eq!(event3.level, Level::Error); assert_eq!(event3.message, "event message"); assert_eq!(event3.fields.get("extra").unwrap().as_i64(), Some(-5)); + + let level_event = &level_sink.events.read()[0]; + assert_eq!(level_event.target, "first_target"); + assert_eq!(level_event.level, Level::Error); + assert_eq!(level_event.message, "event message"); + assert_eq!(level_event.fields.get("extra").unwrap().as_i64(), Some(-5)); } } diff --git a/third_party/rust/viaduct/.cargo-checksum.json b/third_party/rust/viaduct/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"d978a9d8dd9e141b50c773cd62a5fae6c9ef9e4357c8e1212adc08d6c9cef840","README.md":"d73ab3e981a55c60a3c0995daa46fee6e513e8d78fb3c34691dd47320547695e","src/backend.rs":"501657c02aff05a987eb9d1a94aef8725a4db0af8a4d7c4959a49b3dc048fa68","src/backend/ffi.rs":"1c07fbfd807ee63ce32d5ac1000b8e0eef0a660ac4aa48c6ff15abf59fdadb75","src/client.rs":"1e0bba022e9ddcb58ff5754254e7879dbcd36dfb427815259bc4409e54502372","src/error.rs":"7f9ada23416b5cb91ffa3705bf73f621ebde45916f3365535f08ff8ebce43d03","src/fetch_msg_types.proto":"de8a46a4947a140783a4d714364f18ccf02c4759d6ab5ace9da0b1c058efa6c3","src/headers.rs":"3e108f0867e3a928d59ac8bbc95e4848dec025654562bc034772aed0e8396165","src/headers/name.rs":"d7304e006278a5466b5fe09e194a9ca921b565d76e24be491ec6178f527d2d61","src/lib.rs":"2f314771362420b3f14434f9a9d63fe4f7e361fae83bb4b246fe292cbce966ab","src/mozilla.appservices.httpconfig.protobuf.rs":"9ede762489a0c07bc08a5b852b33013a410cb41b44b92a44555f85bb2db91412","src/new_backend.rs":"3c8f5eaed4c96b9208035c9e288ca1dc3ed2178dcf223017721aaa6e4803ff9a","src/settings.rs":"d2788035fed3bbbb43ed4ddbb832fbd98019cce1c34e32ffa21c74a8c127b540","uniffi.toml":"d73e88bf94a6b2c4a94bd0bada509542dbbadb3be1bf4ad32f52e465af6503d7"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"0b5140ab92d21de6dac878ac7879062b447782634c5837686276990de77661ff","README.md":"d73ab3e981a55c60a3c0995daa46fee6e513e8d78fb3c34691dd47320547695e","src/backend.rs":"501657c02aff05a987eb9d1a94aef8725a4db0af8a4d7c4959a49b3dc048fa68","src/backend/ffi.rs":"1c07fbfd807ee63ce32d5ac1000b8e0eef0a660ac4aa48c6ff15abf59fdadb75","src/client.rs":"0c35db14ba2a1952a1543bc3e8307bfb94de4b73964f58aecbac599a562b2246","src/error.rs":"2bf09dee6a0fbce7f0a7ffb4d5a01c59d458634336e8767399ed42ce7e9b1287","src/fetch_msg_types.proto":"de8a46a4947a140783a4d714364f18ccf02c4759d6ab5ace9da0b1c058efa6c3","src/headers.rs":"63bfa3ba4953f1900a56e3cc6e15b0eb8bd1268cdb0020a1b86391f1043dab82","src/headers/name.rs":"d7304e006278a5466b5fe09e194a9ca921b565d76e24be491ec6178f527d2d61","src/lib.rs":"9eb7c5a81feca0cf7fa22af3d1f32e8043bf7259765c4018e8cd53ee65d91933","src/mozilla.appservices.httpconfig.protobuf.rs":"9ede762489a0c07bc08a5b852b33013a410cb41b44b92a44555f85bb2db91412","src/new_backend.rs":"c2a7211498c89b3445f0048a79a1d3a4f5e10e53a304fe126cff04a165f90c4e","src/ohttp.rs":"d2edbb15f15d3ade8fad1eb13a767fcfe65e6b0b76b3bfb599af379608be4575","src/ohttp_client.rs":"cb91882a05ddcc1fe759dcab0d4cff7570571600922bef9cce3207b6a79fe249","src/settings.rs":"d2788035fed3bbbb43ed4ddbb832fbd98019cce1c34e32ffa21c74a8c127b540","uniffi.toml":"d73e88bf94a6b2c4a94bd0bada509542dbbadb3be1bf4ad32f52e465af6503d7"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/viaduct/Cargo.toml b/third_party/rust/viaduct/Cargo.toml @@ -27,6 +27,17 @@ autobenches = false readme = "README.md" license = "MPL-2.0" +[features] +backend-dev = [ + "dep:tokio", + "dep:hyper", +] +default = [] +ohttp = [ + "dep:bhttp", + "dep:ohttp", +] + [lib] name = "viaduct" crate-type = ["lib"] @@ -43,11 +54,43 @@ serde_json = "1" thiserror = "2" url = "2" +[dependencies.bhttp] +git = "https://github.com/martinthomson/ohttp.git" +rev = "bf6a983845cc0b540effb3a615e92d914dfcfd0b" +optional = true + [dependencies.error-support] path = "../support/error" +[dependencies.hyper] +version = "0.14" +features = [ + "client", + "http1", + "http2", + "tcp", +] +optional = true + +[dependencies.ohttp] +git = "https://github.com/martinthomson/ohttp.git" +rev = "bf6a983845cc0b540effb3a615e92d914dfcfd0b" +features = [ + "client", + "server", + "app-svc", + "external-sqlite", +] +optional = true +default-features = false + [dependencies.parking_lot] version = ">=0.11,<=0.12" +[dependencies.tokio] +version = "1" +features = ["rt-multi-thread"] +optional = true + [dependencies.uniffi] version = "0.29.0" diff --git a/third_party/rust/viaduct/src/client.rs b/third_party/rust/viaduct/src/client.rs @@ -18,8 +18,40 @@ impl Client { Self { settings } } + /// Create a client that uses OHTTP with the specified channel for all requests + #[cfg(feature = "ohttp")] + pub fn with_ohttp_channel( + channel: &str, + settings: ClientSettings, + ) -> Result<Self, crate::ViaductError> { + if !crate::ohttp::is_ohttp_channel_configured(channel) { + return Err(crate::ViaductError::OhttpChannelNotConfigured( + channel.to_string(), + )); + } + let mut client_settings = settings; + client_settings.ohttp_channel = Some(channel.to_string()); + Ok(Self { + settings: client_settings, + }) + } + pub async fn send(&self, request: Request) -> Result<Response> { validate_request(&request)?; + + // Check if this client should use OHTTP for all requests + #[cfg(feature = "ohttp")] + if let Some(channel) = &self.settings.ohttp_channel { + crate::debug!( + "Client configured for OHTTP channel '{}', processing request via OHTTP", + channel + ); + return crate::ohttp::process_ohttp_request(request, channel, self.settings.clone()) + .await; + } + + // For non-OHTTP requests, use the normal backend + crate::debug!("Processing request via standard backend"); get_backend()? .send_request(request, self.settings.clone()) .await @@ -39,6 +71,9 @@ pub struct ClientSettings { // Maximum amount of redirects to follow (0 means redirects are not allowed) #[uniffi(default = 10)] pub redirect_limit: u32, + // OHTTP channel to use for all requests (if any) + #[cfg(feature = "ohttp")] + pub ohttp_channel: Option<String>, } impl Default for ClientSettings { @@ -49,6 +84,8 @@ impl Default for ClientSettings { #[cfg(not(target_os = "ios"))] timeout: 10000, redirect_limit: 10, + #[cfg(feature = "ohttp")] + ohttp_channel: None, } } } diff --git a/third_party/rust/viaduct/src/error.rs b/third_party/rust/viaduct/src/error.rs @@ -31,6 +31,21 @@ pub enum ViaductError { #[error("[no-sentry] Validation error: URL does not use TLS protocol.")] NonTlsUrl, + + #[error("OHTTP channel '{0}' is not configured")] + OhttpChannelNotConfigured(String), + + #[error("Failed to fetch OHTTP config: {0}")] + OhttpConfigFetchFailed(String), + + #[error("OHTTP request error: {0}")] + OhttpRequestError(String), + + #[error("OHTTP response error: {0}")] + OhttpResponseError(String), + + #[error("OHTTP support is not enabled in this build")] + OhttpNotSupported, } impl ViaductError { diff --git a/third_party/rust/viaduct/src/headers.rs b/third_party/rust/viaduct/src/headers.rs @@ -115,6 +115,18 @@ impl Headers { Default::default() } + /// Create headers from a HashMap of name-value pairs + /// + /// # Errors + /// Returns an error if any header name or value is invalid + pub fn try_from_hashmap(map: HashMap<String, String>) -> Result<Self, crate::ViaductError> { + let mut headers = Headers::new(); + for (name, value) in map { + headers.insert(name, value)?; + } + Ok(headers) + } + /// Initialize an empty list of headers backed by a vector with the provided /// capacity. pub fn with_capacity(c: usize) -> Self { @@ -391,6 +403,7 @@ pub mod consts { (ACCEPT_ENCODING, "accept-encoding"), (ACCEPT, "accept"), (AUTHORIZATION, "authorization"), + (CACHE_CONTROL, "cache-control"), (CONTENT_TYPE, "content-type"), (ETAG, "etag"), (IF_NONE_MATCH, "if-none-match"), diff --git a/third_party/rust/viaduct/src/lib.rs b/third_party/rust/viaduct/src/lib.rs @@ -13,6 +13,10 @@ mod backend; mod client; pub mod error; mod new_backend; +#[cfg(feature = "ohttp")] +pub mod ohttp; +#[cfg(feature = "ohttp")] +mod ohttp_client; pub mod settings; pub use error::*; // reexport logging helpers. @@ -22,6 +26,8 @@ pub use backend::{note_backend, set_backend, Backend as OldBackend}; pub use client::{Client, ClientSettings}; pub use headers::{consts as header_names, Header, HeaderName, Headers, InvalidHeaderName}; pub use new_backend::{init_backend, Backend}; +#[cfg(feature = "ohttp")] +pub use ohttp::{clear_ohttp_channels, configure_ohttp_channel, list_ohttp_channels, OhttpConfig}; pub use settings::{allow_android_emulator_loopback, GLOBAL_SETTINGS}; #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/third_party/rust/viaduct/src/new_backend.rs b/third_party/rust/viaduct/src/new_backend.rs @@ -45,16 +45,14 @@ pub fn get_backend() -> Result<&'static Arc<dyn Backend>> { impl old_backend::Backend for Arc<dyn Backend> { fn send(&self, request: crate::Request) -> Result<crate::Response, crate::ViaductError> { let settings = GLOBAL_SETTINGS.read(); - // Do our best to map the old settings to the new - // - // In practice this should be okay, since no component ever overwrites the default, so we - // we can assume things like the read/connect timeout are always the same. let client_settings = ClientSettings { timeout: match settings.read_timeout { Some(d) => d.as_millis() as u32, None => 0, }, redirect_limit: if settings.follow_redirects { 10 } else { 0 }, + #[cfg(feature = "ohttp")] + ohttp_channel: None, }; pollster::block_on(self.send_request(request, client_settings)) } diff --git a/third_party/rust/viaduct/src/ohttp.rs b/third_party/rust/viaduct/src/ohttp.rs @@ -0,0 +1,479 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::ohttp_client::OhttpSession; +use once_cell::sync::Lazy; +use parking_lot::RwLock; +use std::collections::HashMap; +use std::time::{Duration, SystemTime}; +use url::Url; + +use crate::{Headers, Method, Request, Response, Result, ViaductError}; + +/// Configuration for an OHTTP channel +#[derive(Debug, Clone, uniffi::Record)] +pub struct OhttpConfig { + /// The relay URL that will proxy requests + pub relay_url: String, + /// The gateway host that provides encryption keys and decrypts requests + pub gateway_host: String, +} + +/// Cached gateway configuration with expiration +#[derive(Debug, Clone)] +struct CachedGatewayConfig { + config_data: Vec<u8>, + expires_at: SystemTime, +} + +/// Global registry of OHTTP channel configurations +static OHTTP_CHANNELS: Lazy<RwLock<HashMap<String, OhttpConfig>>> = + Lazy::new(|| RwLock::new(HashMap::new())); + +/// Cache for gateway configurations with async protection +static CONFIG_CACHE: Lazy<RwLock<HashMap<String, CachedGatewayConfig>>> = + Lazy::new(|| RwLock::new(HashMap::new())); + +/// Configure an OHTTP channel with the given configuration +/// If an existing OHTTP config exists with the same name, it will be overwritten +#[uniffi::export] +pub fn configure_ohttp_channel(channel: String, config: OhttpConfig) -> Result<()> { + crate::trace!( + "Configuring OHTTP channel '{}' with relay: {}, gateway: {}", + channel, + config.relay_url, + config.gateway_host + ); + + // Validate URLs + let parsed_relay = Url::parse(&config.relay_url)?; + crate::trace!( + "Relay URL validated: scheme={}, host={:?}", + parsed_relay.scheme(), + parsed_relay.host_str() + ); + + // Validate gateway host format + if config.gateway_host.is_empty() { + return Err(crate::ViaductError::NetworkError( + "Gateway host cannot be empty".to_string(), + )); + } + crate::trace!("Gateway host validated: {}", config.gateway_host); + + OHTTP_CHANNELS.write().insert(channel.clone(), config); + crate::trace!("OHTTP channel '{}' configured successfully", channel); + Ok(()) +} + +/// Configure default OHTTP channels for common Mozilla services +/// This sets up: +/// - "relay1": For general telemetry and services through Mozilla's shared gateway +/// - "merino": For Firefox Suggest recommendations through Merino's dedicated relay/gateway +#[uniffi::export] +pub fn configure_default_ohttp_channels() -> Result<()> { + crate::trace!("Configuring default OHTTP channels"); + + // Configure relay1 for general purpose OHTTP + // Fastly relay forwards to Mozilla's shared gateway + configure_ohttp_channel( + "relay1".to_string(), + OhttpConfig { + relay_url: "https://mozilla-ohttp.fastly-edge.com/".to_string(), + gateway_host: "prod.ohttp-gateway.prod.webservices.mozgcp.net".to_string(), + }, + )?; + + // Configure merino with its dedicated relay and integrated gateway + configure_ohttp_channel( + "merino".to_string(), + OhttpConfig { + relay_url: "https://ohttp-relay-merino-prod.edgecompute.app/".to_string(), + gateway_host: "prod.merino.prod.webservices.mozgcp.net".to_string(), + }, + )?; + + crate::trace!("Default OHTTP channels configured successfully"); + Ok(()) +} + +/// Clear all OHTTP channel configurations +#[uniffi::export] +pub fn clear_ohttp_channels() { + crate::trace!("Clearing all OHTTP channel configurations"); + OHTTP_CHANNELS.write().clear(); + CONFIG_CACHE.write().clear(); +} + +/// Get the configuration for a specific OHTTP channel +pub fn get_ohttp_config(channel: &str) -> Result<OhttpConfig> { + crate::trace!("Looking up OHTTP config for channel: {}", channel); + let channels = OHTTP_CHANNELS.read(); + match channels.get(channel) { + Some(config) => { + crate::trace!( + "Found OHTTP config for channel '{}': relay={}, gateway={}", + channel, + config.relay_url, + config.gateway_host + ); + Ok(config.clone()) + } + None => { + let available_channels: Vec<_> = channels.keys().collect(); + crate::error!( + "OHTTP channel '{}' not configured. Available channels: {:?}", + channel, + available_channels + ); + Err(ViaductError::OhttpChannelNotConfigured(channel.to_string())) + } + } +} + +/// Check if an OHTTP channel is configured +pub fn is_ohttp_channel_configured(channel: &str) -> bool { + OHTTP_CHANNELS.read().contains_key(channel) +} + +/// List all configured OHTTP channels +#[uniffi::export] +pub fn list_ohttp_channels() -> Vec<String> { + OHTTP_CHANNELS.read().keys().cloned().collect() +} + +/// Fetch and cache gateway configuration (encryption keys) +pub async fn fetch_gateway_config(gateway_host: &str) -> Result<Vec<u8>> { + if let Some(cached) = read_config_from_cache(gateway_host) { + return Ok(cached); + } + + // Could be that multiple threads fetch an already existing config + // because we don't double check here. We are currently ok with that + // to keep the code simpler + let config_data = fetch_config_from_network(gateway_host).await?; + + // Update cache (last writer wins) + { + let mut cache = CONFIG_CACHE.write(); + cache.insert( + gateway_host.to_string(), + CachedGatewayConfig { + config_data: config_data.clone(), + // Set the cache expiry to 1 day + expires_at: SystemTime::now() + Duration::from_secs(60 * 60 * 24), + }, + ); + } + + Ok(config_data) +} + +/// Read from cache if valid +fn read_config_from_cache(gateway_host: &str) -> Option<Vec<u8>> { + let cache = CONFIG_CACHE.read(); + check_cache_entry(&cache, gateway_host) +} + +/// Check if cache entry exists and is valid +fn check_cache_entry( + cache: &HashMap<String, CachedGatewayConfig>, + gateway_host: &str, +) -> Option<Vec<u8>> { + cache.get(gateway_host).and_then(|cached| { + if cached.expires_at > SystemTime::now() { + crate::trace!("Using cached config for gateway: {}", gateway_host); + Some(cached.config_data.clone()) + } else { + crate::trace!("Cached config for {} has expired", gateway_host); + None + } + }) +} + +/// Fetch config from network and update cache +async fn fetch_config_from_network(gateway_host: &str) -> Result<Vec<u8>> { + let gateway_url = format!("https://{}", gateway_host); + let config_url = Url::parse(&gateway_url)?.join("ohttp-configs")?; + + let request = Request::get(config_url.clone()); + let backend = crate::new_backend::get_backend()?; + let settings = crate::ClientSettings { + timeout: 10000, + redirect_limit: 5, + #[cfg(feature = "ohttp")] + ohttp_channel: None, + }; + + let response = backend.send_request(request, settings).await?; + + if !response.is_success() { + return Err(ViaductError::OhttpConfigFetchFailed(format!( + "Failed to fetch config from {}: HTTP {}", + config_url, response.status + ))); + } + + let config_data = response.body; + if config_data.is_empty() { + return Err(ViaductError::OhttpConfigFetchFailed( + "Empty config received from gateway".to_string(), + )); + } + + crate::trace!("Successfully fetched {} bytes", config_data.len()); + Ok(config_data) +} + +/// Process an OHTTP request using the OHTTP client component +pub async fn process_ohttp_request( + request: Request, + channel: &str, + settings: crate::ClientSettings, +) -> Result<Response> { + let overall_start = std::time::Instant::now(); + crate::trace!( + "=== Starting OHTTP request processing for channel: '{}' ===", + channel + ); + crate::trace!("Target URL: {} {}", request.method, request.url); + + let config = get_ohttp_config(channel)?; + crate::trace!( + "Retrieved OHTTP config - relay: {}, gateway: {}", + config.relay_url, + config.gateway_host + ); + + // Fetch gateway config (encryption keys) + crate::trace!( + "Step 1: Fetching gateway encryption keys from: {}", + config.gateway_host + ); + let gateway_config_start = std::time::Instant::now(); + let gateway_config_data = fetch_gateway_config(&config.gateway_host).await?; + let gateway_config_duration = gateway_config_start.elapsed(); + crate::trace!( + "Gateway config fetched: {} bytes in {:?}", + gateway_config_data.len(), + gateway_config_duration + ); + + // Create OHTTP session using the gateway's encryption keys + crate::trace!("Step 2: Creating OHTTP session with gateway keys..."); + let session_start = std::time::Instant::now(); + let ohttp_session = OhttpSession::new(&gateway_config_data).map_err(|e| { + crate::error!("Failed to create OHTTP session: {}", e); + ViaductError::OhttpRequestError(format!("Failed to create OHTTP session: {}", e)) + })?; + let session_duration = session_start.elapsed(); + crate::trace!( + "OHTTP session created successfully in {:?}", + session_duration + ); + + // Prepare request components - these come from the actual request URL (target) + let method = request.method.as_str(); + let scheme = request.url.scheme(); + let authority = request.url.host_str().unwrap_or(""); + let path_and_query = { + let mut path = request.url.path().to_string(); + if let Some(query) = request.url.query() { + path.push('?'); + path.push_str(query); + } + path + }; + let headers_map: HashMap<String, String> = request.headers.clone().into(); + let payload = request.body.unwrap_or_default(); + + crate::trace!( + "Step 3: Preparing request - {} {}://{}{}", + method, + scheme, + authority, + path_and_query + ); + crate::trace!("Request headers: {} total", headers_map.len()); + crate::trace!("Request payload: {} bytes", payload.len()); + + // Encapsulate the request using the OHTTP session + crate::trace!("Step 4: Encapsulating request with OHTTP..."); + let encap_start = std::time::Instant::now(); + let encrypted_request = ohttp_session + .encapsulate( + method, + scheme, + authority, + &path_and_query, + headers_map, + &payload, + ) + .map_err(|e| { + crate::error!("Failed to encapsulate request: {}", e); + ViaductError::OhttpRequestError(format!("Failed to encapsulate request: {}", e)) + })?; + let encap_duration = encap_start.elapsed(); + crate::trace!( + "Request encapsulated: {} bytes → {} bytes encrypted in {:?}", + payload.len(), + encrypted_request.len(), + encap_duration + ); + + // Create HTTP request to send to the relay + let relay_url = Url::parse(&config.relay_url)?; + crate::trace!("Step 5: Sending encrypted request to relay: {}", relay_url); + + let mut relay_headers = Headers::new(); + relay_headers.insert("Content-Type", "message/ohttp-req")?; + + let relay_request = Request { + method: Method::Post, + url: relay_url.clone(), + headers: relay_headers, + body: Some(encrypted_request), + }; + + // Send the encrypted request to the relay using the backend + crate::trace!("Sending to relay with timeout: {}ms", settings.timeout); + let relay_start = std::time::Instant::now(); + let backend = crate::new_backend::get_backend()?; + let relay_response = backend.send_request(relay_request, settings).await?; + let relay_duration = relay_start.elapsed(); + + crate::trace!( + "Relay responded: HTTP {} in {:?}", + relay_response.status, + relay_duration + ); + + // Check if the relay responded successfully + if !relay_response.is_success() { + crate::error!( + "OHTTP relay {} returned error: HTTP {} - {}", + relay_url, + relay_response.status, + String::from_utf8_lossy(&relay_response.body) + ); + return Err(ViaductError::OhttpRequestError(format!( + "OHTTP relay returned error: HTTP {} - {}", + relay_response.status, + String::from_utf8_lossy(&relay_response.body) + ))); + } + + // Verify the response content type + if let Some(content_type) = relay_response.headers.get("content-type") { + if content_type != "message/ohttp-res" { + crate::warn!( + "OHTTP relay returned unexpected content-type: {} (expected: message/ohttp-res)", + content_type + ); + } else { + crate::trace!("Relay response content-type verified: {}", content_type); + } + } else { + crate::warn!("OHTTP relay response missing content-type header"); + } + + // Decapsulate the encrypted response using the OHTTP session + crate::trace!( + "Step 6: Decapsulating response ({} bytes from relay)...", + relay_response.body.len() + ); + let decap_start = std::time::Instant::now(); + let ohttp_response = ohttp_session + .decapsulate(&relay_response.body) + .map_err(|e| { + crate::error!("Failed to decapsulate OHTTP response: {}", e); + ViaductError::OhttpResponseError(format!("Failed to decapsulate OHTTP response: {}", e)) + })?; + let decap_duration = decap_start.elapsed(); + + // Convert the OHTTP response back to a viaduct Response + let (status, headers_map, body) = ohttp_response.into_parts(); + let final_headers = Headers::try_from_hashmap(headers_map)?; + + let final_response = Response { + request_method: request.method, + url: request.url, + status, + headers: final_headers, + body, + }; + + let overall_duration = overall_start.elapsed(); + crate::trace!( + "=== OHTTP request completed successfully for channel '{}' ===", + channel + ); + crate::trace!( + "Final result: HTTP {} with {} bytes (total time: {:?})", + final_response.status, + final_response.body.len(), + overall_duration + ); + crate::trace!( + "Timing breakdown - Config: {:?}, Session: {:?}, Encap: {:?}, Relay: {:?}, Decap: {:?}", + gateway_config_duration, + session_duration, + encap_duration, + relay_duration, + decap_duration + ); + + Ok(final_response) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_channel_configuration() { + clear_ohttp_channels(); + + let config = OhttpConfig { + relay_url: "https://relay.example.com".to_string(), + gateway_host: "gateway.example.com".to_string(), + }; + + configure_ohttp_channel("test".to_string(), config.clone()).unwrap(); + + assert!(is_ohttp_channel_configured("test")); + assert!(!is_ohttp_channel_configured("nonexistent")); + + let retrieved = get_ohttp_config("test").unwrap(); + assert_eq!(retrieved.relay_url, config.relay_url); + assert_eq!(retrieved.gateway_host, config.gateway_host); + + let channels = list_ohttp_channels(); + assert_eq!(channels, vec!["test"]); + + clear_ohttp_channels(); + assert!(!is_ohttp_channel_configured("test")); + } + + #[test] + fn test_headers_conversion() { + let mut headers = Headers::new(); + headers.insert("Content-Type", "application/json").unwrap(); + headers.insert("Authorization", "Bearer token").unwrap(); + + let map: HashMap<String, String> = headers.clone().into(); + + assert_eq!(map.len(), 2); + assert_eq!(map.get("content-type").unwrap(), "application/json"); + assert_eq!(map.get("authorization").unwrap(), "Bearer token"); + + let headers_back = Headers::try_from_hashmap(map).unwrap(); + + assert_eq!( + headers_back.get("Content-Type").unwrap(), + "application/json" + ); + assert_eq!(headers_back.get("Authorization").unwrap(), "Bearer token"); + } +} diff --git a/third_party/rust/viaduct/src/ohttp_client.rs b/third_party/rust/viaduct/src/ohttp_client.rs @@ -0,0 +1,147 @@ +use crate::ViaductError; +use parking_lot::Mutex; +use std::collections::HashMap; + +#[derive(Debug, thiserror::Error)] +pub enum OhttpError { + #[error("OHTTP key config is malformed")] + MalformedKeyConfig, + #[error("Unsupported OHTTP encryption algorithm")] + UnsupportedKeyConfig, + #[error("OhttpSession is in invalid state")] + InvalidSession, + #[error("Cannot encode message as BHTTP/OHTTP")] + CannotEncodeMessage, + #[error("Cannot decode OHTTP/BHTTP message")] + MalformedMessage, + #[error("Duplicate HTTP response headers")] + DuplicateHeaders, +} + +impl From<OhttpError> for ViaductError { + fn from(e: OhttpError) -> Self { + ViaductError::OhttpRequestError(e.to_string()) + } +} + +#[derive(Default)] +enum ExchangeState { + #[default] + Invalid, + Request(ohttp::ClientRequest), + Response(ohttp::ClientResponse), +} + +pub struct OhttpSession { + state: Mutex<ExchangeState>, +} + +pub struct OhttpResponse { + status_code: u16, + headers: HashMap<String, String>, + payload: Vec<u8>, +} + +impl OhttpResponse { + pub fn into_parts(self) -> (u16, HashMap<String, String>, Vec<u8>) { + (self.status_code, self.headers, self.payload) + } +} + +fn headers_to_map(message: &bhttp::Message) -> Result<HashMap<String, String>, OhttpError> { + let mut headers = HashMap::new(); + + for field in message.header().iter() { + if headers + .insert( + std::str::from_utf8(field.name()) + .map_err(|_| OhttpError::MalformedMessage)? + .into(), + std::str::from_utf8(field.value()) + .map_err(|_| OhttpError::MalformedMessage)? + .into(), + ) + .is_some() + { + return Err(OhttpError::DuplicateHeaders); + } + } + + Ok(headers) +} + +impl OhttpSession { + pub fn new(config: &[u8]) -> Result<Self, OhttpError> { + ohttp::init(); + + let request = ohttp::ClientRequest::from_encoded_config(config).map_err(|e| match e { + ohttp::Error::Unsupported => OhttpError::UnsupportedKeyConfig, + _ => OhttpError::MalformedKeyConfig, + })?; + + let state = Mutex::new(ExchangeState::Request(request)); + Ok(OhttpSession { state }) + } + + pub fn encapsulate( + &self, + method: &str, + scheme: &str, + server: &str, + endpoint: &str, + mut headers: HashMap<String, String>, + payload: &[u8], + ) -> Result<Vec<u8>, OhttpError> { + let mut message = + bhttp::Message::request(method.into(), scheme.into(), server.into(), endpoint.into()); + + for (k, v) in headers.drain() { + message.put_header(k, v); + } + + message.write_content(payload); + + let mut encoded = vec![]; + message + .write_bhttp(bhttp::Mode::KnownLength, &mut encoded) + .map_err(|_| OhttpError::CannotEncodeMessage)?; + + let mut state = self.state.lock(); + let request = match std::mem::take(&mut *state) { + ExchangeState::Request(request) => request, + _ => return Err(OhttpError::InvalidSession), + }; + let (capsule, response) = request + .encapsulate(&encoded) + .map_err(|_| OhttpError::CannotEncodeMessage)?; + *state = ExchangeState::Response(response); + + Ok(capsule) + } + + pub fn decapsulate(&self, encoded: &[u8]) -> Result<OhttpResponse, OhttpError> { + let mut state = self.state.lock(); + let decoder = match std::mem::take(&mut *state) { + ExchangeState::Response(response) => response, + _ => return Err(OhttpError::InvalidSession), + }; + let binary = decoder + .decapsulate(encoded) + .map_err(|_| OhttpError::MalformedMessage)?; + + let mut cursor = std::io::Cursor::new(binary); + let message = + bhttp::Message::read_bhttp(&mut cursor).map_err(|_| OhttpError::MalformedMessage)?; + + let headers = headers_to_map(&message)?; + + Ok(OhttpResponse { + status_code: match message.control() { + bhttp::ControlData::Response(sc) => (*sc).into(), + _ => return Err(OhttpError::InvalidSession), + }, + headers, + payload: message.content().into(), + }) + } +} diff --git a/toolkit/components/glean/src/init/viaduct_uploader.rs b/toolkit/components/glean/src/init/viaduct_uploader.rs @@ -68,14 +68,19 @@ impl PingUploader for ViaductUploader { match result { Ok(result) => result, Err(ViaductUploaderError::Viaduct(ve)) => match ve { - NonTlsUrl | UrlError(_) | BackendAlreadyInitialized => { - UploadResult::unrecoverable_failure() - } + NonTlsUrl + | UrlError(_) + | BackendAlreadyInitialized + | OhttpNotSupported + | OhttpChannelNotConfigured(_) => UploadResult::unrecoverable_failure(), RequestHeaderError(_) | BackendError(_) | NetworkError(_) | BackendNotInitialized - | SetBackendError => UploadResult::recoverable_failure(), + | SetBackendError + | OhttpConfigFetchFailed(_) + | OhttpRequestError(_) + | OhttpResponseError(_) => UploadResult::recoverable_failure(), }, Err( ViaductUploaderError::Bhttp(_) diff --git a/toolkit/components/uniffi-bindgen-gecko-js/components/generated/RustViaduct.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/components/generated/RustViaduct.sys.mjs @@ -889,6 +889,93 @@ export class NonTlsUrl extends ViaductError { } } +/** + * OhttpChannelNotConfigured + */ +export class OhttpChannelNotConfigured extends ViaductError { + + constructor( + v0, + ...params + ) { + const message = `v0: ${ v0 }`; + super(message, ...params); + this.v0 = v0; + } + toString() { + return `OhttpChannelNotConfigured: ${super.toString()}` + } +} + +/** + * OhttpConfigFetchFailed + */ +export class OhttpConfigFetchFailed extends ViaductError { + + constructor( + v0, + ...params + ) { + const message = `v0: ${ v0 }`; + super(message, ...params); + this.v0 = v0; + } + toString() { + return `OhttpConfigFetchFailed: ${super.toString()}` + } +} + +/** + * OhttpRequestError + */ +export class OhttpRequestError extends ViaductError { + + constructor( + v0, + ...params + ) { + const message = `v0: ${ v0 }`; + super(message, ...params); + this.v0 = v0; + } + toString() { + return `OhttpRequestError: ${super.toString()}` + } +} + +/** + * OhttpResponseError + */ +export class OhttpResponseError extends ViaductError { + + constructor( + v0, + ...params + ) { + const message = `v0: ${ v0 }`; + super(message, ...params); + this.v0 = v0; + } + toString() { + return `OhttpResponseError: ${super.toString()}` + } +} + +/** + * OhttpNotSupported + */ +export class OhttpNotSupported extends ViaductError { + + constructor( + ...params + ) { + super(...params); + } + toString() { + return `OhttpNotSupported: ${super.toString()}` + } +} + // Export the FFIConverter object to make external types work. export class FfiConverterTypeViaductError extends FfiConverterArrayBuffer { static read(dataStream) { @@ -921,6 +1008,25 @@ export class FfiConverterTypeViaductError extends FfiConverterArrayBuffer { case 8: return new NonTlsUrl( ); + case 9: + return new OhttpChannelNotConfigured( + FfiConverterString.read(dataStream) + ); + case 10: + return new OhttpConfigFetchFailed( + FfiConverterString.read(dataStream) + ); + case 11: + return new OhttpRequestError( + FfiConverterString.read(dataStream) + ); + case 12: + return new OhttpResponseError( + FfiConverterString.read(dataStream) + ); + case 13: + return new OhttpNotSupported( + ); default: throw new UniFFITypeError("Unknown ViaductError variant"); } @@ -956,6 +1062,25 @@ export class FfiConverterTypeViaductError extends FfiConverterArrayBuffer { if (value instanceof NonTlsUrl) { return totalSize; } + if (value instanceof OhttpChannelNotConfigured) { + totalSize += FfiConverterString.computeSize(value.v0); + return totalSize; + } + if (value instanceof OhttpConfigFetchFailed) { + totalSize += FfiConverterString.computeSize(value.v0); + return totalSize; + } + if (value instanceof OhttpRequestError) { + totalSize += FfiConverterString.computeSize(value.v0); + return totalSize; + } + if (value instanceof OhttpResponseError) { + totalSize += FfiConverterString.computeSize(value.v0); + return totalSize; + } + if (value instanceof OhttpNotSupported) { + return totalSize; + } throw new UniFFITypeError("Unknown ViaductError variant"); } static write(dataStream, value) { @@ -995,6 +1120,30 @@ export class FfiConverterTypeViaductError extends FfiConverterArrayBuffer { dataStream.writeInt32(8); return; } + if (value instanceof OhttpChannelNotConfigured) { + dataStream.writeInt32(9); + FfiConverterString.write(dataStream, value.v0); + return; + } + if (value instanceof OhttpConfigFetchFailed) { + dataStream.writeInt32(10); + FfiConverterString.write(dataStream, value.v0); + return; + } + if (value instanceof OhttpRequestError) { + dataStream.writeInt32(11); + FfiConverterString.write(dataStream, value.v0); + return; + } + if (value instanceof OhttpResponseError) { + dataStream.writeInt32(12); + FfiConverterString.write(dataStream, value.v0); + return; + } + if (value instanceof OhttpNotSupported) { + dataStream.writeInt32(13); + return; + } throw new UniFFITypeError("Unknown ViaductError variant"); }