From 3edee8ffd645e809f81b37e0a4f7fff3546ba353 Mon Sep 17 00:00:00 2001 From: Jer Miller Date: Fri, 2 Oct 2026 03:19:13 -0600 Subject: [PATCH] Add the browser host, and upload browser pages as their own source The solstone browser extension reaches the app over native messaging. The browser starts this binary in host mode, recognised from argv before any other startup work; it relays whole frames to the running app's same-user unix socket and holds no journal credential. The app keeps what it accepts in five-minute periods, answers a replayed batch as a duplicate, refuses new pages by name once its one bound is reached, and retires everything held for a journal it is no longer paired with. Finished periods are delivered as the `browser` source, and the journal mark gate holds them like any other upload. The whole path is compiled and tested in every build but is reachable only with the `browser` feature, which no owner build sets yet: without it there is no host mode, no endpoint and no browser registration. Capture segments under any stream folder other than the browser one are still the device's primary capture; browser periods are no longer sent as primary capture. The wire contract and frame crate are vendored unchanged from solstone-browser 16ad2636. --- AGENTS.md | 21 + Cargo.lock | 131 +- Cargo.toml | 2 + RUST_DEPENDENCY_NOTICES.txt | 854 ++++- crates/solstone-linux/Cargo.toml | 11 +- crates/solstone-linux/src/browser.rs | 184 + crates/solstone-linux/src/browser/argv.rs | 208 ++ crates/solstone-linux/src/browser/custody.rs | 1324 ++++++++ crates/solstone-linux/src/browser/host.rs | 297 ++ .../src/browser/registration.rs | 277 ++ crates/solstone-linux/src/browser/server.rs | 746 ++++ crates/solstone-linux/src/browser/status.rs | 246 ++ crates/solstone-linux/src/cli.rs | 103 +- crates/solstone-linux/src/doctor.rs | 30 + crates/solstone-linux/src/lib.rs | 1 + crates/solstone-linux/src/private_link.rs | 17 +- crates/solstone-linux/src/run.rs | 59 + crates/solstone-linux/src/service.rs | 20 + crates/solstone-linux/src/sync.rs | 215 +- crates/solstone-linux/src/upload.rs | 36 +- .../overrides/jsonschema-regex/0.58.4/LICENSE | 21 + .../overrides/jsonschema-value/0.58.4/LICENSE | 21 + licensing/overrides/uuid-simd/0.8.0/LICENSE | 21 + licensing/overrides/vsimd/0.8.0/LICENSE | 21 + licensing/rust-dependency-notices.index.json | 3003 ++++++++++------- vendor/contracts/native-browser/adoption.json | 10 + .../native-browser/adoption.schema.json | 45 + .../contracts/native-browser/authority.json | 328 ++ .../native-browser/browser.schema.json | 305 ++ vendor/contracts/native-browser/constants.js | 331 ++ vendor/contracts/native-browser/corpus.json | 1164 +++++++ .../native-browser/envelope.schema.json | 699 ++++ vendor/contracts/native-browser/manifest.json | 45 + vendor/contracts/native-browser/recipes.json | 41 + .../registration/dev/chrome/linux.json | 9 + .../registration/dev/chrome/macos.json | 9 + .../registration/dev/chrome/windows.json | 9 + .../registration/dev/edge/linux.json | 9 + .../registration/dev/edge/macos.json | 9 + .../registration/dev/edge/windows.json | 9 + .../registration/dev/firefox/linux.json | 9 + .../registration/dev/firefox/macos.json | 9 + .../registration/dev/firefox/windows.json | 9 + .../registration/production/chrome/linux.json | 9 + .../registration/production/chrome/macos.json | 9 + .../production/chrome/windows.json | 9 + .../registration/production/edge/linux.json | 9 + .../registration/production/edge/macos.json | 9 + .../registration/production/edge/windows.json | 9 + .../production/firefox/linux.json | 9 + .../production/firefox/macos.json | 9 + .../production/firefox/windows.json | 9 + vendor/contracts/native-browser/schemas.js | 1009 ++++++ vendor/contracts/native-browser/swift.json | 1490 ++++++++ vendor/crates/native-browser-frame/Cargo.toml | 11 + .../crates/native-browser-frame/src/codec.rs | 692 ++++ .../native-browser-frame/src/constants.rs | 74 + .../crates/native-browser-frame/src/frame.rs | 208 ++ vendor/crates/native-browser-frame/src/lib.rs | 15 + .../native-browser-frame/src/predicate.rs | 79 + .../native-browser-frame/src/register.rs | 110 + 61 files changed, 13415 insertions(+), 1272 deletions(-) create mode 100644 crates/solstone-linux/src/browser.rs create mode 100644 crates/solstone-linux/src/browser/argv.rs create mode 100644 crates/solstone-linux/src/browser/custody.rs create mode 100644 crates/solstone-linux/src/browser/host.rs create mode 100644 crates/solstone-linux/src/browser/registration.rs create mode 100644 crates/solstone-linux/src/browser/server.rs create mode 100644 crates/solstone-linux/src/browser/status.rs create mode 100644 licensing/overrides/jsonschema-regex/0.58.4/LICENSE create mode 100644 licensing/overrides/jsonschema-value/0.58.4/LICENSE create mode 100644 licensing/overrides/uuid-simd/0.8.0/LICENSE create mode 100644 licensing/overrides/vsimd/0.8.0/LICENSE create mode 100644 vendor/contracts/native-browser/adoption.json create mode 100644 vendor/contracts/native-browser/adoption.schema.json create mode 100644 vendor/contracts/native-browser/authority.json create mode 100644 vendor/contracts/native-browser/browser.schema.json create mode 100644 vendor/contracts/native-browser/constants.js create mode 100644 vendor/contracts/native-browser/corpus.json create mode 100644 vendor/contracts/native-browser/envelope.schema.json create mode 100644 vendor/contracts/native-browser/manifest.json create mode 100644 vendor/contracts/native-browser/recipes.json create mode 100644 vendor/contracts/native-browser/registration/dev/chrome/linux.json create mode 100644 vendor/contracts/native-browser/registration/dev/chrome/macos.json create mode 100644 vendor/contracts/native-browser/registration/dev/chrome/windows.json create mode 100644 vendor/contracts/native-browser/registration/dev/edge/linux.json create mode 100644 vendor/contracts/native-browser/registration/dev/edge/macos.json create mode 100644 vendor/contracts/native-browser/registration/dev/edge/windows.json create mode 100644 vendor/contracts/native-browser/registration/dev/firefox/linux.json create mode 100644 vendor/contracts/native-browser/registration/dev/firefox/macos.json create mode 100644 vendor/contracts/native-browser/registration/dev/firefox/windows.json create mode 100644 vendor/contracts/native-browser/registration/production/chrome/linux.json create mode 100644 vendor/contracts/native-browser/registration/production/chrome/macos.json create mode 100644 vendor/contracts/native-browser/registration/production/chrome/windows.json create mode 100644 vendor/contracts/native-browser/registration/production/edge/linux.json create mode 100644 vendor/contracts/native-browser/registration/production/edge/macos.json create mode 100644 vendor/contracts/native-browser/registration/production/edge/windows.json create mode 100644 vendor/contracts/native-browser/registration/production/firefox/linux.json create mode 100644 vendor/contracts/native-browser/registration/production/firefox/macos.json create mode 100644 vendor/contracts/native-browser/registration/production/firefox/windows.json create mode 100644 vendor/contracts/native-browser/schemas.js create mode 100644 vendor/contracts/native-browser/swift.json create mode 100644 vendor/crates/native-browser-frame/Cargo.toml create mode 100644 vendor/crates/native-browser-frame/src/codec.rs create mode 100644 vendor/crates/native-browser-frame/src/constants.rs create mode 100644 vendor/crates/native-browser-frame/src/frame.rs create mode 100644 vendor/crates/native-browser-frame/src/lib.rs create mode 100644 vendor/crates/native-browser-frame/src/predicate.rs create mode 100644 vendor/crates/native-browser-frame/src/register.rs diff --git a/AGENTS.md b/AGENTS.md index 3939dfc..032585a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -125,6 +125,27 @@ Shipping system packages: - Captures: `~/.local/share/solstone-linux/captures/` - State: `~/.local/share/solstone-linux/state/` - Restore token: `~/.config/solstone-linux/restore_token` +- Browser custody: `~/.local/share/solstone-linux/browser/` (open periods, the + destination generation, and pages retired for a previous journal); finished + browser periods are capture segments under the `_browser` stream folder +- Browser endpoint: `$XDG_RUNTIME_DIR/solstone-linux/browser-host.sock` + +## Browser host + +The solstone browser extension reaches the app over native messaging. The +browser starts this same binary in host mode, recognised from argv at the top of +`cli::run` before any other startup work; host mode relays whole frames to the +running app's same-user unix socket and holds no journal credential. The app +keeps accepted text in `src/browser/custody.rs` and the sync service delivers +finished periods as the `browser` source. The wire contract and the frame crate +are vendored unchanged under `vendor/` from `solstone-browser`; the pin is +`vendor/contracts/native-browser/adoption.json`, and a test checks every vendored +artifact against the bundle manifest. + +The path is compiled and tested in every build but reachable only with the +`browser` feature: without it there is no host mode, no endpoint and no browser +registration. `browser-dev-host` also admits the unpacked development extension; +no release target sets it. ## Testing diff --git a/Cargo.lock b/Cargo.lock index 456d57a..3d7d696 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,6 +910,17 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fancy-regex" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301f5bf187b3c295fce6468d3875037a0bccc5f6b151c63cac2f85babf21912" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -1012,6 +1023,16 @@ dependencies = [ "num", ] +[[package]] +name = "fraction" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e246562084dde8ebbcc943b261c406ce4f68e5032ec28029a251a47d6a295500" +dependencies = [ + "num", + "num-bigint", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -1672,19 +1693,47 @@ dependencies = [ "bytecount", "data-encoding", "email_address", - "fancy-regex", - "fraction", + "fancy-regex 0.18.0", + "fraction 0.15.4", "getrandom 0.3.4", "idna", "itoa", - "jsonschema-regex", + "jsonschema-regex 0.48.2", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing 0.48.2", + "regex", + "serde", + "serde_json", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonschema" +version = "0.58.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5761b0ca0a31464030d7c2a98087fde3124ebb5e09b5a112cdb815f03b8ee150" +dependencies = [ + "ahash", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex 0.19.2", + "fraction 0.17.0", + "getrandom 0.3.4", + "itoa", + "jsonschema-regex 0.58.4", + "jsonschema-value", "num-cmp", "num-traits", "percent-encoding", - "referencing", + "referencing 0.58.4", "regex", "serde", "serde_json", + "strum", "unicode-general-category", "uuid-simd", ] @@ -1698,6 +1747,31 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "jsonschema-regex" +version = "0.58.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ffba4378a3d6ee7f1ab65a8ed95c711ccfe4a3870932bdce89d8260abed38a" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "jsonschema-value" +version = "0.58.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "386121f898b560ff6aaa7974952720e9d9276a201eca31296387c2ed8707855e" +dependencies = [ + "ahash", + "bytecount", + "fraction 0.17.0", + "getrandom 0.3.4", + "num-cmp", + "num-traits", + "serde_json", + "zmij", +] + [[package]] name = "keccak" version = "0.1.6" @@ -1918,6 +1992,14 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" +[[package]] +name = "native-browser-frame" +version = "1.0.0" +dependencies = [ + "jsonschema 0.58.4", + "serde_json", +] + [[package]] name = "nom" version = "7.1.3" @@ -2505,6 +2587,23 @@ dependencies = [ "serde_json", ] +[[package]] +name = "referencing" +version = "0.58.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cfa1c91745dbca76a406b926918d7c63e15a4525ebab939fec3e015a1fb82b1" +dependencies = [ + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", +] + [[package]] name = "regex" version = "1.13.1" @@ -2654,7 +2753,7 @@ dependencies = [ "chrono", "clap", "flate2", - "jsonschema", + "jsonschema 0.48.1", "rpm", "serde", "serde_json", @@ -2966,6 +3065,7 @@ dependencies = [ "jiff", "ksni", "libpulse-binding", + "native-browser-frame", "notify-rust", "open", "proc-macro2", @@ -3084,6 +3184,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 5148954..4f19c2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,8 @@ members = [ "crates/rust-release-manifest", "crates/solstone-linux", ] +# Vendored from solstone-browser with the browser contract; see vendor/contracts/native-browser/adoption.json. +exclude = ["vendor/crates/native-browser-frame"] resolver = "3" [workspace.package] diff --git a/RUST_DEPENDENCY_NOTICES.txt b/RUST_DEPENDENCY_NOTICES.txt index 54ff81c..9b2e57f 100644 --- a/RUST_DEPENDENCY_NOTICES.txt +++ b/RUST_DEPENDENCY_NOTICES.txt @@ -276,6 +276,25 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +MIT No Attribution + +Copyright 2024 Scallop Ye + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + This project is dual-licensed under the Unlicense and MIT licenses. You may use this code under the terms of either license. @@ -508,6 +527,32 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2018 Tom Kaitchuck + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + Copyright (c) 2010 The Rust Project Developers Permission is hereby granted, free of charge, to any @@ -1695,6 +1740,28 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +MIT License + +Copyright (c) 2020-2026 Dmitry Dygalo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + Copyright (c) The Rust Project Developers Permission is hereby granted, free of charge, to any @@ -4069,63 +4136,499 @@ See the License for the specific language governing permissions and limitations under the License. -Short version for non-lawyers: - -`rustix` is triple-licensed under Apache 2.0 with the LLVM Exception, -Apache 2.0, and MIT terms. - - -Longer version: - -Copyrights in the `rustix` project are retained by their contributors. -No copyright assignment is required to contribute to the `rustix` -project. - -Some files include code derived from Rust's `libstd`; see the comments in -the code for details. - -Except as otherwise noted (below and/or in individual files), `rustix` -is licensed under: - - - the Apache License, Version 2.0, with the LLVM Exception - or - - - the Apache License, Version 2.0 - or - , - - or the MIT license - or - , - -at your option. - -Copyright (c) 2014 Alex Crichton - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +Short version for non-lawyers: + +`rustix` is triple-licensed under Apache 2.0 with the LLVM Exception, +Apache 2.0, and MIT terms. + + +Longer version: + +Copyrights in the `rustix` project are retained by their contributors. +No copyright assignment is required to contribute to the `rustix` +project. + +Some files include code derived from Rust's `libstd`; see the comments in +the code for details. + +Except as otherwise noted (below and/or in individual files), `rustix` +is licensed under: + + - the Apache License, Version 2.0, with the LLVM Exception + or + + - the Apache License, Version 2.0 + or + , + - or the MIT license + or + , + +at your option. + +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +Copyright (c) 2015-2025 Sean McArthur + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +MIT License + +Copyright (c) 2019 Simon Johnston + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +The author of num-cmp, Kang Seonghoon, disclaims copyright to +this source code. In place of a legal notice, here is a blessing: + + May you do good and not evil. + May you find forgiveness for yourself and forgive others. + May you share freely, never taking more than you give. + +Alternatively, for jurisdictions where authors cannot disclaim +their copyright, this source code is distributed under the terms of +CC0 1.0 Universal license [1] as published by Creative Commons. + +In order to allow the maximal compatibility with Rust projects, +this source code is also dual-licensed under the MIT License [2] and +Apache 2.0 License [3]. Copyright (c) 2017, Kang Seonghoon. + +This dual-licensing is disjunctive with the public domain dedication +and shall not affect the casual use of this source code. This also +means that not every contribution to the MIT/Apache-licensed version +of this source code cannot be ported back to the public domain +version. The author encourages (but not requires, naturally) that +any contribution to this source code is also pushed back separately +to the public domain repository. + +This legal notice and blessing is shamelessly adopted from +the SQLite library. + +Nota Bene: [2] and [3] are same as the Rust Project's own license. + + +[1]: , which is + reproduced below: + +~~~~ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. +~~~~ + + +[2]: , which is reproduced below: + +~~~~ +The MIT License (MIT) + +Copyright (c) 2017, Kang Seonghoon. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +~~~~ + + +[3]: , which is reproduced below: + +~~~~ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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. +~~~~ + + +The MIT License -Copyright (c) 2015-2025 Sean McArthur +Copyright 2015 The Fancy Regex Authors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -4145,7 +4648,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - Copyright Mozilla Foundation Permission is hereby granted, free of charge, to any @@ -7066,6 +7568,28 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +MIT License + +Copyright (c) 2021 Nugine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + Copyright (c) 2016--2023 Permission is hereby granted, free of charge, to any @@ -7129,6 +7653,64 @@ submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +Unicode Data Files include all data files under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +Unicode Data Files do not include PDF online code charts under the +directory http://www.unicode.org/Public/. + +Software includes any source code published in the Unicode Standard +or under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S +DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), +YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE +THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2018 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -9292,6 +9874,28 @@ 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. +MIT License + +Copyright (c) 2019 Peter Glotfelty + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + Copyright (c) 2018-2026 The RustCrypto Project Developers Permission is hereby granted, free of charge, to any @@ -9470,6 +10074,26 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2023-2026 Yegor Bugayenko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + Rust-chrono is dual-licensed under The MIT License [1] and Apache 2.0 License [2]. Copyright (c) 2014--2026, Kang Seonghoon and contributors. @@ -10023,6 +10647,50 @@ 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. +MIT License + +Copyright (c) 2024 Scallop Ye + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +MIT License + +Copyright (c) 2022 Nugine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + Copyright (c) 2017 Pierre Chifflier Permission is hereby granted, free of charge, to any @@ -10049,6 +10717,26 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2017 The bytecount Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -14030,6 +14718,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2023 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + Copyright 2017 the Resvg Authors Permission is hereby granted, free of charge, to any @@ -14164,6 +14878,32 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2016-2019 Ulrik Sverdrup "bluss" and scopeguard developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + Copyright (c) 2017 Daniel Abramov Copyright (c) 2017 Alexey Galakhov diff --git a/crates/solstone-linux/Cargo.toml b/crates/solstone-linux/Cargo.toml index 79676e7..b004433 100644 --- a/crates/solstone-linux/Cargo.toml +++ b/crates/solstone-linux/Cargo.toml @@ -27,7 +27,7 @@ rustix = { version = "1", features = ["event", "fs", "process", "time"] } reqwest = { version = "0.12", default-features = false, features = ["cookies", "json", "multipart", "stream"] } spl-core.workspace = true spl-transport.workspace = true -tokio = { workspace = true, features = ["signal", "sync"] } +tokio = { workspace = true, features = ["io-util", "net", "signal", "sync"] } tokio-util = { version = "0.7", features = ["io", "rt"] } ashpd = { version = "=0.13.12", features = ["screencast"] } futures-util = "0.3" @@ -40,6 +40,15 @@ open = "5.4.0" notify-rust = { version = "=4.18.0", default-features = false, features = ["z-with-tokio"] } zbus = { version = "=5.17.0", features = ["tokio"] } sd-notify = "0.5.0" +native-browser-frame = { version = "1.0.0", path = "../../vendor/crates/native-browser-frame" } + +[features] +# The browser host stays off in owner builds until it is launched. `browser` +# makes host mode reachable and writes the browser registrations; +# `browser-dev-host` also admits the unpacked development extension, and no +# release target sets it. +browser = [] +browser-dev-host = ["browser"] [build-dependencies] resvg = { version = "0.47", default-features = false } diff --git a/crates/solstone-linux/src/browser.rs b/crates/solstone-linux/src/browser.rs new file mode 100644 index 0000000..1e5402b --- /dev/null +++ b/crates/solstone-linux/src/browser.rs @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +//! The browser path: the extension talks to this binary in host mode over native +//! messaging, host mode relays to the running app over a same-user unix socket, and +//! the app keeps what it accepts on this computer until it goes into the journal as +//! the `browser` source. +//! +//! The whole path is compiled in every build so the routine gate exercises it, but it +//! is reachable only in builds with the `browser` feature: without it there is no host +//! mode, no endpoint and no browser registration. + +pub mod argv; +pub mod custody; +pub mod host; +pub mod registration; +pub mod server; +pub mod status; + +use std::{ + env, + ffi::OsString, + io, + path::{Path, PathBuf}, +}; + +/// Whether this build carries the browser path. +pub const ENABLED: bool = cfg!(feature = "browser"); +/// Whether this build also admits the unpacked development extension. No release +/// target sets it. +pub const DEV_ENABLED: bool = cfg!(feature = "browser-dev-host"); + +/// The local stream folder that holds finalized browser periods. A stream name must +/// start with a lowercase letter or digit, so this can never collide with one. +pub const STREAM_DIR: &str = "_browser"; +/// The journal-side source name for browser text. +pub const SOURCE: &str = "browser"; +/// The one payload file of every browser period. +pub const PAGES_FILENAME: &str = "browser_pages.jsonl"; + +const ENDPOINT_DIR: &str = "solstone-linux"; +const ENDPOINT_NAME: &str = "browser-host.sock"; + +/// The app's endpoint: `$XDG_RUNTIME_DIR/solstone-linux/browser-host.sock`, falling +/// back to the systemd per-user runtime directory when the variable is unset. +pub fn endpoint_path(runtime_dir: Option, uid: u32) -> io::Result { + let root = runtime_dir + .filter(|value| !value.is_empty()) + .map(PathBuf::from) + .filter(|path| path.is_absolute()) + .unwrap_or_else(|| PathBuf::from(format!("/run/user/{uid}"))); + let path = root.join(ENDPOINT_DIR).join(ENDPOINT_NAME); + // sun_path holds 108 bytes including the terminator. + if path.as_os_str().len() > 107 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "browser endpoint path is too long", + )); + } + Ok(path) +} + +pub fn production_endpoint_path() -> io::Result { + endpoint_path( + env::var_os("XDG_RUNTIME_DIR"), + rustix::process::getuid().as_raw(), + ) +} + +/// Where browser custody lives that is not yet a finalized segment. +pub fn custody_root(base_dir: &Path) -> PathBuf { + base_dir.join("browser") +} + +/// Whether a capture segment directory is a browser period. +pub fn is_browser_segment(segment_dir: &Path) -> bool { + segment_dir + .parent() + .and_then(Path::file_name) + .is_some_and(|name| name == STREAM_DIR) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn endpoint_uses_runtime_dir_and_falls_back_to_the_user_runtime() { + assert_eq!( + endpoint_path(Some("/run/user/1000".into()), 1000).unwrap(), + PathBuf::from("/run/user/1000/solstone-linux/browser-host.sock") + ); + assert_eq!( + endpoint_path(None, 42).unwrap(), + PathBuf::from("/run/user/42/solstone-linux/browser-host.sock") + ); + assert_eq!( + endpoint_path(Some("relative".into()), 42).unwrap(), + PathBuf::from("/run/user/42/solstone-linux/browser-host.sock") + ); + assert!(endpoint_path(Some(format!("/{}", "x".repeat(120)).into()), 1).is_err()); + } + + fn vendored(relative: &str) -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../vendor") + .join(relative) + } + + /// The vendored contract and frame crate are byte-for-byte the pinned bundle. + #[test] + fn the_vendored_browser_contract_is_the_pinned_bundle() { + use sha2::{Digest, Sha256}; + let sha = |path: &Path| format!("{:x}", Sha256::digest(std::fs::read(path).unwrap())); + let manifest_path = vendored("contracts/native-browser/manifest.json"); + let adoption: serde_json::Value = serde_json::from_slice( + &std::fs::read(vendored("contracts/native-browser/adoption.json")).unwrap(), + ) + .unwrap(); + assert_eq!(adoption["manifest_sha256"], sha(&manifest_path)); + let manifest: serde_json::Value = + serde_json::from_slice(&std::fs::read(&manifest_path).unwrap()).unwrap(); + assert_eq!( + manifest["wire_protocol"], + native_browser_frame::WIRE_PROTOCOL + ); + assert_eq!( + manifest["bundle_version"], + native_browser_frame::BUNDLE_VERSION + ); + for (relative, expected) in manifest["artifacts"].as_object().unwrap() { + assert_eq!(&sha(&vendored(relative)), expected, "{relative} drifted"); + } + } + + /// The shared vector corpus decodes here exactly as it does for the extension and + /// the Mac app. + #[test] + fn the_shared_vector_corpus_decodes_as_specified() { + use native_browser_frame::{DecodeOutcome, Direction, decode}; + let corpus: Vec = serde_json::from_slice( + &std::fs::read(vendored("contracts/native-browser/corpus.json")).unwrap(), + ) + .unwrap(); + assert!(!corpus.is_empty()); + for vector in &corpus { + let id = vector["id"].as_str().unwrap(); + let direction = match vector["direction"].as_str().unwrap() { + "extension_to_host" => Direction::ExtensionToHost, + _ => Direction::HostToExtension, + }; + let bytes = vector + .get("payload") + .or_else(|| vector.get("raw")) + .and_then(serde_json::Value::as_str) + .unwrap() + .as_bytes(); + match (vector["expect"].as_str().unwrap(), decode(bytes, direction)) { + ("accept", DecodeOutcome::Accept(_)) => {} + ("unsupported", DecodeOutcome::Unsupported { behind, .. }) => { + if let Some(expected) = vector.get("behind").and_then(|v| v.as_str()) { + assert_eq!(behind, expected, "{id}"); + } + } + ("refuse", DecodeOutcome::Refuse(error)) => { + if let Some(code) = vector.get("code").and_then(|v| v.as_str()) { + assert_eq!(error.code, code, "{id}"); + } + } + (expected, outcome) => panic!("{id}: expected {expected}, got {outcome:?}"), + } + } + } + + #[test] + fn browser_segments_are_recognised_by_their_stream_folder() { + assert!(is_browser_segment(Path::new( + "/c/20261002/_browser/101500_300" + ))); + assert!(!is_browser_segment(Path::new( + "/c/20261002/host/101500_300" + ))); + } +} diff --git a/crates/solstone-linux/src/browser/argv.rs b/crates/solstone-linux/src/browser/argv.rs new file mode 100644 index 0000000..57e9194 --- /dev/null +++ b/crates/solstone-linux/src/browser/argv.rs @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +//! Recognising a browser's native-messaging launch from argv alone, before any other +//! startup work. The id check is a consistency check, not caller authentication: any +//! process of the same user can launch this binary with any arguments. + +use native_browser_frame::{DEV_CHROME_ID, DEV_FIREFOX_ID, PROD_CHROME_ID, PROD_FIREFOX_ID}; +use std::ffi::OsString; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Channel { + Production, + Development, +} + +impl Channel { + pub fn as_str(self) -> &'static str { + match self { + Channel::Production => "production", + Channel::Development => "development", + } + } +} + +/// Chrome and Edge share one extension id, so a Chromium launch is told apart only by +/// the extension's own `hello`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BrandHint { + Chromium, + Firefox, +} + +impl BrandHint { + pub fn as_str(self) -> &'static str { + match self { + BrandHint::Chromium => "chromium", + BrandHint::Firefox => "firefox", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct HostInvocation { + pub brand: BrandHint, + pub channel: Channel, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Recognition { + /// An ordinary command line; the CLI handles it. + NotHost, + Host(HostInvocation), + /// Looks like a native-messaging launch, but for an extension this build does not + /// admit. + Refused, +} + +const CHROME_ORIGIN_PREFIX: &str = "chrome-extension://"; + +/// `args` excludes argv[0]. Chromium passes `chrome-extension:///`; Firefox passes +/// the manifest path and then the extension id. +pub fn recognize(args: &[OsString], dev_enabled: bool) -> Recognition { + let args: Option> = args.iter().map(|arg| arg.to_str()).collect(); + let Some(args) = args else { + return Recognition::NotHost; + }; + let channel_for = |production: bool, development: bool| match (production, development) { + (true, false) => Recognition::Host(HostInvocation { + brand: BrandHint::Chromium, + channel: Channel::Production, + }), + (false, true) if dev_enabled => Recognition::Host(HostInvocation { + brand: BrandHint::Chromium, + channel: Channel::Development, + }), + _ => Recognition::Refused, + }; + match args.as_slice() { + [origin] if origin.starts_with(CHROME_ORIGIN_PREFIX) => { + let Some(id) = origin + .strip_prefix(CHROME_ORIGIN_PREFIX) + .and_then(|rest| rest.strip_suffix('/')) + else { + return Recognition::Refused; + }; + channel_for(id == PROD_CHROME_ID, id == DEV_CHROME_ID) + } + [first, ..] if first.starts_with(CHROME_ORIGIN_PREFIX) => Recognition::Refused, + [manifest, id] if is_firefox_launch(manifest, id) => { + if manifest.is_empty() { + return Recognition::Refused; + } + match channel_for(*id == PROD_FIREFOX_ID, *id == DEV_FIREFOX_ID) { + Recognition::Host(invocation) => Recognition::Host(HostInvocation { + brand: BrandHint::Firefox, + ..invocation + }), + other => other, + } + } + _ => Recognition::NotHost, + } +} + +/// Firefox's launch: a manifest path and an add-on id. Ids are `name@domain` or a +/// braced UUID; the CLI has no two-argument form that looks like this. +fn is_firefox_launch(manifest: &str, id: &str) -> bool { + !manifest.starts_with('-') + && manifest.ends_with(".json") + && (id.contains('@') || (id.starts_with('{') && id.ends_with('}'))) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn args(values: &[&str]) -> Vec { + values.iter().map(OsString::from).collect() + } + + fn host(brand: BrandHint, channel: Channel) -> Recognition { + Recognition::Host(HostInvocation { brand, channel }) + } + + #[test] + fn production_ids_are_recognised_in_every_build() { + for dev in [false, true] { + assert_eq!( + recognize( + &args(&["chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim/"]), + dev + ), + host(BrandHint::Chromium, Channel::Production) + ); + assert_eq!( + recognize( + &args(&[ + "/home/o/.mozilla/native-messaging-hosts/app.solstone.browser.json", + "browser@solstone.app" + ]), + dev + ), + host(BrandHint::Firefox, Channel::Production) + ); + } + } + + #[test] + fn development_ids_are_refused_unless_the_build_admits_them() { + let chromium = args(&["chrome-extension://fgfnkcefedeheoeamppkiiloncfekakf/"]); + let firefox = args(&[ + "/m/app.solstone.browser.dev.json", + "browser.dev@solstone.app", + ]); + assert_eq!(recognize(&chromium, false), Recognition::Refused); + assert_eq!(recognize(&firefox, false), Recognition::Refused); + assert_eq!( + recognize(&chromium, true), + host(BrandHint::Chromium, Channel::Development) + ); + assert_eq!( + recognize(&firefox, true), + host(BrandHint::Firefox, Channel::Development) + ); + } + + #[test] + fn native_looking_launches_for_other_extensions_are_refused() { + for argv in [ + vec!["chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/"], + vec!["chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim"], + vec![ + "chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim/", + "--parent-window=0", + ], + vec!["/m/app.solstone.browser.json", "other@example.org"], + vec![ + "/m/app.solstone.browser.json", + "{9f1c2c9e-0000-0000-0000-000000000000}", + ], + ] { + assert_eq!( + recognize(&args(&argv), true), + Recognition::Refused, + "{argv:?}" + ); + } + } + + #[test] + fn ordinary_command_lines_are_left_to_the_cli() { + for argv in [ + vec![], + vec!["run"], + vec!["status"], + vec!["setup", "--mark"], + vec!["pause", "--minutes"], + vec!["--help"], + ] { + assert_eq!( + recognize(&args(&argv), true), + Recognition::NotHost, + "{argv:?}" + ); + } + } +} diff --git a/crates/solstone-linux/src/browser/custody.rs b/crates/solstone-linux/src/browser/custody.rs new file mode 100644 index 0000000..bc2da22 --- /dev/null +++ b/crates/solstone-linux/src/browser/custody.rs @@ -0,0 +1,1324 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +//! What the app holds for the journal from the browser, on this computer. +//! +//! - A batch is accepted only after its records and its receipt are on disk, so a +//! replayed batch id is answered `duplicate` and nothing is taken in twice. +//! - Text is kept in five-minute periods, one `browser_pages.jsonl` each. A finished +//! period becomes a capture segment under the `_browser` stream folder, and the sync +//! service delivers it as the `browser` source. +//! - Everything is stamped with a destination generation, which belongs to exactly one +//! journal: the paired instance plus its CA chain. When the app finds itself paired +//! to a different journal, everything held for the old one is retired before +//! anything else runs. Retired text is kept and shown, never delivered or counted, +//! until the owner discards it. +//! - One bound covers everything held: past it, new batches are refused as +//! `queue_full` and the extension is told custody is full. + +use chrono::{DateTime, Datelike, Local, NaiveDate, TimeZone, Timelike}; +use native_browser_frame::{ + ACCEPTED_RETENTION_MS_MIN, FILE_MAX, FUTURE_SKEW_MS_MAX, OUTBOX_AGE_MS_MAX, SPOOL_BYTES_MAX, + canonical_stringify, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use sha2::{Digest, Sha256}; +use std::{ + collections::{HashMap, HashSet}, + fs::{self, File, OpenOptions}, + io::{self, BufRead, BufReader, Read, Write}, + os::unix::fs::{DirBuilderExt, OpenOptionsExt}, + path::{Path, PathBuf}, +}; + +/// Sidecar naming the period and its generation. Dot files are never uploaded. +pub const PERIOD_FILE: &str = ".browser_period.json"; +/// The receipts that cover the period's payload, for duplicate answers. +pub const RECEIPTS_FILE: &str = ".browser_receipts.jsonl"; +const GENERATION_FILE: &str = "generation.json"; +const PERIOD_MINUTES: u32 = 5; + +#[derive(Clone, Debug)] +pub struct Layout { + pub root: PathBuf, + pub captures: PathBuf, +} + +impl Layout { + pub fn new(base_dir: &Path) -> Self { + Self { + root: super::custody_root(base_dir), + captures: base_dir.join("captures"), + } + } + fn open(&self) -> PathBuf { + self.root.join("open") + } + pub fn retired(&self) -> PathBuf { + self.root.join("retired") + } + fn generation(&self) -> PathBuf { + self.root.join(GENERATION_FILE) + } +} + +/// The strict identity of a journal: the exact instance and its CA chain, with PEM +/// formatting differences removed. Never instance alone. +pub fn journal_identity(instance_id: &str, ca_chain_pem: &[String]) -> String { + let mut digest = Sha256::new(); + digest.update(b"solstone-browser-journal-v1\0"); + digest.update(instance_id.as_bytes()); + // One separator per certificate, however the chain's PEM text is split. + for line in ca_chain_pem + .iter() + .flat_map(|pem| pem.lines()) + .map(str::trim) + { + if line.starts_with("-----BEGIN") { + digest.update(b"\0"); + } else if !line.is_empty() && !line.starts_with("-----") { + digest.update(line.as_bytes()); + } + } + format!("{:x}", digest.finalize()) +} + +#[derive(Serialize, Deserialize)] +struct GenerationRecord { + journal: String, + generation: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct PeriodRecord { + period_id: String, + generation: String, + created_at_ms: u64, +} + +#[derive(Serialize, Deserialize)] +struct ReceiptLine { + inst: String, + batch_id: String, + end: u64, + at_ms: u64, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct Bucket { + date: NaiveDate, + index: u32, +} + +impl Bucket { + fn of(now: &DateTime) -> Self { + Self { + date: now.date_naive(), + index: (now.hour() * 60 + now.minute()) / PERIOD_MINUTES, + } + } + fn start_ms(&self) -> Option { + let minutes = self.index * PERIOD_MINUTES; + let naive = self.date.and_hms_opt(minutes / 60, minutes % 60, 0)?; + let local = Local.from_local_datetime(&naive).earliest()?; + u64::try_from(local.timestamp_millis()).ok() + } +} + +#[derive(Debug)] +struct Period { + id: String, + bucket: Bucket, + created_at_ms: u64, + committed: u64, + receipts_len: u64, + contexts: HashSet<(String, String)>, + has_dir: bool, +} + +#[derive(Clone, Debug)] +struct Receipt { + period_id: String, + at_ms: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Outcome { + Accepted { period_id: String }, + Duplicate { period_id: String }, + Rejected { reason: &'static str }, +} + +/// What the extension is told about capture and delivery. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Facts { + pub capture: &'static str, + pub delivery: &'static str, + pub failure: Option<&'static str>, + pub generation: Option, + pub period_id: Option, + pub full: bool, + pub held_bytes: u64, +} + +pub struct Custody { + layout: Layout, + generation: Option, + period: Option, + receipts: HashMap<(String, String), Receipt>, + /// Finished periods that could not yet be turned into segments; retried each tick. + unfinished: Vec<(Period, DateTime)>, + held_bytes: u64, + bound: u64, +} + +impl Custody { + /// Open custody for the journal this app is paired with (`None` when unpaired). + /// Retirement happens here, before the sync service can deliver anything. + pub fn open(layout: Layout, journal: Option<&str>, now: DateTime) -> io::Result { + create_private_dir(&layout.root)?; + create_private_dir(&layout.open())?; + create_private_dir(&layout.retired())?; + let generation = match journal { + None => None, + Some(journal) => Some(generation_for(&layout, journal)?), + }; + let mut custody = Self { + layout, + generation, + period: None, + receipts: HashMap::new(), + unfinished: Vec::new(), + held_bytes: 0, + bound: SPOOL_BYTES_MAX as u64, + }; + if custody.generation.is_some() { + custody.retire_foreign(now); + custody.recover_open(now); + custody.load_finalized_receipts(); + if custody.period.is_none() { + custody.period = Some(new_period(&now)); + } + } + custody.refresh_held(); + Ok(custody) + } + + #[cfg(test)] + fn with_bound(mut self, bound: u64) -> Self { + self.bound = bound; + self + } + + pub fn generation(&self) -> Option<&str> { + self.generation.as_deref() + } + + pub fn period_id(&self) -> Option<&str> { + self.period.as_ref().map(|period| period.id.as_str()) + } + + pub fn facts(&self, paused: bool) -> Facts { + let held = self.held_bytes > 0; + let Some(generation) = &self.generation else { + return Facts { + capture: "not_paired", + delivery: if held { "kept_locally" } else { "unknown" }, + failure: None, + generation: None, + period_id: None, + full: false, + held_bytes: self.held_bytes, + }; + }; + let full = self.held_bytes >= self.bound; + Facts { + capture: if paused { + "paused" + } else if full { + "intake_off" + } else { + "permitted" + }, + delivery: if held { "kept_locally" } else { "idle" }, + failure: full.then_some("queue_full"), + generation: Some(generation.clone()), + period_id: self.period_id().map(str::to_owned), + full, + held_bytes: self.held_bytes, + } + } + + /// Start a new period when the five-minute local-clock bucket changes. Returns the + /// new period id, which every connected browser must hear as a `boundary`. + pub fn tick(&mut self, now: DateTime) -> Option { + self.generation.as_ref()?; + if !self.unfinished.is_empty() { + let pending = std::mem::take(&mut self.unfinished); + for (period, end) in pending { + if let Err(error) = self.finalize(&period, end) { + tracing::debug!(%error, "A browser period is still waiting to finish"); + self.unfinished.push((period, end)); + } + } + } + let bucket = Bucket::of(&now); + if self + .period + .as_ref() + .is_some_and(|period| period.bucket == bucket) + { + return None; + } + self.rotate(now) + } + + fn rotate(&mut self, now: DateTime) -> Option { + if let Some(period) = self.period.take() + && let Err(error) = self.finalize(&period, now) + { + tracing::warn!(%error, "Could not finish a browser period yet; it stays held"); + self.unfinished.push((period, now)); + } + self.prune_receipts(now_ms(&now)); + let period = new_period(&now); + let id = period.id.clone(); + self.period = Some(period); + self.refresh_held(); + Some(id) + } + + /// Accept one decoded batch. The second value is a new period id when accepting it + /// had to start a new period. + pub fn accept(&mut self, batch: &Value, now: DateTime) -> (Outcome, Option) { + let field = |name: &str| batch.get(name).and_then(Value::as_str).unwrap_or_default(); + let (generation, inst, batch_id) = ( + field("destination_generation"), + field("inst"), + field("batch_id"), + ); + let key = (inst.to_owned(), batch_id.to_owned()); + if self.generation.as_deref() == Some(generation) + && let Some(receipt) = self.receipts.get(&key) + { + return ( + Outcome::Duplicate { + period_id: receipt.period_id.clone(), + }, + None, + ); + } + if self.generation.as_deref() != Some(generation) || self.period.is_none() { + return (reject("stale_generation"), None); + } + let now_ms = now_ms(&now); + let queued_at = batch + .get("queued_at_ms") + .and_then(native_browser_frame::codec::nonnegative_integer) + .unwrap_or(0); + if queued_at > now_ms.saturating_add(FUTURE_SKEW_MS_MAX) { + return (reject("age_policy"), None); + } + if now_ms.saturating_sub(queued_at) >= OUTBOX_AGE_MS_MAX { + return (reject("expired_unaccepted"), None); + } + let Some(records) = batch.get("records").and_then(Value::as_array) else { + return (reject("malformed"), None); + }; + let mut payload = String::new(); + for record in records { + if canonical_stringify(record, &mut payload).is_err() { + return (reject("malformed"), None); + } + payload.push('\n'); + } + let bytes = payload.len() as u64; + if bytes > FILE_MAX as u64 { + return (reject("resource_exhausted"), None); + } + let mut rotated = None; + if self.period.as_ref().is_some_and(|period| { + period.committed > 0 && period.committed + bytes > FILE_MAX as u64 + }) { + rotated = self.rotate(now); + } + let first = &records[0]; + let context = ( + inst.to_owned(), + first + .get("ctx") + .and_then(Value::as_str) + .unwrap_or_default() + .to_owned(), + ); + let snapshot = first.get("t").and_then(Value::as_str) == Some("segment_start"); + let period = self + .period + .as_ref() + .expect("a generation always has a period"); + if !snapshot && !period.contexts.contains(&context) { + return (reject("snapshot_required"), rotated); + } + if self.held_bytes + bytes > self.bound { + return (reject("queue_full"), rotated); + } + match self.commit(&payload, inst, batch_id, now_ms) { + Ok(period_id) => { + let period = self.period.as_mut().expect("committed into a period"); + if snapshot { + period.contexts.insert(context); + } + self.receipts.insert( + key, + Receipt { + period_id: period_id.clone(), + at_ms: now_ms, + }, + ); + self.held_bytes += bytes; + (Outcome::Accepted { period_id }, rotated) + } + Err(error) => { + tracing::warn!(%error, "Could not keep a browser batch"); + (reject("resource_exhausted"), rotated) + } + } + } + + /// Write the records, then the receipt that covers them, each at the length this + /// process knows is committed. Bytes past that length are never covered by a + /// receipt, so a failed or torn write is overwritten by the next one, and a crash + /// leaves them to be cut back on reopen. + fn commit( + &mut self, + payload: &str, + inst: &str, + batch_id: &str, + at_ms: u64, + ) -> io::Result { + let generation = self.generation.clone().expect("commit needs a generation"); + let open_root = self.layout.open(); + let period = self.period.as_mut().expect("commit needs a period"); + let directory = open_root.join(&period.id); + let first = !period.has_dir; + if first { + create_private_dir(&directory)?; + write_atomic( + &directory.join(PERIOD_FILE), + &serde_json::to_vec(&PeriodRecord { + period_id: period.id.clone(), + generation, + created_at_ms: period.created_at_ms, + }) + .map_err(io::Error::other)?, + )?; + sync_dir(&open_root)?; + period.has_dir = true; + } + let end = period.committed + payload.len() as u64; + let mut line = serde_json::to_vec(&ReceiptLine { + inst: inst.to_owned(), + batch_id: batch_id.to_owned(), + end, + at_ms, + }) + .map_err(io::Error::other)?; + line.push(b'\n'); + write_at( + &directory.join(super::PAGES_FILENAME), + period.committed, + payload.as_bytes(), + )?; + write_at(&directory.join(RECEIPTS_FILE), period.receipts_len, &line)?; + if first { + // The payload and receipt files are new: make their names durable too. + sync_dir(&directory)?; + } + period.committed = end; + period.receipts_len += line.len() as u64; + Ok(period.id.clone()) + } + + /// Turn a finished period into a capture segment the sync service delivers. + fn finalize(&self, period: &Period, end: DateTime) -> io::Result<()> { + let directory = self.layout.open().join(&period.id); + if !period.has_dir { + return Ok(()); + } + if truncate_to_receipts(&directory)? == 0 { + return remove_dir_all_if_present(&directory); + } + let start_ms = period + .bucket + .start_ms() + .unwrap_or(period.created_at_ms) + .max(period.created_at_ms); + let captures = &self.layout.captures; + move_period( + &directory, + |day| captures.join(day).join(super::STREAM_DIR), + start_ms, + now_ms(&end), + ) + } + + /// Move everything held for any other journal out of reach of delivery. Each item + /// is handled on its own: one that cannot be moved now stays where it is, and the + /// sync service still never delivers it, because it is not this generation's. + fn retire_foreign(&mut self, now: DateTime) { + let current = self.generation.clone().expect("retire needs a generation"); + for directory in sorted_dirs(&self.layout.open()) { + let record = read_period(&directory); + if record + .as_ref() + .is_some_and(|record| record.generation == current) + { + continue; + } + let label = record + .as_ref() + .map_or("unknown", |record| record.generation.as_str()); + let result = (|| -> io::Result<()> { + if truncate_to_receipts(&directory)? == 0 { + return remove_dir_all_if_present(&directory); + } + let start_ms = record + .as_ref() + .map_or_else(|| now_ms(&now), |r| r.created_at_ms); + let end_ms = + modified_ms(&directory.join(super::PAGES_FILENAME)).unwrap_or(start_ms); + let retired = self.layout.retired().join(safe_label(label)); + move_period( + &directory, + |day| retired.join(day), + start_ms, + end_ms.max(start_ms), + ) + })(); + if let Err(error) = result { + tracing::warn!(%error, "Could not retire an open browser period"); + } + } + for segment in browser_segments(&self.layout.captures) { + let generation = read_period(&segment).map(|record| record.generation); + if generation.as_deref() == Some(current.as_str()) { + continue; + } + let (Some(key), Some(day)) = ( + segment.file_name(), + segment + .parent() + .and_then(Path::parent) + .and_then(Path::file_name), + ) else { + continue; + }; + let parent = self + .layout + .retired() + .join(safe_label(generation.as_deref().unwrap_or("unknown"))) + .join(day); + let result = fs::create_dir_all(&parent) + .and_then(|()| fs::rename(&segment, parent.join(key))) + .and_then(|()| sync_dir(&parent)); + if let Err(error) = result { + tracing::warn!(%error, "Could not retire a browser period"); + } + } + } + + fn recover_open(&mut self, now: DateTime) { + let current = self.generation.clone().expect("recover needs a generation"); + let bucket = Bucket::of(&now); + for directory in sorted_dirs(&self.layout.open()) { + let Some(record) = read_period(&directory) else { + continue; + }; + if record.generation != current { + continue; + } + let committed = match truncate_to_receipts(&directory) { + Ok(committed) => committed, + Err(error) => { + tracing::warn!(%error, "Could not reopen a browser period"); + continue; + } + }; + let receipts = read_receipts(&directory); + for line in &receipts { + self.receipts.insert( + (line.inst.clone(), line.batch_id.clone()), + Receipt { + period_id: record.period_id.clone(), + at_ms: line.at_ms, + }, + ); + } + let created = Local + .timestamp_millis_opt(record.created_at_ms as i64) + .single() + .unwrap_or(now); + let period = Period { + id: record.period_id.clone(), + bucket: Bucket::of(&created), + created_at_ms: record.created_at_ms, + committed, + receipts_len: receipts_length(&directory), + contexts: snapshot_contexts(&directory.join(super::PAGES_FILENAME)), + has_dir: true, + }; + if period.bucket == bucket && self.period.is_none() { + self.period = Some(period); + } else { + let end = modified_ms(&directory.join(super::PAGES_FILENAME)) + .and_then(|ms| Local.timestamp_millis_opt(ms as i64).single()) + .unwrap_or(now); + if let Err(error) = self.finalize(&period, end) { + tracing::warn!(%error, "Could not finish a browser period yet"); + self.unfinished.push((period, end)); + } + } + } + } + + fn load_finalized_receipts(&mut self) { + let current = self.generation.clone(); + for segment in browser_segments(&self.layout.captures) { + let Some(record) = read_period(&segment) else { + continue; + }; + if Some(&record.generation) != current.as_ref() { + continue; + } + for line in read_receipts(&segment) { + self.receipts.insert( + (line.inst, line.batch_id), + Receipt { + period_id: record.period_id.clone(), + at_ms: line.at_ms, + }, + ); + } + } + } + + fn prune_receipts(&mut self, now_ms: u64) { + // Keep receipts of the open period, and finished ones for at least the + // contract's retention, so a replay after a lost reply is still a duplicate. + let open = self.period_id().map(str::to_owned); + self.receipts.retain(|_, receipt| { + Some(&receipt.period_id) == open.as_ref() + || now_ms.saturating_sub(receipt.at_ms) < 3 * ACCEPTED_RETENTION_MS_MIN + }); + } + + /// Recount what is held for the journal: open periods plus finished browser + /// segments not yet released by the sync service. + pub fn refresh_held(&mut self) { + self.held_bytes = held_bytes(&self.layout); + } +} + +pub fn held_bytes(layout: &Layout) -> u64 { + let open: u64 = sorted_dirs(&layout.open()) + .iter() + .map(|directory| file_len(&directory.join(super::PAGES_FILENAME))) + .sum(); + let finished: u64 = browser_segments(&layout.captures) + .iter() + .map(|segment| file_len(&segment.join(super::PAGES_FILENAME))) + .sum(); + open + finished +} + +/// The generation the sync service may deliver browser periods for: the recorded +/// generation, but only while the paired journal is still the one it belongs to. +pub fn deliverable_generation(layout: &Layout, journal: Option<&str>) -> Option { + let record: GenerationRecord = + serde_json::from_slice(&fs::read(layout.generation()).ok()?).ok()?; + (Some(record.journal.as_str()) == journal).then_some(record.generation) +} + +/// The generation a finished browser period was accepted under. +pub fn segment_generation(segment_dir: &Path) -> Option { + read_period(segment_dir).map(|record| record.generation) +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct RetiredSummary { + pub periods: usize, + pub bytes: u64, +} + +pub fn retired_summary(layout: &Layout) -> RetiredSummary { + let mut summary = RetiredSummary::default(); + for generation in sorted_dirs(&layout.retired()) { + for day in sorted_dirs(&generation) { + for period in sorted_dirs(&day) { + summary.periods += 1; + summary.bytes += file_len(&period.join(super::PAGES_FILENAME)); + } + } + } + summary +} + +/// Discard browser text kept for a journal this computer is no longer paired with. +/// Nothing held for the current journal is touched. +pub fn discard_retired(layout: &Layout) -> io::Result { + let summary = retired_summary(layout); + for generation in sorted_dirs(&layout.retired()) { + fs::remove_dir_all(&generation)?; + } + if layout.retired().exists() { + sync_dir(&layout.retired())?; + } + Ok(summary) +} + +fn generation_for(layout: &Layout, journal: &str) -> io::Result { + if let Ok(bytes) = fs::read(layout.generation()) + && let Ok(record) = serde_json::from_slice::(&bytes) + && record.journal == journal + { + return Ok(record.generation); + } + let generation = random_id()?; + write_atomic( + &layout.generation(), + &serde_json::to_vec(&GenerationRecord { + journal: journal.to_owned(), + generation: generation.clone(), + }) + .map_err(io::Error::other)?, + )?; + Ok(generation) +} + +fn new_period(now: &DateTime) -> Period { + Period { + id: random_id().unwrap_or_else(|_| format!("{:032x}", now_ms(now))), + bucket: Bucket::of(now), + created_at_ms: now_ms(now), + committed: 0, + receipts_len: 0, + contexts: HashSet::new(), + has_dir: false, + } +} + +/// Move an open period directory to `//`, named by its +/// local start time like every capture segment. +fn move_period( + directory: &Path, + parent_for_day: impl Fn(&str) -> PathBuf, + start_ms: u64, + end_ms: u64, +) -> io::Result<()> { + let start = Local + .timestamp_millis_opt(start_ms as i64) + .single() + .ok_or_else(|| io::Error::other("period start is not a local time"))?; + let day = format!("{:04}{:02}{:02}", start.year(), start.month(), start.day()); + let stem = format!( + "{:02}{:02}{:02}", + start.hour(), + start.minute(), + start.second() + ); + let length = (end_ms.saturating_sub(start_ms) / 1000).max(1); + let parent = parent_for_day(&day); + for extra in 0..60 { + let target = parent.join(format!("{stem}_{}", length + extra)); + if target.exists() { + continue; + } + // The sync service removes empty stream folders, so the folder can vanish + // between creating it and the rename; create it again and retry. + let mut attempts = 0; + loop { + fs::create_dir_all(&parent)?; + match fs::rename(directory, &target) { + Err(error) if error.kind() == io::ErrorKind::NotFound && attempts < 3 => { + attempts += 1; + } + result => { + result?; + break; + } + } + } + sync_dir(&parent)?; + return Ok(()); + } + Err(io::Error::new( + io::ErrorKind::AlreadyExists, + "no free segment name for a browser period", + )) +} + +/// Cut the payload back to what the last receipt covers: bytes past it were never +/// acknowledged, so the extension still has them. +fn truncate_to_receipts(directory: &Path) -> io::Result { + let committed = read_receipts(directory).last().map_or(0, |line| line.end); + let pages = directory.join(super::PAGES_FILENAME); + match OpenOptions::new().write(true).open(&pages) { + Ok(file) => { + if file.metadata()?.len() > committed { + file.set_len(committed)?; + file.sync_data()?; + } + } + Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(0), + Err(error) => return Err(error), + } + Ok(committed) +} + +/// The receipts that cover the payload: complete lines only, each ending past the one +/// before it and within the payload actually on disk. Anything after the first line +/// that fails those checks never committed. +fn read_receipts(directory: &Path) -> Vec { + let Ok(bytes) = fs::read(directory.join(RECEIPTS_FILE)) else { + return Vec::new(); + }; + let payload = file_len(&directory.join(super::PAGES_FILENAME)); + let mut previous = 0; + let mut out = Vec::new(); + for line in bytes.split_inclusive(|byte| *byte == b'\n') { + if line.last() != Some(&b'\n') { + break; + } + let Ok(receipt) = serde_json::from_slice::(line) else { + break; + }; + if receipt.end <= previous || receipt.end > payload { + break; + } + previous = receipt.end; + out.push(receipt); + } + out +} + +/// The byte length of the receipts `read_receipts` accepts. +fn receipts_length(directory: &Path) -> u64 { + read_receipts(directory) + .iter() + .map(|receipt| serde_json::to_vec(receipt).map_or(0, |line| line.len() as u64 + 1)) + .sum() +} + +fn snapshot_contexts(pages: &Path) -> HashSet<(String, String)> { + let Ok(file) = File::open(pages) else { + return HashSet::new(); + }; + BufReader::new(file) + .lines() + .map_while(Result::ok) + .filter_map(|line| serde_json::from_str::(&line).ok()) + .filter(|record| record.get("t").and_then(Value::as_str) == Some("segment_start")) + .filter_map(|record| { + Some(( + record.get("inst")?.as_str()?.to_owned(), + record.get("ctx")?.as_str()?.to_owned(), + )) + }) + .collect() +} + +fn read_period(directory: &Path) -> Option { + serde_json::from_slice(&fs::read(directory.join(PERIOD_FILE)).ok()?).ok() +} + +fn browser_segments(captures: &Path) -> Vec { + sorted_dirs(captures) + .into_iter() + .flat_map(|day| sorted_dirs(&day.join(super::STREAM_DIR))) + .filter(|segment| { + let name = segment.file_name().unwrap_or_default().to_string_lossy(); + !name.ends_with(".incomplete") && !name.ends_with(".failed") + }) + .collect() +} + +fn sorted_dirs(root: &Path) -> Vec { + let Ok(entries) = fs::read_dir(root) else { + return Vec::new(); + }; + let mut paths: Vec<_> = entries + .filter_map(Result::ok) + .map(|entry| entry.path()) + .filter(|path| fs::symlink_metadata(path).is_ok_and(|meta| meta.is_dir())) + .collect(); + paths.sort(); + paths +} + +fn safe_label(label: &str) -> String { + let label: String = label + .chars() + .filter(char::is_ascii_alphanumeric) + .take(64) + .collect(); + if label.is_empty() { + "unknown".to_owned() + } else { + label + } +} + +fn file_len(path: &Path) -> u64 { + fs::metadata(path).map_or(0, |meta| meta.len()) +} + +fn modified_ms(path: &Path) -> Option { + let modified = fs::metadata(path).ok()?.modified().ok()?; + let since = modified.duration_since(std::time::UNIX_EPOCH).ok()?; + u64::try_from(since.as_millis()).ok() +} + +fn now_ms(now: &DateTime) -> u64 { + u64::try_from(now.timestamp_millis()).unwrap_or(0) +} + +fn reject(reason: &'static str) -> Outcome { + Outcome::Rejected { reason } +} + +fn random_id() -> io::Result { + let mut bytes = [0_u8; 16]; + File::open("/dev/urandom")?.read_exact(&mut bytes)?; + Ok(bytes.iter().map(|byte| format!("{byte:02x}")).collect()) +} + +fn create_private_dir(path: &Path) -> io::Result<()> { + match fs::DirBuilder::new() + .mode(0o700) + .recursive(true) + .create(path) + { + Ok(()) => Ok(()), + Err(error) if error.kind() == io::ErrorKind::AlreadyExists && path.is_dir() => Ok(()), + Err(error) => Err(error), + } +} + +/// Write `bytes` at `offset`, drop anything after them, and make it durable. +fn write_at(path: &Path, offset: u64, bytes: &[u8]) -> io::Result<()> { + use std::os::unix::fs::FileExt; + let file = OpenOptions::new() + .create(true) + .truncate(false) + .write(true) + .mode(0o600) + .open(path)?; + file.write_all_at(bytes, offset)?; + file.set_len(offset + bytes.len() as u64)?; + file.sync_data() +} + +fn write_atomic(path: &Path, contents: &[u8]) -> io::Result<()> { + let directory = path + .parent() + .ok_or_else(|| io::Error::other("path has no parent"))?; + let temporary = directory.join(format!( + ".{}.tmp", + path.file_name().unwrap_or_default().to_string_lossy() + )); + { + let mut file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .mode(0o600) + .open(&temporary)?; + file.write_all(contents)?; + file.sync_all()?; + } + fs::rename(&temporary, path)?; + sync_dir(directory) +} + +fn sync_dir(path: &Path) -> io::Result<()> { + File::open(path)?.sync_all() +} + +fn remove_dir_all_if_present(path: &Path) -> io::Result<()> { + match fs::remove_dir_all(path) { + Err(error) if error.kind() != io::ErrorKind::NotFound => Err(error), + _ => Ok(()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + fn at(hour: u32, minute: u32, second: u32) -> DateTime { + Local + .with_ymd_and_hms(2026, 10, 2, hour, minute, second) + .earliest() + .unwrap() + } + + fn layout(temp: &tempfile::TempDir) -> Layout { + Layout::new(temp.path()) + } + + fn batch(custody: &Custody, id: u8, records: Value, now: DateTime) -> Value { + json!({ + "type": "batch", + "destination_generation": custody.generation().unwrap(), + "inst": "inst-a", + "batch_id": format!("{id:032x}"), + "queued_at_ms": now_ms(&now) - 1000, + "records": records, + }) + } + + fn snapshot(ctx: &str, text: &str) -> Value { + json!([{"t": "segment_start", "ts": 1, "ctx": ctx, "inst": "inst-a", "site": "example.org", + "blocks": [{"id": "b1", "text": text}]}]) + } + + fn delta(ctx: &str, text: &str) -> Value { + json!([{"t": "delta", "ts": 2, "ctx": ctx, "inst": "inst-a", "op": "add", + "block": {"id": "b2", "text": text}}]) + } + + fn accepted(outcome: (Outcome, Option)) -> String { + match outcome.0 { + Outcome::Accepted { period_id } => period_id, + other => panic!("expected accepted, got {other:?}"), + } + } + + fn segment_files(captures: &Path) -> Vec { + browser_segments(captures) + .into_iter() + .map(|segment| segment.join(crate::browser::PAGES_FILENAME)) + .collect() + } + + #[test] + fn journal_identity_is_the_instance_and_the_normalised_ca_chain() { + let pem = "-----BEGIN CERTIFICATE-----\nAAAA\nBBBB\n-----END CERTIFICATE-----\n".to_owned(); + let reflowed = + "-----BEGIN CERTIFICATE-----\r\nAAAABBBB\r\n-----END CERTIFICATE-----".to_owned(); + assert_eq!( + journal_identity("i1", std::slice::from_ref(&pem)), + journal_identity("i1", &[reflowed]) + ); + assert_ne!( + journal_identity("i1", std::slice::from_ref(&pem)), + journal_identity("i2", std::slice::from_ref(&pem)) + ); + assert_ne!( + journal_identity("i1", std::slice::from_ref(&pem)), + journal_identity( + "i1", + &["-----BEGIN CERTIFICATE-----\nCCCC\n-----END CERTIFICATE-----".to_owned()] + ) + ); + } + + #[test] + fn unpaired_custody_reports_not_paired_and_accepts_nothing() { + let temp = tempfile::tempdir().unwrap(); + let mut custody = Custody::open(layout(&temp), None, at(10, 1, 0)).unwrap(); + let facts = custody.facts(false); + assert_eq!((facts.capture, facts.delivery), ("not_paired", "unknown")); + assert_eq!(facts.generation, None); + let batch = + json!({"destination_generation": "g", "inst": "i", "batch_id": "0", "records": []}); + assert_eq!( + custody.accept(&batch, at(10, 1, 0)).0, + reject("stale_generation") + ); + assert_eq!(custody.tick(at(10, 6, 0)), None); + } + + #[test] + fn a_batch_is_kept_once_and_a_replay_is_a_duplicate() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let first = batch(&custody, 1, snapshot("c1", "hello"), now); + let period = accepted(custody.accept(&first, now)); + assert_eq!( + custody.accept(&first, now).0, + Outcome::Duplicate { + period_id: period.clone() + } + ); + let facts = custody.facts(false); + assert_eq!( + (facts.capture, facts.delivery), + ("permitted", "kept_locally") + ); + // A reopen (an app restart) still knows the receipt. + drop(custody); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + assert_eq!(custody.period_id(), Some(period.as_str())); + assert_eq!( + custody.accept(&first, now).0, + Outcome::Duplicate { period_id: period } + ); + } + + #[test] + fn a_delta_needs_a_snapshot_of_its_context_in_the_same_period() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let orphan = batch(&custody, 1, delta("c1", "x"), now); + assert_eq!(custody.accept(&orphan, now).0, reject("snapshot_required")); + // Nothing was recorded, so the same batch id can come back as a snapshot. + accepted(custody.accept(&batch(&custody, 1, snapshot("c1", "x"), now), now)); + accepted(custody.accept(&batch(&custody, 2, delta("c1", "y"), now), now)); + // A new period needs a new snapshot. + let later = at(10, 6, 0); + assert!(custody.tick(later).is_some()); + assert_eq!( + custody + .accept(&batch(&custody, 3, delta("c1", "z"), later), later) + .0, + reject("snapshot_required") + ); + } + + #[test] + fn a_finished_period_becomes_one_browser_segment_named_by_its_start() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 2, 30); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let generation = custody.generation().unwrap().to_owned(); + accepted(custody.accept(&batch(&custody, 1, snapshot("c1", "alpha"), now), now)); + accepted(custody.accept(&batch(&custody, 2, delta("c1", "beta"), now), now)); + assert!(custody.tick(at(10, 4, 59)).is_none()); + let next = custody.tick(at(10, 5, 0)).unwrap(); + assert_eq!(custody.period_id(), Some(next.as_str())); + let segment = temp.path().join("captures/20261002/_browser/100230_150"); + let pages = fs::read_to_string(segment.join(crate::browser::PAGES_FILENAME)).unwrap(); + assert_eq!(pages.lines().count(), 2); + assert!(pages.contains("alpha") && pages.contains("beta")); + assert_eq!( + segment_generation(&segment).as_deref(), + Some(generation.as_str()) + ); + // An empty period leaves nothing behind. + assert!(custody.tick(at(10, 10, 0)).is_some()); + assert_eq!(segment_files(&temp.path().join("captures")).len(), 1); + assert!(sorted_dirs(&temp.path().join("browser/open")).is_empty()); + } + + #[test] + fn a_different_journal_retires_everything_held_for_the_old_one() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let old_generation = custody.generation().unwrap().to_owned(); + accepted(custody.accept( + &batch(&custody, 1, snapshot("c1", "for-a-finished"), now), + now, + )); + custody.tick(at(10, 5, 0)); + let later = at(10, 6, 0); + accepted(custody.accept( + &batch(&custody, 2, snapshot("c1", "for-a-open"), later), + later, + )); + drop(custody); + + let custody = Custody::open(layout(&temp), Some("journal-b"), at(10, 7, 0)).unwrap(); + assert_ne!(custody.generation(), Some(old_generation.as_str())); + assert!(segment_files(&temp.path().join("captures")).is_empty()); + assert_eq!(custody.facts(false).held_bytes, 0); + let summary = retired_summary(&custody.layout); + assert_eq!(summary.periods, 2); + assert!(summary.bytes > 0); + assert_eq!( + deliverable_generation(&custody.layout, Some("journal-a")), + None + ); + assert_eq!( + deliverable_generation(&custody.layout, Some("journal-b")).as_deref(), + custody.generation() + ); + assert_eq!(discard_retired(&custody.layout).unwrap(), summary); + assert_eq!(retired_summary(&custody.layout), RetiredSummary::default()); + } + + #[test] + fn the_same_journal_keeps_its_generation() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let generation = custody.generation().unwrap().to_owned(); + drop(custody); + let custody = Custody::open(layout(&temp), Some("journal-a"), at(11, 0, 0)).unwrap(); + assert_eq!(custody.generation(), Some(generation.as_str())); + // Unpairing does not retire anything: an unknown journal is not a different one. + drop(custody); + let custody = Custody::open(layout(&temp), None, at(11, 0, 0)).unwrap(); + assert_eq!(custody.generation(), None); + let custody = + Custody::open(custody.layout.clone(), Some("journal-a"), at(11, 0, 0)).unwrap(); + assert_eq!(custody.generation(), Some(generation.as_str())); + } + + #[test] + fn a_stale_generation_and_aged_batches_are_refused() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let mut stale = batch(&custody, 1, snapshot("c1", "x"), now); + stale["destination_generation"] = json!("someone-else"); + assert_eq!(custody.accept(&stale, now).0, reject("stale_generation")); + let mut future = batch(&custody, 2, snapshot("c1", "x"), now); + future["queued_at_ms"] = json!(now_ms(&now) + FUTURE_SKEW_MS_MAX + 1); + assert_eq!(custody.accept(&future, now).0, reject("age_policy")); + let mut old = batch(&custody, 3, snapshot("c1", "x"), now); + old["queued_at_ms"] = json!(now_ms(&now) - OUTBOX_AGE_MS_MAX); + assert_eq!(custody.accept(&old, now).0, reject("expired_unaccepted")); + } + + #[test] + fn a_full_queue_refuses_new_intake_by_name() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + accepted(custody.accept(&batch(&custody, 1, snapshot("c1", "first"), now), now)); + let held = custody.facts(false).held_bytes; + custody = custody.with_bound(held + 50); + assert_eq!( + custody + .accept( + &batch(&custody, 2, snapshot("c2", &"y".repeat(200)), now), + now + ) + .0, + reject("queue_full") + ); + assert_eq!(custody.facts(false).capture, "permitted"); + custody = custody.with_bound(held); + let facts = custody.facts(false); + assert!(facts.full); + assert_eq!( + (facts.capture, facts.delivery, facts.failure), + ("intake_off", "kept_locally", Some("queue_full")) + ); + assert_eq!(custody.facts(true).capture, "paused"); + } + + #[test] + fn bytes_no_receipt_covers_are_dropped_on_reopen() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let period = + accepted(custody.accept(&batch(&custody, 1, snapshot("c1", "kept"), now), now)); + drop(custody); + let pages = temp + .path() + .join("browser/open") + .join(&period) + .join(crate::browser::PAGES_FILENAME); + let committed = fs::read(&pages).unwrap(); + OpenOptions::new() + .append(true) + .open(&pages) + .unwrap() + .write_all(b"{\"t\":\"segme") + .unwrap(); + let custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + assert_eq!(fs::read(&pages).unwrap(), committed); + assert_eq!(custody.facts(false).held_bytes, committed.len() as u64); + } + + #[test] + fn bytes_left_by_a_failed_write_are_overwritten_and_never_covered() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let period = + accepted(custody.accept(&batch(&custody, 1, snapshot("c1", "first"), now), now)); + let directory = temp.path().join("browser/open").join(&period); + // What an interrupted write leaves: a partial record and a partial receipt. + for (name, junk) in [ + ( + crate::browser::PAGES_FILENAME, + &b"{\"t\":\"delta\",\"ts\""[..], + ), + (RECEIPTS_FILE, &b"{\"inst\":\"inst-a\",\"batch_"[..]), + ] { + OpenOptions::new() + .append(true) + .open(directory.join(name)) + .unwrap() + .write_all(junk) + .unwrap(); + } + accepted(custody.accept(&batch(&custody, 2, delta("c1", "second"), now), now)); + drop(custody); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let pages = fs::read_to_string(directory.join(crate::browser::PAGES_FILENAME)).unwrap(); + assert_eq!(pages.lines().count(), 2); + assert!( + pages + .lines() + .all(|line| serde_json::from_str::(line).is_ok()) + ); + for id in [1, 2] { + assert!(matches!( + custody + .accept(&batch(&custody, id, snapshot("c1", "replay"), now), now) + .0, + Outcome::Duplicate { .. } + )); + } + } + + #[test] + fn a_receipt_for_bytes_that_are_not_there_is_ignored() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + let period = + accepted(custody.accept(&batch(&custody, 1, snapshot("c1", "first"), now), now)); + drop(custody); + let directory = temp.path().join("browser/open").join(&period); + let mut line = serde_json::to_vec(&ReceiptLine { + inst: "inst-a".into(), + batch_id: format!("{:032x}", 2), + end: 1_000_000, + at_ms: now_ms(&now), + }) + .unwrap(); + line.push(b'\n'); + OpenOptions::new() + .append(true) + .open(directory.join(RECEIPTS_FILE)) + .unwrap() + .write_all(&line) + .unwrap(); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + // The text of batch 2 never reached disk, so it is not a duplicate. + accepted(custody.accept(&batch(&custody, 2, delta("c1", "second"), now), now)); + } + + #[test] + fn an_old_open_period_is_finished_on_reopen() { + let temp = tempfile::tempdir().unwrap(); + let now = at(10, 1, 0); + let mut custody = Custody::open(layout(&temp), Some("journal-a"), now).unwrap(); + accepted(custody.accept(&batch(&custody, 1, snapshot("c1", "kept"), now), now)); + drop(custody); + let custody = Custody::open(layout(&temp), Some("journal-a"), at(12, 0, 0)).unwrap(); + assert_eq!(segment_files(&temp.path().join("captures")).len(), 1); + assert!(custody.period_id().is_some()); + } +} diff --git a/crates/solstone-linux/src/browser/host.rs b/crates/solstone-linux/src/browser/host.rs new file mode 100644 index 0000000..789a1dc --- /dev/null +++ b/crates/solstone-linux/src/browser/host.rs @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +//! Host mode: the process a browser starts for native messaging. It holds no journal +//! credential and opens no journal session. It relays whole frames between the +//! extension (stdin/stdout) and the running app's same-user endpoint, and writes +//! nothing but frames to stdout. + +use super::argv::HostInvocation; +use native_browser_frame::{Assembler, Chunk, Direction, Step}; +use std::{ + fs, + io::{self, Read, Write}, + os::unix::{ + fs::{FileTypeExt, MetadataExt}, + net::UnixStream, + }, + path::Path, + thread, +}; + +/// What the extension is told when the app is not running. +pub const UNAVAILABLE_HELLO_ACK: &[u8] = br#"{"type":"hello_ack","capture":"unavailable","delivery":"unknown","freshness_ms":0,"destination_generation":null,"period_id":null,"custody":{"full":false,"stale":false},"version":"1.1.0"}"#; +const ARGV_REJECTED: &[u8] = br#"{"code":"argv_rejected"}"#; + +/// Exit code for a refused native-messaging launch. +pub const EXIT_REFUSED: i32 = 1; + +pub fn refuse(output: &mut dyn Write) -> i32 { + let _ = write_frame(output, ARGV_REJECTED); + EXIT_REFUSED +} + +pub fn local_hello(invocation: HostInvocation) -> Vec { + format!( + r#"{{"brand":"{}","mode":"{}","type":"local_hello"}}"#, + invocation.brand.as_str(), + invocation.channel.as_str() + ) + .into_bytes() +} + +pub fn run(invocation: HostInvocation) -> i32 { + let mut stdout = io::stdout().lock(); + let endpoint = match super::production_endpoint_path() { + Ok(endpoint) => endpoint, + Err(_) => { + let _ = write_frame(&mut stdout, UNAVAILABLE_HELLO_ACK); + return 0; + } + }; + let socket = match connect(&endpoint, rustix::process::geteuid().as_raw()) { + Ok(socket) => socket, + Err(Connect::Unavailable) => { + let _ = write_frame(&mut stdout, UNAVAILABLE_HELLO_ACK); + return 0; + } + Err(Connect::Untrusted) => return 0, + }; + drop(stdout); + relay(invocation, socket, io::stdin(), io::stdout()) +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Connect { + /// Nothing is listening: the app is not running. + Unavailable, + /// Something is there that this user does not own outright. Say nothing. + Untrusted, +} + +/// Connect only to an endpoint in a directory this user owns with mode 0700, held by a +/// process of the same user. +pub fn connect(endpoint: &Path, uid: u32) -> Result { + let directory = endpoint.parent().ok_or(Connect::Untrusted)?; + match fs::symlink_metadata(directory) { + Ok(meta) if meta.is_dir() && meta.uid() == uid && meta.mode() & 0o777 == 0o700 => {} + Ok(_) => return Err(Connect::Untrusted), + Err(error) if error.kind() == io::ErrorKind::NotFound => { + return Err(Connect::Unavailable); + } + Err(_) => return Err(Connect::Untrusted), + } + match fs::symlink_metadata(endpoint) { + Ok(meta) if meta.file_type().is_socket() && meta.uid() == uid => {} + Ok(_) => return Err(Connect::Untrusted), + Err(error) if error.kind() == io::ErrorKind::NotFound => { + return Err(Connect::Unavailable); + } + Err(_) => return Err(Connect::Untrusted), + } + let socket = match UnixStream::connect(endpoint) { + Ok(socket) => socket, + Err(error) + if matches!( + error.kind(), + io::ErrorKind::NotFound | io::ErrorKind::ConnectionRefused + ) => + { + return Err(Connect::Unavailable); + } + Err(_) => return Err(Connect::Untrusted), + }; + match rustix::net::sockopt::socket_peercred(&socket) { + Ok(credentials) if credentials.uid.as_raw() == uid => Ok(socket), + _ => Err(Connect::Untrusted), + } +} + +/// Relay frames both ways until either side ends. Returns the process exit code. +pub fn relay(invocation: HostInvocation, socket: UnixStream, input: R, output: W) -> i32 +where + R: Read + Send + 'static, + W: Write, +{ + let Ok(mut to_app) = socket.try_clone() else { + return 0; + }; + if write_frame(&mut to_app, &local_hello(invocation)).is_err() { + return 0; + } + let upstream = thread::spawn(move || { + let _ = pump(input, &mut to_app, Direction::ExtensionToHost, |_| false); + // The extension went away: the app sees end of input and ends the session. + let _ = to_app.shutdown(std::net::Shutdown::Write); + }); + let mut output = output; + let _ = pump(socket, &mut output, Direction::HostToExtension, |body| { + matches!(message_type(body).as_deref(), Some("bye" | "unsupported")) + }); + let _ = output.flush(); + // Either the app ended the session or the extension did; there is nothing left to + // relay, so the input pump is not waited for. + drop(upstream); + 0 +} + +/// Copy whole frames from `input` to `output`, stopping at end of input, a framing +/// error, a write failure, or after a frame `stop_after` matches. +fn pump( + mut input: R, + output: &mut W, + direction: Direction, + stop_after: impl Fn(&[u8]) -> bool, +) -> io::Result<()> { + let mut assembler = Assembler::new(direction); + let mut buffer = vec![0_u8; 64 * 1024]; + loop { + let count = match input.read(&mut buffer) { + Ok(count) => count, + Err(error) if error.kind() == io::ErrorKind::Interrupted => continue, + Err(error) => return Err(error), + }; + let chunk = if count == 0 { + Chunk::Eof + } else { + Chunk::Data(&buffer[..count]) + }; + let steps = assembler.feed(chunk).map_err(io::Error::other)?; + for step in steps { + match step { + Step::Message(body) => { + write_frame(output, &body)?; + if stop_after(&body) { + return Ok(()); + } + } + Step::CleanEof => return Ok(()), + Step::EmptyRead | Step::NeedMore => {} + } + } + } +} + +pub fn write_frame(output: &mut W, body: &[u8]) -> io::Result<()> { + let length = u32::try_from(body.len()).map_err(io::Error::other)?; + let mut frame = Vec::with_capacity(4 + body.len()); + frame.extend_from_slice(&length.to_ne_bytes()); + frame.extend_from_slice(body); + output.write_all(&frame)?; + output.flush() +} + +fn message_type(body: &[u8]) -> Option { + let value: serde_json::Value = serde_json::from_slice(body).ok()?; + value.get("type")?.as_str().map(str::to_owned) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::browser::argv::{BrandHint, Channel}; + use native_browser_frame::{DecodeOutcome, decode}; + use std::os::unix::fs::PermissionsExt; + + fn invocation() -> HostInvocation { + HostInvocation { + brand: BrandHint::Firefox, + channel: Channel::Production, + } + } + + fn read_frame(input: &mut impl Read) -> Vec { + let mut length = [0_u8; 4]; + input.read_exact(&mut length).unwrap(); + let mut body = vec![0_u8; u32::from_ne_bytes(length) as usize]; + input.read_exact(&mut body).unwrap(); + body + } + + fn frame(body: &[u8]) -> Vec { + let mut out = Vec::new(); + write_frame(&mut out, body).unwrap(); + out + } + + #[test] + fn the_unavailable_answer_is_a_valid_hello_ack() { + assert!(matches!( + decode(UNAVAILABLE_HELLO_ACK, Direction::HostToExtension), + DecodeOutcome::Accept(_) + )); + } + + #[test] + fn local_hello_names_the_launch() { + assert_eq!( + local_hello(invocation()), + br#"{"brand":"firefox","mode":"production","type":"local_hello"}"#.to_vec() + ); + } + + #[test] + fn connect_reports_a_missing_app_and_refuses_a_loose_directory() { + let temp = tempfile::tempdir().unwrap(); + let uid = rustix::process::geteuid().as_raw(); + let directory = temp.path().join("solstone-linux"); + let endpoint = directory.join("browser-host.sock"); + assert_eq!(connect(&endpoint, uid).unwrap_err(), Connect::Unavailable); + fs::create_dir(&directory).unwrap(); + fs::set_permissions(&directory, fs::Permissions::from_mode(0o755)).unwrap(); + assert_eq!(connect(&endpoint, uid).unwrap_err(), Connect::Untrusted); + fs::set_permissions(&directory, fs::Permissions::from_mode(0o700)).unwrap(); + assert_eq!(connect(&endpoint, uid).unwrap_err(), Connect::Unavailable); + let listener = std::os::unix::net::UnixListener::bind(&endpoint).unwrap(); + assert!(connect(&endpoint, uid).is_ok()); + assert_eq!( + connect(&endpoint, uid.wrapping_add(1)).unwrap_err(), + Connect::Untrusted + ); + drop(listener); + assert_eq!(connect(&endpoint, uid).unwrap_err(), Connect::Unavailable); + } + + #[test] + fn relay_forwards_whole_frames_both_ways_and_stops_after_bye() { + let (host_side, mut app_side) = UnixStream::pair().unwrap(); + let hello = + br#"{"type":"hello","protocol":1,"version":"0.2.0","brand":"firefox","inst":"i"}"#; + // The extension's frame arrives split across reads. + let input = io::Cursor::new(frame(hello)); + let app = thread::spawn(move || { + assert_eq!(read_frame(&mut app_side), local_hello(invocation())); + assert_eq!(read_frame(&mut app_side), hello.to_vec()); + app_side + .write_all(&frame(br#"{"type":"bye","reason":"shutdown"}"#)) + .unwrap(); + app_side + .write_all(&frame(br#"{"type":"state","after":"bye"}"#)) + .unwrap(); + app_side + }); + let mut output = Vec::new(); + assert_eq!(relay(invocation(), host_side, input, &mut output), 0); + let _app_side = app.join().unwrap(); + let mut cursor = io::Cursor::new(output); + assert_eq!( + read_frame(&mut cursor), + br#"{"type":"bye","reason":"shutdown"}"#.to_vec() + ); + assert_eq!(cursor.position() as usize, cursor.get_ref().len()); + } + + #[test] + fn relay_refuses_an_oversized_frame_from_the_app() { + let (host_side, mut app_side) = UnixStream::pair().unwrap(); + let app = thread::spawn(move || { + let _ = read_frame(&mut app_side); + app_side.write_all(&(70_000_u32).to_ne_bytes()).unwrap(); + app_side + }); + let mut output = Vec::new(); + relay(invocation(), host_side, io::empty(), &mut output); + let _app_side = app.join().unwrap(); + assert!(output.is_empty()); + } +} diff --git a/crates/solstone-linux/src/browser/registration.rs b/crates/solstone-linux/src/browser/registration.rs new file mode 100644 index 0000000..86679c2 --- /dev/null +++ b/crates/solstone-linux/src/browser/registration.rs @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +//! Per-user native-messaging registrations. Written by `install-service` and by every +//! `run`, so a browser installed after the app is covered at the next start, and a +//! tampered or moved manifest is repaired. Removed by `uninstall-service`. + +use native_browser_frame::render_registration; +use std::{ + fs, + io::{self, Write}, + os::unix::fs::PermissionsExt, + path::{Path, PathBuf}, +}; + +/// The browsers a registration is written for, and where each one looks. Brave reads +/// Chrome's directory, and Chromium reads its own; both take the Chromium manifest. +pub const TARGETS: &[(&str, &str, &str)] = &[ + ( + "chrome", + "chrome", + ".config/google-chrome/NativeMessagingHosts", + ), + ( + "chromium", + "chrome", + ".config/chromium/NativeMessagingHosts", + ), + ( + "edge", + "edge", + ".config/microsoft-edge/NativeMessagingHosts", + ), + ("firefox", "firefox", ".mozilla/native-messaging-hosts"), +]; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Manifest { + pub browser: &'static str, + pub path: PathBuf, + pub json: String, +} + +fn channels(dev_enabled: bool) -> &'static [&'static str] { + if dev_enabled { + &["production", "dev"] + } else { + &["production"] + } +} + +/// Every manifest this build writes, pointing at `binary`. +pub fn manifests(home: &Path, binary: &Path, dev_enabled: bool) -> Vec { + let binary = binary.to_string_lossy(); + let mut out = Vec::new(); + for channel in channels(dev_enabled) { + for (browser, contract_browser, directory) in TARGETS { + let rendered = + render_registration(channel, contract_browser, "linux", Some(&binary), None) + .expect("the contract renders every linux registration"); + out.push(Manifest { + browser, + path: home.join(directory).join(&rendered.filename), + json: rendered.json, + }); + } + } + out +} + +/// Manifests this build must not leave behind: the development registrations, unless +/// the build admits the development extension. +fn stale_paths(home: &Path, dev_enabled: bool) -> Vec { + if dev_enabled { + return Vec::new(); + } + TARGETS + .iter() + .map(|(_, contract_browser, directory)| { + let rendered = render_registration("dev", contract_browser, "linux", None, None) + .expect("the contract renders every linux registration"); + home.join(directory).join(rendered.filename) + }) + .collect() +} + +/// Write every registration whose content differs, creating directories as needed. +/// Directory existence is not an install detector: a browser installed later finds +/// its registration already in place. +pub fn write_all(home: &Path, binary: &Path, dev_enabled: bool) -> io::Result { + let mut written = 0; + let mut first_error = None; + for manifest in manifests(home, binary, dev_enabled) { + match write_if_changed(&manifest.path, manifest.json.as_bytes()) { + Ok(true) => written += 1, + Ok(false) => {} + Err(error) => { + first_error.get_or_insert(error); + } + } + } + for path in stale_paths(home, dev_enabled) { + if let Err(error) = remove_if_present(&path) { + first_error.get_or_insert(error); + } + } + match first_error { + Some(error) => Err(error), + None => Ok(written), + } +} + +pub fn remove_all(home: &Path) -> io::Result<()> { + let mut first_error = None; + for path in manifests(home, Path::new("/"), true) + .into_iter() + .map(|manifest| manifest.path) + { + if let Err(error) = remove_if_present(&path) { + first_error.get_or_insert(error); + } + } + first_error.map_or(Ok(()), Err) +} + +/// Which registrations are in place and current, for `status` and `doctor`. +pub fn check(home: &Path, binary: &Path) -> Vec<(&'static str, bool)> { + manifests(home, binary, false) + .into_iter() + .map(|manifest| { + let current = + fs::read(&manifest.path).is_ok_and(|bytes| bytes == manifest.json.as_bytes()); + (manifest.browser, current) + }) + .collect() +} + +fn write_if_changed(path: &Path, contents: &[u8]) -> io::Result { + if fs::read(path).is_ok_and(|existing| existing == contents) { + return Ok(false); + } + let directory = path + .parent() + .ok_or_else(|| io::Error::other("registration path has no parent"))?; + fs::create_dir_all(directory)?; + let temporary = directory.join(format!( + ".{}.tmp", + path.file_name().unwrap_or_default().to_string_lossy() + )); + { + let mut file = fs::File::create(&temporary)?; + file.write_all(contents)?; + file.set_permissions(fs::Permissions::from_mode(0o644))?; + file.sync_all()?; + } + fs::rename(&temporary, path)?; + Ok(true) +} + +fn remove_if_present(path: &Path) -> io::Result<()> { + match fs::remove_file(path) { + Err(error) if error.kind() != io::ErrorKind::NotFound => Err(error), + _ => Ok(()), + } +} + +/// The path the registrations name: the stable command, never a versioned install +/// directory. The platform installer keeps each version under +/// `/opt/solstone/desktop//bin/` and publishes `/bin/` as the +/// stable pointer; a package installs `/usr/bin/solstone-linux` directly. +pub fn stable_binary(current_exe: &Path) -> PathBuf { + let components: Vec<_> = current_exe.components().collect(); + let name = current_exe.file_name(); + if components.len() >= 6 { + let tail = &components[components.len() - 6..]; + let tail: Vec<_> = tail.iter().map(|part| part.as_os_str()).collect(); + if tail[0] == "opt" && tail[1] == "solstone" && tail[4] == "bin" { + let prefix: PathBuf = components[..components.len() - 6].iter().collect(); + if let Some(name) = name { + return prefix.join("bin").join(name); + } + } + } + current_exe.to_path_buf() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn production_builds_write_production_registrations_only() { + let temp = tempfile::tempdir().unwrap(); + let binary = Path::new("/usr/bin/solstone-linux"); + assert_eq!(write_all(temp.path(), binary, false).unwrap(), 4); + let chrome = temp + .path() + .join(".config/google-chrome/NativeMessagingHosts/app.solstone.browser.json"); + let manifest: serde_json::Value = + serde_json::from_slice(&fs::read(&chrome).unwrap()).unwrap(); + assert_eq!(manifest["path"], "/usr/bin/solstone-linux"); + assert_eq!( + manifest["allowed_origins"], + serde_json::json!(["chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim/"]) + ); + let firefox: serde_json::Value = serde_json::from_slice( + &fs::read( + temp.path() + .join(".mozilla/native-messaging-hosts/app.solstone.browser.json"), + ) + .unwrap(), + ) + .unwrap(); + assert_eq!( + firefox["allowed_extensions"], + serde_json::json!(["browser@solstone.app"]) + ); + assert!( + temp.path() + .join(".config/chromium/NativeMessagingHosts/app.solstone.browser.json") + .is_file() + ); + assert!( + temp.path() + .join(".config/microsoft-edge/NativeMessagingHosts/app.solstone.browser.json") + .is_file() + ); + assert!( + !temp + .path() + .join(".config/google-chrome/NativeMessagingHosts/app.solstone.browser.dev.json") + .exists() + ); + assert!(check(temp.path(), binary).iter().all(|(_, ok)| *ok)); + // Unchanged content is not rewritten. + assert_eq!(write_all(temp.path(), binary, false).unwrap(), 0); + } + + #[test] + fn a_tampered_registration_is_repaired_and_a_dev_leftover_removed() { + let temp = tempfile::tempdir().unwrap(); + let binary = Path::new("/usr/bin/solstone-linux"); + write_all(temp.path(), binary, true).unwrap(); + let dev = temp + .path() + .join(".mozilla/native-messaging-hosts/app.solstone.browser.dev.json"); + assert!(dev.is_file()); + let firefox = temp + .path() + .join(".mozilla/native-messaging-hosts/app.solstone.browser.json"); + fs::write(&firefox, b"{}").unwrap(); + assert!(!check(temp.path(), binary).iter().all(|(_, ok)| *ok)); + assert_eq!(write_all(temp.path(), binary, false).unwrap(), 1); + assert!(!dev.exists()); + assert!(check(temp.path(), binary).iter().all(|(_, ok)| *ok)); + remove_all(temp.path()).unwrap(); + assert!(!firefox.exists()); + } + + #[test] + fn the_registered_path_is_the_stable_command() { + assert_eq!( + stable_binary(Path::new( + "/home/o/.local/opt/solstone/desktop/2.0.12-0123456789ab/bin/solstone-linux" + )), + PathBuf::from("/home/o/.local/bin/solstone-linux") + ); + assert_eq!( + stable_binary(Path::new("/usr/bin/solstone-linux")), + PathBuf::from("/usr/bin/solstone-linux") + ); + assert_eq!( + stable_binary(Path::new("/home/o/.local/bin/solstone-linux")), + PathBuf::from("/home/o/.local/bin/solstone-linux") + ); + } +} diff --git a/crates/solstone-linux/src/browser/server.rs b/crates/solstone-linux/src/browser/server.rs new file mode 100644 index 0000000..80d5c54 --- /dev/null +++ b/crates/solstone-linux/src/browser/server.rs @@ -0,0 +1,746 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +//! The running app's side of the browser path: a same-user unix socket that host-mode +//! processes connect to, one session per connected browser. +//! +//! The trust boundary is the OS user (any process of the same user could launch the +//! host itself), so the controls are the endpoint's ownership and mode and the peer +//! uid, and nothing more. + +use super::custody::{Custody, Facts, Layout, Outcome}; +use crate::observer::StateSnapshot; +use chrono::Local; +use native_browser_frame::{ + Assembler, CONTROL_MAX, Chunk, DecodeOutcome, Direction, FRESHNESS_MS_MAX, HANDSHAKE_MS_BUDGET, + STATE_RENEWAL_MS_INTERVAL, Step, build_reply, decode, encode, +}; +use serde::Serialize; +use serde_json::{Value, json}; +use std::{ + fs, io, + os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt}, + path::{Path, PathBuf}, + sync::{Arc, Mutex}, + time::Duration, +}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{UnixListener, UnixStream}, + sync::{broadcast, watch}, + task::JoinHandle, + time::{Instant, timeout}, +}; + +const LOCAL_HELLO_MAX: usize = 512; +const MAX_SESSIONS: usize = 16; + +#[derive(Clone, Debug)] +enum Event { + Publish, + Boundary { + generation: String, + period_id: String, + }, + Bye(&'static str), +} + +#[derive(Clone, Debug, Serialize)] +struct Connected { + brand: String, + since_ms: i64, + #[serde(skip)] + id: u64, +} + +struct Shared { + custody: Mutex, + /// The latest facts, refreshed after every change to custody, so a state renewal + /// never waits behind a disk write. + view: Mutex, + active: std::sync::atomic::AtomicUsize, + layout: Layout, + events: broadcast::Sender, + paused: watch::Receiver, + connected: Mutex>, + next_id: Mutex, + dev_enabled: bool, + uid: u32, + on_period_finished: Arc, +} + +impl Shared { + fn paused(&self) -> bool { + self.paused.borrow().paused + } + + fn facts(&self) -> Facts { + let mut facts = self.view.lock().unwrap_or_else(|e| e.into_inner()).clone(); + if self.paused() && facts.generation.is_some() { + facts.capture = "paused"; + } + facts + } + + /// Run `change` on custody off the async workers, then refresh the view. + async fn with_custody( + self: &Arc, + change: impl FnOnce(&mut Custody) -> T + Send + 'static, + ) -> io::Result { + let shared = Arc::clone(self); + tokio::task::spawn_blocking(move || { + let mut custody = shared.custody.lock().unwrap_or_else(|e| e.into_inner()); + let result = change(&mut custody); + *shared.view.lock().unwrap_or_else(|e| e.into_inner()) = custody.facts(false); + result + }) + .await + .map_err(io::Error::other) + } + + fn write_status(&self) { + let facts = self.facts(); + let connected = self + .connected + .lock() + .unwrap_or_else(|e| e.into_inner()) + .clone(); + let status = super::status::Running { + updated_at_ms: Local::now().timestamp_millis(), + capture: facts.capture.to_owned(), + delivery: facts.delivery.to_owned(), + failure: facts.failure.map(str::to_owned), + held_bytes: facts.held_bytes, + full: facts.full, + browsers: connected.iter().map(|c| c.brand.clone()).collect(), + }; + if let Err(error) = super::status::write_running(&self.layout, &status) { + tracing::debug!(%error, "Could not write browser status"); + } + } +} + +pub struct ServerHandle { + shared: Arc, + endpoint: PathBuf, + tasks: Vec>, +} + +impl ServerHandle { + /// Tell every connected browser the app is going away, then close the endpoint. + pub async fn shutdown(self) { + let _ = self.shared.events.send(Event::Bye("shutdown")); + tokio::time::sleep(Duration::from_millis(200)).await; + for task in &self.tasks { + task.abort(); + } + let _ = fs::remove_file(&self.endpoint); + super::status::clear_running(&self.shared.layout); + } +} + +pub struct ServerConfig { + pub base_dir: PathBuf, + pub endpoint: PathBuf, + pub journal: Option, + pub dev_enabled: bool, + pub paused: watch::Receiver, + pub on_period_finished: Arc, +} + +/// Open custody and publish the endpoint. Must be called inside a tokio runtime. +pub fn start(config: ServerConfig) -> io::Result { + let layout = Layout::new(&config.base_dir); + let custody = Custody::open(layout.clone(), config.journal.as_deref(), Local::now())?; + let uid = rustix::process::geteuid().as_raw(); + let listener = bind(&config.endpoint, uid)?; + let (events, _) = broadcast::channel(64); + let view = custody.facts(false); + let shared = Arc::new(Shared { + view: Mutex::new(view), + active: std::sync::atomic::AtomicUsize::new(0), + custody: Mutex::new(custody), + layout, + events, + paused: config.paused, + connected: Mutex::new(Vec::new()), + next_id: Mutex::new(0), + dev_enabled: config.dev_enabled, + uid, + on_period_finished: config.on_period_finished, + }); + shared.write_status(); + let tasks = vec![ + tokio::spawn(accept_loop(Arc::clone(&shared), listener)), + tokio::spawn(clock_loop(Arc::clone(&shared))), + tokio::spawn(pause_loop(Arc::clone(&shared))), + ]; + Ok(ServerHandle { + shared, + endpoint: config.endpoint, + tasks, + }) +} + +/// Publish the endpoint in a directory only this user can enter. A file already at the +/// endpoint path is never connected to: the app holds the single-instance lock, so +/// anything there is stale. +fn bind(endpoint: &Path, uid: u32) -> io::Result { + let directory = endpoint + .parent() + .ok_or_else(|| io::Error::other("browser endpoint has no directory"))?; + match fs::DirBuilder::new().mode(0o700).create(directory) { + Ok(()) => {} + Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {} + Err(error) => return Err(error), + } + let meta = fs::symlink_metadata(directory)?; + if !meta.is_dir() || meta.uid() != uid { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "browser endpoint directory is not this user's", + )); + } + if meta.mode() & 0o777 != 0o700 { + fs::set_permissions(directory, fs::Permissions::from_mode(0o700))?; + } + // Shorter than the endpoint's own name, so it always fits in sun_path. + let staging = directory.join(format!(".bh{}", std::process::id())); + let _ = fs::remove_file(&staging); + let listener = std::os::unix::net::UnixListener::bind(&staging)?; + fs::set_permissions(&staging, fs::Permissions::from_mode(0o600))?; + fs::rename(&staging, endpoint)?; + listener.set_nonblocking(true)?; + UnixListener::from_std(listener) +} + +async fn accept_loop(shared: Arc, listener: UnixListener) { + loop { + let Ok((stream, _)) = listener.accept().await else { + tokio::time::sleep(Duration::from_millis(100)).await; + continue; + }; + if stream + .peer_cred() + .map_or(true, |credentials| credentials.uid() != shared.uid) + { + continue; + } + use std::sync::atomic::Ordering; + if shared.active.fetch_add(1, Ordering::AcqRel) >= MAX_SESSIONS { + shared.active.fetch_sub(1, Ordering::AcqRel); + continue; + } + let worker = Arc::clone(&shared); + tokio::spawn(async move { + session(Arc::clone(&worker), stream).await; + worker.active.fetch_sub(1, Ordering::AcqRel); + }); + } +} + +async fn clock_loop(shared: Arc) { + let mut ticks = tokio::time::interval(Duration::from_secs(1)); + let mut since_status = 0_u32; + loop { + ticks.tick().await; + since_status += 1; + let refresh = since_status >= 5; + if refresh { + since_status = 0; + } + let Ok(rotated) = shared + .with_custody(move |custody| { + let new_period = custody.tick(Local::now()); + if refresh { + custody.refresh_held(); + } + new_period.zip(custody.generation().map(str::to_owned)) + }) + .await + else { + continue; + }; + if refresh { + let status = Arc::clone(&shared); + let _ = tokio::task::spawn_blocking(move || status.write_status()).await; + } + if let Some((period_id, generation)) = rotated { + let _ = shared.events.send(Event::Boundary { + generation, + period_id, + }); + (shared.on_period_finished)(); + } + } +} + +async fn pause_loop(shared: Arc) { + let mut paused = shared.paused.clone(); + let mut last = paused.borrow().paused; + while paused.changed().await.is_ok() { + let now = paused.borrow().paused; + if now != last { + last = now; + let _ = shared.events.send(Event::Publish); + shared.write_status(); + } + } +} + +struct Session { + stream: UnixStream, + assembler: Assembler, + pending: std::collections::VecDeque>, +} + +impl Session { + /// Read the next whole frame, or `None` at end of input or on a framing error. + async fn next(&mut self) -> Option> { + let mut buffer = vec![0_u8; 64 * 1024]; + loop { + if let Some(frame) = self.pending.pop_front() { + return Some(frame); + } + let count = self.stream.read(&mut buffer).await.ok()?; + let chunk = if count == 0 { + Chunk::Eof + } else { + Chunk::Data(&buffer[..count]) + }; + for step in self.assembler.feed(chunk).ok()? { + match step { + Step::Message(body) => self.pending.push_back(body), + Step::CleanEof => return None, + Step::EmptyRead | Step::NeedMore => {} + } + } + } + } + + async fn send(&mut self, message: &Value) -> io::Result<()> { + let body = encode(message).map_err(io::Error::other)?; + // Never put a message on the wire the extension's own codec would refuse. + if !matches!( + decode(&body, Direction::HostToExtension), + DecodeOutcome::Accept(_) + ) { + return Err(io::Error::other("refusing to send an invalid message")); + } + let mut frame = Vec::with_capacity(4 + body.len()); + frame.extend_from_slice(&(body.len() as u32).to_ne_bytes()); + frame.extend_from_slice(&body); + timeout( + Duration::from_millis(HANDSHAKE_MS_BUDGET), + self.stream.write_all(&frame), + ) + .await + .map_err(|_| io::Error::from(io::ErrorKind::TimedOut))? + } +} + +pub fn state_message(kind: &str, facts: &Facts) -> Value { + let mut message = json!({ + "type": kind, + "capture": facts.capture, + "delivery": facts.delivery, + "freshness_ms": FRESHNESS_MS_MAX, + "destination_generation": facts.generation, + "period_id": facts.period_id, + "custody": {"full": facts.full, "stale": false}, + }); + if let Some(failure) = facts.failure { + message["failure"] = json!(failure); + } + message +} + +struct Hello { + brand: String, + inst: String, +} + +/// The handshake: the host's `local_hello`, then the extension's `hello`, both within +/// one budget. Anything unexpected closes the session without a reply. +async fn handshake(shared: &Shared, session: &mut Session) -> Option { + let deadline = Instant::now() + Duration::from_millis(HANDSHAKE_MS_BUDGET); + let local = tokio::time::timeout_at(deadline, session.next()) + .await + .ok()??; + if local.len() > LOCAL_HELLO_MAX { + return None; + } + let local: Value = serde_json::from_slice(&local).ok()?; + if local.get("type")?.as_str()? != "local_hello" { + return None; + } + let launched_firefox = match local.get("brand")?.as_str()? { + "chromium" => false, + "firefox" => true, + _ => return None, + }; + match local.get("mode")?.as_str()? { + "production" => {} + "development" if shared.dev_enabled => {} + _ => return None, + } + let hello = tokio::time::timeout_at(deadline, session.next()) + .await + .ok()??; + if hello.len() > CONTROL_MAX { + return None; + } + match decode(&hello, Direction::ExtensionToHost) { + DecodeOutcome::Accept(value) if value["type"] == "hello" => { + let brand = value["brand"].as_str()?.to_owned(); + if (brand == "firefox") != launched_firefox { + return None; + } + Some(Hello { + brand, + inst: value["inst"].as_str()?.to_owned(), + }) + } + DecodeOutcome::Unsupported { + protocol, behind, .. + } => { + let _ = session + .send(&json!({"type": "unsupported", "protocol": protocol, "behind": behind})) + .await; + None + } + _ => None, + } +} + +async fn session(shared: Arc, stream: UnixStream) { + let mut events = shared.events.subscribe(); + // Handshake frames are small; only batches may be large. + let mut session = Session { + stream, + assembler: Assembler::new(Direction::HostToExtension), + pending: Default::default(), + }; + let Some(hello) = handshake(&shared, &mut session).await else { + return; + }; + if session.assembler.retained() != 0 { + return; + } + session.assembler = Assembler::new(Direction::ExtensionToHost); + let id = { + let mut next = shared.next_id.lock().unwrap_or_else(|e| e.into_inner()); + *next += 1; + *next + }; + shared + .connected + .lock() + .unwrap_or_else(|e| e.into_inner()) + .push(Connected { + brand: hello.brand.clone(), + since_ms: Local::now().timestamp_millis(), + id, + }); + shared.write_status(); + let _ = run_session(&shared, &mut session, &hello, &mut events).await; + shared + .connected + .lock() + .unwrap_or_else(|e| e.into_inner()) + .retain(|connected| connected.id != id); + shared.write_status(); +} + +async fn run_session( + shared: &Arc, + session: &mut Session, + hello: &Hello, + events: &mut broadcast::Receiver, +) -> io::Result<()> { + let facts = shared.facts(); + let generation = facts.generation.clone(); + session.send(&state_message("hello_ack", &facts)).await?; + session.send(&state_message("state", &facts)).await?; + let mut renewal = tokio::time::interval(Duration::from_millis(STATE_RENEWAL_MS_INTERVAL)); + renewal.tick().await; + loop { + tokio::select! { + frame = session.next() => { + let Some(frame) = frame else { return Ok(()) }; + let inst = hello.inst.clone(); + let handled = shared + .with_custody(move |custody| { + let DecodeOutcome::Accept(batch) = decode(&frame, Direction::ExtensionToHost) + else { + return None; + }; + if batch["type"] != "batch" || batch["inst"].as_str() != Some(inst.as_str()) { + return None; + } + let ids = json!({ + "destination_generation": batch["destination_generation"], + "inst": batch["inst"], + "batch_id": batch["batch_id"], + }); + let (outcome, rotated) = custody.accept(&batch, Local::now()); + Some((ids, outcome, rotated)) + }) + .await?; + // Anything but a valid batch from this browser ends the session. + let Some((batch, outcome, rotated)) = handled else { + return Ok(()); + }; + if let Some(period_id) = rotated + && let Some(generation) = &generation + { + let _ = shared.events.send(Event::Boundary { + generation: generation.clone(), + period_id, + }); + (shared.on_period_finished)(); + } + session.send(&reply(&batch, outcome)?).await?; + session.send(&state_message("state", &shared.facts())).await?; + } + _ = renewal.tick() => { + session.send(&state_message("state", &shared.facts())).await?; + } + event = events.recv() => match event { + Ok(Event::Publish) | Err(broadcast::error::RecvError::Lagged(_)) => { + session.send(&state_message("state", &shared.facts())).await?; + } + Ok(Event::Boundary { generation: changed, period_id }) => { + if generation.as_deref() == Some(changed.as_str()) { + session + .send(&json!({"type": "boundary", "destination_generation": changed, "period_id": period_id})) + .await?; + } + session.send(&state_message("state", &shared.facts())).await?; + } + Ok(Event::Bye(reason)) => { + let _ = session.send(&json!({"type": "bye", "reason": reason})).await; + return Ok(()); + } + Err(broadcast::error::RecvError::Closed) => return Ok(()), + }, + } + } +} + +fn reply(ids: &Value, outcome: Outcome) -> io::Result { + let mut receipt = ids.clone(); + match outcome { + Outcome::Accepted { period_id } => { + receipt["result"] = json!("accepted"); + receipt["period_id"] = json!(period_id); + } + Outcome::Duplicate { period_id } => { + receipt["result"] = json!("duplicate"); + receipt["period_id"] = json!(period_id); + } + Outcome::Rejected { reason } => { + receipt["result"] = json!("rejected"); + receipt["reason"] = json!(reason); + } + } + build_reply(&receipt).map_err(io::Error::other) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::browser::host::write_frame; + use crate::observer::Mode; + use tokio::io::AsyncReadExt; + + fn snapshot(paused: bool) -> StateSnapshot { + StateSnapshot { + mode: Mode::Idle, + paused, + segment_open: false, + captures_today: 0, + total_size_mb: 0, + pause_until: None, + segment_start_mono: None, + process_start_mono: 0.0, + } + } + + struct Rig { + _temp: tempfile::TempDir, + endpoint: PathBuf, + pause: watch::Sender, + handle: ServerHandle, + } + + fn rig(journal: Option<&str>, dev_enabled: bool) -> Rig { + let temp = tempfile::tempdir().unwrap(); + let endpoint = temp.path().join("run/solstone-linux/browser-host.sock"); + fs::create_dir_all(endpoint.parent().unwrap().parent().unwrap()).unwrap(); + let (pause, paused) = watch::channel(snapshot(false)); + let handle = start(ServerConfig { + base_dir: temp.path().join("data"), + endpoint: endpoint.clone(), + journal: journal.map(str::to_owned), + dev_enabled, + paused, + on_period_finished: Arc::new(|| {}), + }) + .unwrap(); + Rig { + _temp: temp, + endpoint, + pause, + handle, + } + } + + async fn frame(stream: &mut UnixStream, body: &[u8]) { + let mut out = Vec::new(); + write_frame(&mut out, body).unwrap(); + stream.write_all(&out).await.unwrap(); + } + + async fn read(stream: &mut UnixStream) -> Value { + let mut length = [0_u8; 4]; + timeout(Duration::from_secs(10), stream.read_exact(&mut length)) + .await + .unwrap() + .unwrap(); + let mut body = vec![0_u8; u32::from_ne_bytes(length) as usize]; + stream.read_exact(&mut body).await.unwrap(); + serde_json::from_slice(&body).unwrap() + } + + async fn read_type(stream: &mut UnixStream, kind: &str) -> Value { + loop { + let message = read(stream).await; + if message["type"] == kind { + return message; + } + } + } + + async fn connect(rig: &Rig, brand: &str, mode: &str, hello_brand: &str) -> UnixStream { + let mut stream = UnixStream::connect(&rig.endpoint).await.unwrap(); + frame( + &mut stream, + format!(r#"{{"brand":"{brand}","mode":"{mode}","type":"local_hello"}}"#).as_bytes(), + ) + .await; + frame( + &mut stream, + format!(r#"{{"type":"hello","protocol":1,"version":"0.2.0","brand":"{hello_brand}","inst":"inst-a"}}"#) + .as_bytes(), + ) + .await; + stream + } + + #[tokio::test(flavor = "multi_thread")] + async fn a_paired_app_permits_capture_accepts_batches_and_answers_replays() { + let rig = rig(Some("journal-a"), false); + let meta = fs::metadata(rig.endpoint.parent().unwrap()).unwrap(); + assert_eq!(meta.mode() & 0o777, 0o700); + let mut stream = connect(&rig, "chromium", "production", "edge").await; + let ack = read(&mut stream).await; + assert_eq!(ack["type"], "hello_ack"); + assert_eq!(ack["capture"], "permitted"); + assert_eq!(ack["freshness_ms"], 15000); + let generation = ack["destination_generation"].as_str().unwrap().to_owned(); + assert_eq!(read(&mut stream).await["type"], "state"); + let batch = json!({ + "type": "batch", "destination_generation": generation, "inst": "inst-a", + "batch_id": "0123456789abcdef0123456789abcdef", + "queued_at_ms": Local::now().timestamp_millis(), + "records": [{"t": "segment_start", "ts": 1, "ctx": "c1", "inst": "inst-a", + "blocks": [{"id": "b", "text": "marker"}]}], + }); + let bytes = encode(&batch).unwrap(); + frame(&mut stream, &bytes).await; + let first = read_type(&mut stream, "accepted").await; + assert_eq!(first["result"], "accepted"); + frame(&mut stream, &bytes).await; + let replay = read_type(&mut stream, "accepted").await; + assert_eq!(replay["result"], "duplicate"); + assert_eq!(replay["period_id"], first["period_id"]); + + rig.pause.send_replace(snapshot(true)); + let paused = read_type(&mut stream, "state").await; + let paused = if paused["capture"] == "paused" { + paused + } else { + read_type(&mut stream, "state").await + }; + assert_eq!(paused["capture"], "paused"); + rig.pause.send_replace(snapshot(false)); + + rig.handle.shutdown().await; + assert_eq!(read_type(&mut stream, "bye").await["reason"], "shutdown"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn an_unpaired_app_says_so() { + let rig = rig(None, false); + let mut stream = connect(&rig, "firefox", "production", "firefox").await; + let ack = read(&mut stream).await; + assert_eq!(ack["capture"], "not_paired"); + assert_eq!(ack["destination_generation"], Value::Null); + rig.handle.shutdown().await; + } + + #[tokio::test(flavor = "multi_thread")] + async fn a_development_launch_is_closed_unless_the_build_admits_it() { + let rig = rig(Some("journal-a"), false); + let mut stream = connect(&rig, "chromium", "development", "chrome").await; + let mut byte = [0_u8; 1]; + assert_eq!( + timeout(Duration::from_secs(10), stream.read(&mut byte)) + .await + .unwrap() + .unwrap(), + 0 + ); + rig.handle.shutdown().await; + let dev = super::tests::rig(Some("journal-a"), true); + let mut stream = connect(&dev, "chromium", "development", "chrome").await; + assert_eq!(read(&mut stream).await["type"], "hello_ack"); + dev.handle.shutdown().await; + } + + #[tokio::test(flavor = "multi_thread")] + async fn a_brand_that_contradicts_the_launch_is_closed() { + let rig = rig(Some("journal-a"), false); + let mut stream = connect(&rig, "chromium", "production", "firefox").await; + let mut byte = [0_u8; 1]; + assert_eq!( + timeout(Duration::from_secs(10), stream.read(&mut byte)) + .await + .unwrap() + .unwrap(), + 0 + ); + rig.handle.shutdown().await; + } + + #[tokio::test(flavor = "multi_thread")] + async fn a_newer_protocol_is_told_the_app_is_behind() { + let rig = rig(Some("journal-a"), false); + let mut stream = UnixStream::connect(&rig.endpoint).await.unwrap(); + frame( + &mut stream, + br#"{"brand":"firefox","mode":"production","type":"local_hello"}"#, + ) + .await; + frame( + &mut stream, + br#"{"type":"hello","protocol":2,"version":"9.0.0","brand":"firefox","inst":"i"}"#, + ) + .await; + let message = read(&mut stream).await; + assert_eq!(message["type"], "unsupported"); + assert_eq!(message["behind"], "app"); + rig.handle.shutdown().await; + } +} diff --git a/crates/solstone-linux/src/browser/status.rs b/crates/solstone-linux/src/browser/status.rs new file mode 100644 index 0000000..d9078b4 --- /dev/null +++ b/crates/solstone-linux/src/browser/status.rs @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +//! What `status` and `doctor` say about the browser path. The running app writes a +//! small status file; everything else is read from disk. + +use super::custody::{self, Layout}; +use serde::{Deserialize, Serialize}; +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +const RUNNING_FILE: &str = "status.json"; +/// The running app rewrites its status every five seconds. +const RUNNING_STALE_MS: i64 = 30_000; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Running { + pub updated_at_ms: i64, + pub capture: String, + pub delivery: String, + pub failure: Option, + pub held_bytes: u64, + pub full: bool, + pub browsers: Vec, +} + +pub fn write_running(layout: &Layout, status: &Running) -> io::Result<()> { + let path = layout.root.join(RUNNING_FILE); + let temporary = layout.root.join(format!(".{RUNNING_FILE}.tmp")); + fs::write( + &temporary, + serde_json::to_vec(status).map_err(io::Error::other)?, + )?; + fs::rename(temporary, path) +} + +pub fn clear_running(layout: &Layout) { + let _ = fs::remove_file(layout.root.join(RUNNING_FILE)); +} + +fn read_running(layout: &Layout, now_ms: i64) -> Option { + let status: Running = + serde_json::from_slice(&fs::read(layout.root.join(RUNNING_FILE)).ok()?).ok()?; + (now_ms - status.updated_at_ms < RUNNING_STALE_MS).then_some(status) +} + +fn size(bytes: u64) -> String { + if bytes < 1024 * 1024 { + format!("{:.0} KB", (bytes as f64 / 1024.0).ceil()) + } else { + format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0)) + } +} + +/// Lines for `solstone-linux status`. +pub fn status_lines(base_dir: &Path, now_ms: i64) -> Vec { + let layout = Layout::new(base_dir); + let mut lines = Vec::new(); + match read_running(&layout, now_ms) { + Some(running) => { + let mut browsers = running.browsers.clone(); + browsers.sort(); + browsers.dedup(); + lines.push(if browsers.is_empty() { + "Browser: no browser connected".to_owned() + } else { + format!("Browser: {} connected", browsers.join(", ")) + }); + if running.full { + lines.push( + " browser pages are full, so no new pages are taken in until some go into your journal" + .to_owned(), + ); + } else if running.capture == "paused" { + lines.push(" paused, so no new pages are taken in".to_owned()); + } + if running.held_bytes > 0 { + lines.push(format!( + " {} of browser pages on this computer, waiting to go into your journal", + size(running.held_bytes) + )); + } + } + None => { + let held = custody::held_bytes(&layout); + lines.push("Browser: the solstone app is not running".to_owned()); + if held > 0 { + lines.push(format!( + " {} of browser pages on this computer, waiting to go into your journal", + size(held) + )); + } + } + } + lines.extend(retired_lines(&layout)); + lines +} + +fn retired_lines(layout: &Layout) -> Vec { + let retired = custody::retired_summary(layout); + if retired.periods == 0 { + return Vec::new(); + } + vec![ + format!( + " {} of browser pages were kept for a journal this computer was paired with before.", + size(retired.bytes) + ), + " they won't go into any journal, and they stay on this computer until you discard them:" + .to_owned(), + " solstone-linux discard-browser-pages".to_owned(), + ] +} + +/// What a browser install can and cannot do, for `doctor`. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct BrowserInstalls { + pub chromium_snap: bool, + pub firefox_snap: bool, + pub flatpak_chromium_family: Vec, +} + +pub fn detect_installs(snap_root: &Path, flatpak_roots: &[PathBuf]) -> BrowserInstalls { + let flatpak_ids = [ + "com.google.Chrome", + "org.chromium.Chromium", + "com.microsoft.Edge", + "com.brave.Browser", + ]; + let mut flatpak_chromium_family = Vec::new(); + for id in flatpak_ids { + if flatpak_roots.iter().any(|root| root.join(id).is_dir()) { + flatpak_chromium_family.push(id.to_owned()); + } + } + BrowserInstalls { + chromium_snap: snap_root.join("chromium").is_dir(), + firefox_snap: snap_root.join("firefox").is_dir(), + flatpak_chromium_family, + } +} + +/// Lines for `solstone-linux doctor`: whether each registration is in place, and the +/// browser installs that cannot reach the app by construction. +pub fn doctor_lines(home: &Path, binary: &Path, installs: &BrowserInstalls) -> Vec { + let mut lines = Vec::new(); + let checks = super::registration::check(home, binary); + let missing: Vec<_> = checks + .iter() + .filter(|(_, ok)| !ok) + .map(|(browser, _)| *browser) + .collect(); + if missing.is_empty() { + lines.push( + "ok browser registration chrome, chromium, edge and firefox".to_owned(), + ); + } else { + lines.push(format!( + "warn browser registration missing or out of date for {}; start the solstone app to write them again", + missing.join(", ") + )); + } + if installs.firefox_snap { + lines.push( + "ok firefox (snap) asks you once to let it reach the solstone app; if you said no, change it in Settings, Apps, Firefox" + .to_owned(), + ); + } + if installs.chromium_snap { + lines.push( + "warn chromium (snap) can't reach the solstone app; a snap of chromium has no way to start it. use chrome, edge or firefox" + .to_owned(), + ); + } + for id in &installs.flatpak_chromium_family { + lines.push(format!( + "warn {id:<28} can't reach the solstone app from inside a flatpak; use a chrome, edge or firefox package" + )); + } + lines +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn status_reads_the_running_app_and_falls_back_to_disk() { + let temp = tempfile::tempdir().unwrap(); + let layout = Layout::new(temp.path()); + fs::create_dir_all(&layout.root).unwrap(); + assert_eq!( + status_lines(temp.path(), 1_000_000), + vec!["Browser: the solstone app is not running".to_owned()] + ); + write_running( + &layout, + &Running { + updated_at_ms: 1_000_000, + capture: "permitted".into(), + delivery: "kept_locally".into(), + failure: None, + held_bytes: 2048, + full: false, + browsers: vec!["firefox".into(), "chrome".into(), "chrome".into()], + }, + ) + .unwrap(); + assert_eq!( + status_lines(temp.path(), 1_005_000), + vec![ + "Browser: chrome, firefox connected".to_owned(), + " 2 KB of browser pages on this computer, waiting to go into your journal" + .to_owned(), + ] + ); + // A status file the app stopped refreshing is not a running app. + assert_eq!( + status_lines(temp.path(), 1_000_000 + RUNNING_STALE_MS)[0], + "Browser: the solstone app is not running" + ); + } + + #[test] + fn doctor_names_installs_that_cannot_reach_the_app() { + let temp = tempfile::tempdir().unwrap(); + let snap = temp.path().join("snap"); + fs::create_dir_all(snap.join("chromium")).unwrap(); + fs::create_dir_all(snap.join("firefox")).unwrap(); + let flatpak = temp.path().join("flatpak"); + fs::create_dir_all(flatpak.join("com.brave.Browser")).unwrap(); + let installs = detect_installs(&snap, &[flatpak]); + assert!(installs.chromium_snap && installs.firefox_snap); + assert_eq!(installs.flatpak_chromium_family, vec!["com.brave.Browser"]); + let home = temp.path().join("home"); + let binary = Path::new("/usr/bin/solstone-linux"); + let lines = doctor_lines(&home, binary, &installs); + assert!(lines[0].starts_with("warn browser registration")); + assert!(lines.iter().any(|line| line.contains("chromium (snap)"))); + assert!(lines.iter().any(|line| line.contains("com.brave.Browser"))); + crate::browser::registration::write_all(&home, binary, false).unwrap(); + assert!(doctor_lines(&home, binary, &BrowserInstalls::default())[0].starts_with("ok")); + } +} diff --git a/crates/solstone-linux/src/cli.rs b/crates/solstone-linux/src/cli.rs index 088db90..cfcb6e2 100644 --- a/crates/solstone-linux/src/cli.rs +++ b/crates/solstone-linux/src/cli.rs @@ -89,6 +89,12 @@ enum Commands { }, #[command(about = "resume intake")] Resume, + #[cfg(feature = "browser")] + #[command( + name = "discard-browser-pages", + about = "discard browser pages kept for a journal this computer was paired with before" + )] + DiscardBrowserPages, } struct SystemRunner; @@ -226,6 +232,17 @@ fn setup_logging(verbose: bool) { } pub fn run() -> i32 { + // A browser's native-messaging launch is recognised from argv alone, before any + // other startup work: no logging, no config, and no credential lock. + if crate::browser::ENABLED { + use crate::browser::argv::{Recognition, recognize}; + let arguments: Vec<_> = env::args_os().skip(1).collect(); + match recognize(&arguments, crate::browser::DEV_ENABLED) { + Recognition::Host(invocation) => return crate::browser::host::run(invocation), + Recognition::Refused => return crate::browser::host::refuse(&mut io::stdout()), + Recognition::NotHost => {} + } + } let args = Args::parse(); setup_logging(args.verbose); match effective_command(args.command) { @@ -263,6 +280,10 @@ pub fn run() -> i32 { 1 } }, + #[cfg(feature = "browser")] + Commands::DiscardBrowserPages => { + cmd_discard_browser_pages(ConfigPaths::default(), &mut io::stdout(), &mut io::stderr()) + } Commands::UninstallService => match crate::service::ServicePaths::production() { Ok(paths) => crate::service::uninstall(&paths, &SystemRunner, &mut io::stdout()), Err(error) => { @@ -874,6 +895,36 @@ fn escape_display_version(raw: &str) -> String { clean } +#[cfg_attr(not(feature = "browser"), allow(dead_code))] +fn cmd_discard_browser_pages( + paths: ConfigPaths, + output: &mut dyn Write, + errors: &mut dyn Write, +) -> i32 { + let config = load_config(paths).config; + let layout = crate::browser::custody::Layout::new(&config.base_dir); + match crate::browser::custody::discard_retired(&layout) { + Ok(summary) if summary.periods == 0 => { + let _ = write_line( + output, + "there are no browser pages kept for another journal", + ); + 0 + } + Ok(_) => { + let _ = write_line( + output, + "discarded the browser pages kept for a journal this computer was paired with before", + ); + 0 + } + Err(error) => { + let _ = write_line(errors, format!("could not discard them: {error}")); + 1 + } + } +} + fn cmd_status(paths: ConfigPaths, runner: &dyn Runner, output: &mut dyn Write) -> i32 { let loaded = load_config(paths); let config = loaded.config; @@ -976,6 +1027,18 @@ fn cmd_status(paths: ConfigPaths, runner: &dyn Runner, output: &mut dyn Write) - output, derive_health(&facts, now, config.sync_stale_threshold as f64).cli, )?; + if crate::browser::ENABLED { + let now_ms = i64::try_from( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis(), + ) + .unwrap_or(i64::MAX); + for line in crate::browser::status::status_lines(&config.base_dir, now_ms) { + write_line(output, line)?; + } + } if let Some(systemctl) = runner.which("systemctl") && let Ok(result) = runner.run( &systemctl, @@ -1147,22 +1210,23 @@ mod tests { .get_subcommands() .map(|subcommand| subcommand.get_name()) .collect(); - assert_eq!( - names, - vec![ - "run", - "setup", - "confirm", - "doctor", - "settings", - "install-service", - "uninstall-service", - "status", - "panel-icon", - "pause", - "resume" - ] - ); + let mut expected = vec![ + "run", + "setup", + "confirm", + "doctor", + "settings", + "install-service", + "uninstall-service", + "status", + "panel-icon", + "pause", + "resume", + ]; + if crate::browser::ENABLED { + expected.push("discard-browser-pages"); + } + assert_eq!(names, expected); } // the panel-icon command's own help text is owner-visible. @@ -1819,8 +1883,13 @@ mod tests { cmd_status(paths(&t), &StatusRunner(Some("active\n")), &mut out), 0 ); + let browser = if crate::browser::ENABLED { + "Browser: the solstone app is not running\n" + } else { + "" + }; let expected = format!( - "Config: {}\nJournal link: managed privately\nJournal version: unknown\nStream: test-stream\n\nCache: {}\n 0 segments across 0 day(s), 0.0 MB\nSync: offline; held on this device; will retry\n\nService: active\n", + "Config: {}\nJournal link: managed privately\nJournal version: unknown\nStream: test-stream\n\nCache: {}\n 0 segments across 0 day(s), 0.0 MB\nSync: offline; held on this device; will retry\n{browser}\nService: active\n", config.config_path().display(), config.captures_dir().display() ); diff --git a/crates/solstone-linux/src/doctor.rs b/crates/solstone-linux/src/doctor.rs index 3b9266c..7996d53 100644 --- a/crates/solstone-linux/src/doctor.rs +++ b/crates/solstone-linux/src/doctor.rs @@ -85,6 +85,10 @@ pub trait DoctorChecks { fn panel_icon(&mut self) -> CheckResult; fn sync_health(&mut self) -> CheckResult; fn quarantine(&mut self) -> Option; + /// Browser registration and browser installs that cannot reach the app. + fn browser(&mut self) -> Vec { + Vec::new() + } } pub fn run_doctor(checks: &mut dyn DoctorChecks, output: &mut dyn io::Write) -> i32 { @@ -135,6 +139,9 @@ pub fn run_doctor(checks: &mut dyn DoctorChecks, output: &mut dyn io::Write) -> if let Some(line) = checks.quarantine() { let _ = writeln!(output, "{line}"); } + for line in checks.browser() { + let _ = writeln!(output, "{line}"); + } let _ = writeln!( output, "\ndoctor: 8 checks, {failures} failed, {warnings} warnings" @@ -498,6 +505,29 @@ impl DoctorChecks for RealDoctor<'_> { let health = derive_health(&facts, now, config.sync_stale_threshold as f64); sync_health_result(health) } + fn browser(&mut self) -> Vec { + if !crate::browser::ENABLED { + return Vec::new(); + } + let (Some(home), Ok(executable)) = (std::env::var_os("HOME"), std::env::current_exe()) + else { + return Vec::new(); + }; + let home = std::path::PathBuf::from(home); + let installs = crate::browser::status::detect_installs( + std::path::Path::new("/snap"), + &[ + home.join(".local/share/flatpak/app"), + std::path::PathBuf::from("/var/lib/flatpak/app"), + ], + ); + crate::browser::status::doctor_lines( + &home, + &crate::browser::registration::stable_binary(&executable), + &installs, + ) + } + fn quarantine(&mut self) -> Option { let root = self.config().captures_dir(); let now = SystemTime::now() diff --git a/crates/solstone-linux/src/lib.rs b/crates/solstone-linux/src/lib.rs index 570547d..0094887 100644 --- a/crates/solstone-linux/src/lib.rs +++ b/crates/solstone-linux/src/lib.rs @@ -3,6 +3,7 @@ pub mod activity; pub mod audio; +pub mod browser; pub mod capture_stats; pub mod chunking; pub mod cli; diff --git a/crates/solstone-linux/src/private_link.rs b/crates/solstone-linux/src/private_link.rs index 9f61b96..3f7909d 100644 --- a/crates/solstone-linux/src/private_link.rs +++ b/crates/solstone-linux/src/private_link.rs @@ -2040,17 +2040,20 @@ impl PrivateLinkCapability { .await } - pub(crate) async fn segments_day(&self, day: &str) -> LinkOutcome { + pub(crate) async fn segments_day(&self, day: &str, source: Option<&str>) -> LinkOutcome { if !Self::validate_day(day) { return LinkOutcome::LocalRejected { status: StatusCode::BAD_REQUEST, }; } - let Ok(url) = self.ingest_v3_url(&format!("/segments/{day}")) else { + let Ok(mut url) = self.ingest_v3_url(&format!("/segments/{day}")) else { return LinkOutcome::LocalRejected { status: StatusCode::BAD_REQUEST, }; }; + if let Some(source) = source { + url.query_pairs_mut().append_pair("source", source); + } self.send( self.inner .client @@ -4648,7 +4651,7 @@ pub(crate) mod tests { LinkOutcome::Success { .. } )); assert!(matches!( - capability.segments_day("20260101").await, + capability.segments_day("20260101", None).await, LinkOutcome::Success { .. } )); assert!(matches!(capability.system_status().await, Ok(Some(_)))); @@ -4691,7 +4694,7 @@ pub(crate) mod tests { let capability = session.capability(); for day in ["", "2026010", "202601011", "202601?1", "../20260101"] { assert!(matches!( - capability.segments_day(day).await, + capability.segments_day(day, None).await, LinkOutcome::LocalRejected { status: StatusCode::BAD_REQUEST } @@ -4851,7 +4854,7 @@ pub(crate) mod tests { .await; assert!(response.starts_with(b"HTTP/1.1 400")); assert!(matches!( - session.capability().segments_day("20260101").await, + session.capability().segments_day("20260101", None).await, LinkOutcome::Success { .. } )); assert_eq!(peer.requests().len(), 1); @@ -5089,7 +5092,7 @@ pub(crate) mod tests { assert!(sanitized.get("server_url").is_none()); assert!(sanitized.get("key").is_none()); assert!(matches!( - session.capability().segments_day("20260101").await, + session.capability().segments_day("20260101", None).await, LinkOutcome::Success { .. } )); assert_eq!(peer.accepted_carriers(), 1); @@ -5573,7 +5576,7 @@ pub(crate) mod tests { .unwrap(); let request = tokio::spawn({ let capability = owner.capability(); - async move { capability.segments_day("20260101").await } + async move { capability.segments_day("20260101", None).await } }); peer.wait_for_requests(1).await; assert!(!request.is_finished()); diff --git a/crates/solstone-linux/src/run.rs b/crates/solstone-linux/src/run.rs index c95892f..8920a32 100644 --- a/crates/solstone-linux/src/run.rs +++ b/crates/solstone-linux/src/run.rs @@ -327,6 +327,11 @@ fn run_capture( }; let snapshot = Arc::new(Mutex::new(initial_snapshot.clone())); let (states, tray_receiver) = WatchStateSink::channel(initial_snapshot); + let browser = if crate::browser::ENABLED { + start_browser(&config, tray_receiver.clone(), sync_trigger.clone()) + } else { + None + }; let signal_receiver = tray_receiver.clone(); let (initial_health, initial_progress) = sync_sampler.sample(); let health = Arc::new(Mutex::new(initial_health)); @@ -422,6 +427,9 @@ fn run_capture( if let Err(error) = notifier.stopping() { tracing::warn!(%error, "Failed to notify systemd stopping state"); } + if let Some(browser) = browser { + runtime.block_on(browser.shutdown()); + } linked_shutdown_notify.notify_one(); let (shutdown, sync_shutdown, linked_shutdown) = runtime.block_on(shutdown_in_order( observer, @@ -451,6 +459,57 @@ fn run_capture( .and(linked_shutdown) } +/// Write the browser registrations and open the browser endpoint. Opening custody +/// retires anything held for a journal this app is no longer paired with. A failure +/// here leaves the rest of the app running. +fn start_browser( + config: &Config, + paused: tokio::sync::watch::Receiver, + sync: SyncTrigger, +) -> Option { + use crate::browser::{custody, registration, server}; + match (env::var_os("HOME"), env::current_exe()) { + (Some(home), Ok(executable)) => { + if let Err(error) = registration::write_all( + std::path::Path::new(&home), + ®istration::stable_binary(&executable), + crate::browser::DEV_ENABLED, + ) { + tracing::warn!(%error, "Could not write every browser registration"); + } + } + _ => tracing::warn!("Could not locate the browser registrations"), + } + let journal = match load_credential(&config.config_dir) { + Ok(Some(credential)) => Some(custody::journal_identity( + &credential.instance_id, + &credential.ca_chain_pem, + )), + _ => None, + }; + let endpoint = match crate::browser::production_endpoint_path() { + Ok(endpoint) => endpoint, + Err(error) => { + tracing::warn!(%error, "Browser pages are unavailable"); + return None; + } + }; + match server::start(server::ServerConfig { + base_dir: config.base_dir.clone(), + endpoint, + journal, + dev_enabled: crate::browser::DEV_ENABLED, + paused, + on_period_finished: Arc::new(move || sync.trigger()), + }) { + Ok(handle) => Some(handle), + Err(error) => { + tracing::warn!(%error, "Browser pages are unavailable"); + None + } + } +} + #[allow(clippy::too_many_arguments)] async fn start_linked_owner( upload: Arc, diff --git a/crates/solstone-linux/src/service.rs b/crates/solstone-linux/src/service.rs index 47fd01b..257b97b 100644 --- a/crates/solstone-linux/src/service.rs +++ b/crates/solstone-linux/src/service.rs @@ -151,6 +151,18 @@ pub fn install(paths: &ServicePaths, runner: &dyn Runner, output: &mut dyn io::W let _ = writeln!(output, "Error writing service files: {error}"); return 1; } + if crate::browser::ENABLED + && let Err(error) = crate::browser::registration::write_all( + &paths.home, + &crate::browser::registration::stable_binary(&paths.binary), + crate::browser::DEV_ENABLED, + ) + { + let _ = writeln!( + output, + "Warning: could not write every browser registration: {error}" + ); + } systemctl_nonfatal( runner, &["--user", "daemon-reload"], @@ -201,6 +213,14 @@ pub fn uninstall(paths: &ServicePaths, runner: &dyn Runner, output: &mut dyn io: return 1; } } + if crate::browser::ENABLED + && let Err(error) = crate::browser::registration::remove_all(&paths.home) + { + let _ = writeln!( + output, + "Warning: could not remove every browser registration: {error}" + ); + } systemctl_nonfatal( runner, &["--user", "daemon-reload"], diff --git a/crates/solstone-linux/src/sync.rs b/crates/solstone-linux/src/sync.rs index 54ce3e0..1237ffc 100644 --- a/crates/solstone-linux/src/sync.rs +++ b/crates/solstone-linux/src/sync.rs @@ -496,6 +496,20 @@ impl SyncWorker { self.is_running() || self.draining_shutdown } + /// The generation whose browser periods may go to the paired journal. Periods kept + /// for any other journal are never delivered. + fn browser_generation(&self) -> Option { + let credential = crate::private_link::load_credential(&self.config.config_dir).ok()??; + let journal = crate::browser::custody::journal_identity( + &credential.instance_id, + &credential.ca_chain_pem, + ); + crate::browser::custody::deliverable_generation( + &crate::browser::custody::Layout::new(&self.config.base_dir), + Some(&journal), + ) + } + fn current_identity_and_pairing(&self) -> (Option, Option) { self.client .capability() @@ -595,7 +609,10 @@ impl SyncWorker { ensure_cutover(&self.config.state_dir(), &self.config.captures_dir()); let (current_identity, current_pairing) = self.current_identity_and_pairing(); let cutover_segments = load_cutover_segments(&self.config.state_dir()); - let segments_by_day = collect_segments(&self.config.captures_dir()); + let segments_by_day = deliverable_segments( + collect_segments(&self.config.captures_dir()), + self.browser_generation().as_deref(), + ); let mut days: Vec = segments_by_day.keys().cloned().collect(); days.sort_by(|a, b| b.cmp(a)); @@ -696,9 +713,17 @@ impl SyncWorker { // Phase 2: Targeted single GET /app/devices/ingest/segments/{day} for legacy due if !pass_stopped && !self.circuit_open && self.is_active() { let mut custody_days = HashSet::new(); - for (day, segments) in &segments_by_day { + for ((day, source), segments) in segments_by_day.iter().flat_map(|(day, segments)| { + [None, Some(crate::browser::SOURCE)].map(|source| { + let class: Vec<&PathBuf> = segments + .iter() + .filter(|segment| segment_source(segment) == source) + .collect(); + ((day, source), class) + }) + }) { let enters_phase2 = segments.iter().any(|s| { - if uploaded_in_phase1.contains(s) { + if uploaded_in_phase1.contains(*s) { return false; } let ack = read_ack(s); @@ -739,20 +764,26 @@ impl SyncWorker { false }); if enters_phase2 { - custody_days.insert(day.clone()); + custody_days.insert((day.clone(), source)); } } let mut sorted_custody_days: Vec<_> = custody_days.into_iter().collect(); sorted_custody_days.sort_by(|a, b| b.cmp(a)); - for day in sorted_custody_days { + for (day, source) in sorted_custody_days { + let day_segments: Vec<&PathBuf> = segments_by_day + .get(&day) + .into_iter() + .flatten() + .filter(|segment| segment_source(segment) == source) + .collect(); if !self.is_active() || self.circuit_open { pass_stopped = true; break; } self.set_progress(format!("checking {day}..."), true); requests_made += 1; - let custody = self.client.fetch_day_custody(&day).await; + let custody = self.client.fetch_source_day_custody(&day, source).await; if custody.error_type == Some(ErrorType::Auth) { pass_stopped = true; pass_error_type = custody.error_type; @@ -764,8 +795,8 @@ impl SyncWorker { { // Non-auth error or no proof: do not record_failure, do not stop pass. // Upload each legacy segment on that day directly in this pass. - if let Some(segments) = segments_by_day.get(&day) { - for segment_dir in segments { + { + for segment_dir in day_segments.iter().copied() { let ack = read_ack(segment_dir); let valid_ack = is_ack_valid( segment_dir, @@ -825,8 +856,8 @@ impl SyncWorker { self.record_contact(false); let indexed = index_entries(&custody.items); - if let Some(segments) = segments_by_day.get(&day) { - for segment_dir in segments { + { + for segment_dir in day_segments.iter().copied() { if uploaded_in_phase1.contains(segment_dir) { continue; } @@ -1032,7 +1063,13 @@ impl SyncWorker { let zone = crate::recovery::read_capture_zone(segment_dir); let result = self .client - .upload_segment_with_meta(day, &key, &files, zone.as_ref()) + .upload_source_segment( + day, + &key, + &files, + zone.as_ref(), + segment_source(segment_dir), + ) .await; let (current_identity, current_pairing) = self.current_identity_and_pairing(); @@ -1457,6 +1494,8 @@ fn is_bookkeeping_file(name: &str) -> bool { || name == INGEST_ACK_FILENAME || name == INGEST_RETRY_FILENAME || name == SERVER_KEY_FILENAME + || name == crate::browser::custody::PERIOD_FILE + || name == crate::browser::custody::RECEIPTS_FILE || (name.starts_with('.') && name.ends_with(".tmp")) || name.starts_with(".ingest_retry.json.tmp.") } @@ -1996,6 +2035,32 @@ fn collect_segments(root: &Path) -> HashMap> { result } +/// The journal source a capture segment goes in as: browser periods under their own +/// stream folder are the `browser` source; every other stream folder is this device's +/// primary capture. +fn segment_source(segment_dir: &Path) -> Option<&'static str> { + crate::browser::is_browser_segment(segment_dir).then_some(crate::browser::SOURCE) +} + +/// Drop browser periods that were not accepted for the journal this app is paired with +/// now. They are retired when the app starts, so this only closes the window before. +fn deliverable_segments( + mut segments_by_day: HashMap>, + browser_generation: Option<&str>, +) -> HashMap> { + for segments in segments_by_day.values_mut() { + segments.retain(|segment| { + !crate::browser::is_browser_segment(segment) + || browser_generation.is_some_and(|generation| { + crate::browser::custody::segment_generation(segment).as_deref() + == Some(generation) + }) + }); + } + segments_by_day.retain(|_, segments| !segments.is_empty()); + segments_by_day +} + fn remove_if_empty(path: &Path) { if path.is_dir() && fs::read_dir(path).is_ok_and(|mut entries| entries.next().is_none()) { let _ = fs::remove_dir(path); @@ -6511,6 +6576,134 @@ mod tests { assert!(body.contains(&format!("\"segment\":\"{seg_name}\""))); } + /// Keep one browser batch for `journal` and finish its period into a capture + /// segment, the way the running app does. + fn browser_period(temp: &tempfile::TempDir, journal: &str) -> PathBuf { + use chrono::TimeZone; + let layout = crate::browser::custody::Layout::new(temp.path()); + let at = |minute| { + chrono::Local + .with_ymd_and_hms(2026, 10, 2, 10, minute, 0) + .earliest() + .unwrap() + }; + let mut custody = + crate::browser::custody::Custody::open(layout, Some(journal), at(1)).unwrap(); + let batch = json!({ + "type": "batch", + "destination_generation": custody.generation().unwrap(), + "inst": "inst-a", + "batch_id": "0123456789abcdef0123456789abcdef", + "queued_at_ms": at(1).timestamp_millis() - 1000, + "records": [{"t": "segment_start", "ts": 1, "ctx": "c1", "inst": "inst-a", + "blocks": [{"id": "b1", "text": "browser marker"}]}], + }); + assert!(matches!( + custody.accept(&batch, at(1)).0, + crate::browser::custody::Outcome::Accepted { .. } + )); + assert!(custody.tick(at(5)).is_some()); + let segment = temp.path().join("captures/20261002/_browser/100100_240"); + assert!(segment.join(crate::browser::PAGES_FILENAME).is_file()); + segment + } + + fn paired_journal(temp: &tempfile::TempDir, server: &LinkedMockServer) -> String { + let config_dir = temp.path().join("config"); + fs::create_dir_all(&config_dir).unwrap(); + let credential = server.credential(); + crate::private_link::persist_credential(&config_dir, &credential).unwrap(); + crate::browser::custody::journal_identity(&credential.instance_id, &credential.ca_chain_pem) + } + + #[tokio::test] + async fn a_browser_period_goes_into_the_journal_as_the_browser_source() { + let temp = tempfile::tempdir().unwrap(); + let (server, mut worker) = test_worker(&temp, Vec::new()).await; + let journal = paired_journal(&temp, &server); + let segment = browser_period(&temp, &journal); + let pages = segment.join(crate::browser::PAGES_FILENAME); + server.enqueue_response( + 200, + json!({ + "status": "ok", + "segment": "100100_240", + "file_descriptors": [{ + "submitted": "browser_pages.jsonl", + "written": "browser_pages.jsonl", + "size": fs::metadata(&pages).unwrap().len(), + "sha256": sha256_file(&pages).unwrap(), + "disposition": "written", + }] + }) + .to_string(), + ); + worker.sync_pass().await; + assert_eq!(upload_hits(&server), 1); + let request = server + .requests() + .into_iter() + .find(|request| request.uri == "/app/devices/ingest") + .unwrap(); + let body = String::from_utf8_lossy(&request.body); + assert!(body.contains("\"source\":\"browser\""), "{body}"); + assert!(body.contains("\"segment\":\"100100_240\""), "{body}"); + assert!(body.contains("Content-Type: application/jsonl"), "{body}"); + assert!(!body.contains(crate::browser::custody::PERIOD_FILE)); + // Confirmed by the journal, so nothing of the period is left on this computer. + assert!(!segment.exists()); + } + + #[tokio::test] + async fn primary_capture_carries_no_source() { + let temp = tempfile::tempdir().unwrap(); + let segment = create_segment(&temp, "120000_300", b"video data"); + let (server, mut worker) = test_worker( + &temp, + vec![( + 200, + json!({ + "status": "ok", + "segment": "120000_300", + "file_descriptors": [{ + "submitted": "screen.webm", + "written": "screen.webm", + "size": 10, + "sha256": sha256_file(&segment.join("screen.webm")).unwrap(), + "disposition": "written", + }] + }), + )], + ) + .await; + worker.sync_pass().await; + let body = String::from_utf8_lossy(&server.requests()[0].body).into_owned(); + assert!(!body.contains("\"source\""), "{body}"); + } + + #[tokio::test] + async fn a_browser_period_kept_for_another_journal_is_never_uploaded() { + let temp = tempfile::tempdir().unwrap(); + let (server, mut worker) = test_worker(&temp, Vec::new()).await; + // Kept while paired with another journal; the app has not restarted since the + // credential changed, so nothing has retired it yet. + let segment = browser_period(&temp, "another-journal"); + paired_journal(&temp, &server); + worker.sync_pass().await; + assert_eq!(upload_hits(&server), 0); + assert!(segment.join(crate::browser::PAGES_FILENAME).is_file()); + } + + #[tokio::test] + async fn browser_periods_wait_while_the_journal_is_unpaired() { + let temp = tempfile::tempdir().unwrap(); + let (server, mut worker) = test_worker(&temp, Vec::new()).await; + let segment = browser_period(&temp, "journal-a"); + worker.sync_pass().await; + assert_eq!(upload_hits(&server), 0); + assert!(segment.exists()); + } + #[tokio::test] async fn sync_uploads_segment_without_capture_zone_omits_meta() { let temp = tempfile::tempdir().unwrap(); diff --git a/crates/solstone-linux/src/upload.rs b/crates/solstone-linux/src/upload.rs index 1c80d1e..e49516c 100644 --- a/crates/solstone-linux/src/upload.rs +++ b/crates/solstone-linux/src/upload.rs @@ -231,6 +231,19 @@ impl UploadClient { segment: &str, files: &[PathBuf], meta: Option<&crate::recovery::CaptureZone>, + ) -> UploadResult { + self.upload_source_segment(day, segment, files, meta, None) + .await + } + + /// Upload a segment as one of the device's sources; `None` is the primary capture. + pub async fn upload_source_segment( + &self, + day: &str, + segment: &str, + files: &[PathBuf], + meta: Option<&crate::recovery::CaptureZone>, + source: Option<&str>, ) -> UploadResult { if self.is_revoked() { return UploadResult::failure(Some(ErrorType::Auth), None, None); @@ -239,7 +252,10 @@ impl UploadClient { let mut last_status = None; let mut last_reason_code = None; for attempt in 0..self.inner.immediate_attempts { - let (form, framed_length) = match build_multipart_form(day, segment, files, meta).await + let (form, framed_length) = match build_multipart_form( + day, segment, files, meta, source, + ) + .await { Ok(form) => form, Err(MultipartBuildError::NoFiles) => { @@ -345,6 +361,10 @@ impl UploadClient { } pub async fn fetch_day_custody(&self, day: &str) -> DayCustody { + self.fetch_source_day_custody(day, None).await + } + + pub async fn fetch_source_day_custody(&self, day: &str, source: Option<&str>) -> DayCustody { if self.is_revoked() { return custody_failure(ErrorType::Auth, None); } @@ -353,7 +373,7 @@ impl UploadClient { }; let (segments, status_code) = match self - .read_json(capability.segments_day(day).await, "segments") + .read_json(capability.segments_day(day, source).await, "segments") .await { Ok(value) => value, @@ -520,6 +540,7 @@ async fn multipart_part( let content_type = match path.extension().and_then(|value| value.to_str()) { Some(extension) if extension.eq_ignore_ascii_case("flac") => "audio/flac", Some(extension) if extension.eq_ignore_ascii_case("webm") => "video/webm", + Some(extension) if extension.eq_ignore_ascii_case("jsonl") => "application/jsonl", _ => "application/octet-stream", }; let file_name = path @@ -553,6 +574,8 @@ struct UploadEnvelope<'a> { files: Vec, #[serde(skip_serializing_if = "Option::is_none")] meta: Option<&'a crate::recovery::CaptureZone>, + #[serde(skip_serializing_if = "Option::is_none")] + source: Option<&'a str>, } #[derive(Serialize)] @@ -594,6 +617,7 @@ async fn build_multipart_form( segment: &str, files: &[PathBuf], meta: Option<&crate::recovery::CaptureZone>, + source: Option<&str>, ) -> Result<(multipart::Form, u64), MultipartBuildError> { let form = multipart::Form::new(); let boundary = form.boundary().to_owned(); @@ -623,6 +647,7 @@ async fn build_multipart_form( segment, files: submitted, meta, + source, }) .expect("upload envelope is serializable"); if envelope.len() as u64 > MAX_MULTIPART_PART_BYTES { @@ -1041,7 +1066,7 @@ mod tests { file.set_len(MAX_MULTIPART_PART_BYTES).unwrap(); drop(file); assert!( - build_multipart_form("20260101", "boundary", &[media], None) + build_multipart_form("20260101", "boundary", &[media], None, None) .await .is_ok() ); @@ -1057,7 +1082,7 @@ mod tests { file.set_len(MAX_MULTIPART_PART_BYTES - 4096).unwrap(); } let (_, encoded_length) = - build_multipart_form("20260101", "boundary", &[first, second], None) + build_multipart_form("20260101", "boundary", &[first, second], None, None) .await .unwrap(); assert!(encoded_length <= MAX_REQUEST_BODY_BYTES); @@ -1080,7 +1105,8 @@ mod tests { "20260101", "boundary", &[media.clone(), second.clone()], - None + None, + None, ) .await, Err(MultipartBuildError::RequestTooLarge) diff --git a/licensing/overrides/jsonschema-regex/0.58.4/LICENSE b/licensing/overrides/jsonschema-regex/0.58.4/LICENSE new file mode 100644 index 0000000..20c070f --- /dev/null +++ b/licensing/overrides/jsonschema-regex/0.58.4/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2026 Dmitry Dygalo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/overrides/jsonschema-value/0.58.4/LICENSE b/licensing/overrides/jsonschema-value/0.58.4/LICENSE new file mode 100644 index 0000000..20c070f --- /dev/null +++ b/licensing/overrides/jsonschema-value/0.58.4/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2026 Dmitry Dygalo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/overrides/uuid-simd/0.8.0/LICENSE b/licensing/overrides/uuid-simd/0.8.0/LICENSE new file mode 100644 index 0000000..2c7eabb --- /dev/null +++ b/licensing/overrides/uuid-simd/0.8.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Nugine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/licensing/overrides/vsimd/0.8.0/LICENSE b/licensing/overrides/vsimd/0.8.0/LICENSE new file mode 100644 index 0000000..2c7eabb --- /dev/null +++ b/licensing/overrides/vsimd/0.8.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Nugine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/licensing/rust-dependency-notices.index.json b/licensing/rust-dependency-notices.index.json index 736124c..5a418fa 100644 --- a/licensing/rust-dependency-notices.index.json +++ b/licensing/rust-dependency-notices.index.json @@ -1,11 +1,11 @@ { "schema": "solstone-linux.rust-dependency-notices.v1", - "cargo_lock_sha256": "013d37a8b5fca39e0f063f5a4cb69e9e4ce80cc604642d3c9f0405a8bef53d65", - "notices_sha256": "330607a08a161840b0f795b31cd8bbd3b12883f5376fb10d1592a8cf34698206", + "cargo_lock_sha256": "c325ccd6a605b6ac6243aa140f3a04bbcef040f026559cdd44a6f54f820bc524", + "notices_sha256": "429da72b65b3cf2aef141226b2800a854aa3e3465d438c6b8c70ef874f819632", "population_query": "cargo metadata --locked --offline --format-version 1 --filter-platform x86_64-unknown-linux-gnu", "root_package": "solstone-linux", "filter_platform": "x86_64-unknown-linux-gnu", - "external_package_count": 325, + "external_package_count": 360, "packages": [ { "name": "adler2", @@ -15,22 +15,66 @@ "members": [ { "sha256": "861399f8c21c042b110517e76dc6b63a2b334276c8cf17412fc3c8908ca8dc17", - "start": 460974, - "end": 461639, + "start": 490593, + "end": 491258, "origin": "crate-archive", "path": "LICENSE-0BSD" }, { "sha256": "8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb", - "start": 475111, - "end": 485971, + "start": 504730, + "end": 515590, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "ahash", + "version": "0.8.12", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb", + "start": 28003, + "end": 29060, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "aho-corasick", + "version": "1.1.4", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "Unlicense OR MIT", + "members": [ + { + "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f", + "start": 15958, + "end": 16084, + "origin": "crate-archive", + "path": "COPYING" + }, + { + "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f", + "start": 95963, + "end": 97044, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -44,15 +88,15 @@ "members": [ { "sha256": "20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58", - "start": 115369, - "end": 125268, + "start": 118409, + "end": 128308, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9", - "start": 206233, - "end": 207279, + "start": 209273, + "end": 210319, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -66,15 +110,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -88,15 +132,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -110,15 +154,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -132,15 +176,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -154,15 +198,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "33f702959c0ea91c08b21b65cf1f08b6c122ec9e6db0b5db784a7b367d942330", - "start": 194359, - "end": 195441, + "start": 197399, + "end": 198481, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -176,8 +220,8 @@ "members": [ { "sha256": "1bc7e6f475b3ec99b7e2643411950ae2368c250dd4c5c325f80f9811362a94a1", - "start": 108559, - "end": 109895, + "start": 111599, + "end": 112935, "origin": "crate-archive", "path": "LICENSE" } @@ -191,15 +235,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "4da95ec4ecb65b738d470b7d762894ad9c97da93e6cbfb18b570fc2c96f4b871", - "start": 268274, - "end": 269345, + "start": 293981, + "end": 295052, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -213,8 +257,8 @@ "members": [ { "sha256": "ae26bc7b1bc91fb8648f6a298decbb1a77090a56c9d04e2c23b8365e5f54acb5", - "start": 584153, - "end": 585227, + "start": 619112, + "end": 620186, "origin": "crate-archive", "path": "LICENSE" } @@ -228,15 +272,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513", - "start": 534997, - "end": 536057, + "start": 568887, + "end": 569947, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -250,15 +294,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513", - "start": 534997, - "end": 536057, + "start": 568887, + "end": 569947, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -272,15 +316,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "committed-override", "path": "LICENSE-APACHE" }, { "sha256": "a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513", - "start": 534997, - "end": 536057, + "start": 568887, + "end": 569947, "origin": "committed-override", "path": "LICENSE-MIT" } @@ -294,15 +338,15 @@ "members": [ { "sha256": "e4705ddab847449a2cdcb3c88b005ea10330aa249d9148ca2eef9c84c5d29895", - "start": 709787, - "end": 720544, + "start": 744746, + "end": 755503, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "24e5860bf589d8501643e6ea51ffb3df66db2867492b09033d486183efbfa970", - "start": 131620, - "end": 132699, + "start": 134660, + "end": 135739, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -316,15 +360,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -338,15 +382,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -360,15 +404,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -382,15 +426,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -404,15 +448,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -426,15 +470,15 @@ "members": [ { "sha256": "769f80b5bcb42ed0af4e4d2fd74e1ac9bf843cb80c5a29219d1ef3544428a6bb", - "start": 395831, - "end": 406677, + "start": 425450, + "end": 436296, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "30fefc3a7d6a0041541858293bcbea2dde4caa4c0a5802f996a7f7e8c0085652", - "start": 192453, - "end": 193475, + "start": 195493, + "end": 196515, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -448,15 +492,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -470,15 +514,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -492,15 +536,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -514,22 +558,22 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" }, { "sha256": "6226d0632e2e1a80c23597e964da9812ae193c535fe058154afb034e94167aa5", - "start": 331930, - "end": 333779, + "start": 357637, + "end": 359486, "origin": "crate-archive", "path": "LICENSE-THIRD-PARTY" } @@ -543,15 +587,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac", - "start": 170948, - "end": 172002, + "start": 173988, + "end": 175042, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -565,15 +609,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "0aa8963e105e8b6e02f634484145d4e5b0f42d0a6dd05c16f8148f033383adef", - "start": 43820, - "end": 44963, + "start": 45784, + "end": 46927, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -587,15 +631,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7", - "start": 92922, - "end": 93998, + "start": 94886, + "end": 95962, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -609,15 +653,59 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "2d1c57bff28344b9e698f51063bc8509799cc4c99a4e0cf2aa3f7e7c3e1f9a9d", - "start": 177494, - "end": 178642, + "start": 180534, + "end": 181682, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "bit-set", + "version": "0.8.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "Apache-2.0 OR MIT", + "members": [ + { + "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", + "start": 458035, + "end": 468885, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "f51ac2c59a222f7476ce507ca879960e2b64ea64bb2786eefdbeb7b0b538d1b7", + "start": 781235, + "end": 782306, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "bit-vec", + "version": "0.8.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "Apache-2.0 OR MIT", + "members": [ + { + "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", + "start": 458035, + "end": 468885, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "f51ac2c59a222f7476ce507ca879960e2b64ea64bb2786eefdbeb7b0b538d1b7", + "start": 781235, + "end": 782306, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -631,15 +719,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", - "start": 343504, - "end": 344575, + "start": 369211, + "end": 370282, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -653,15 +741,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", - "start": 343504, - "end": 344575, + "start": 369211, + "end": 370282, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -675,15 +763,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "9c768944eb4a0422ca2efc25ea9fb2fb9e7fbd3fdb04e86b87366339cb7466db", - "start": 518653, - "end": 519774, + "start": 550411, + "end": 551532, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -697,15 +785,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "d5c22aa3118d240e877ad41c5d9fa232f9c77d757d4aac0c2f943afc0a95e0ef", - "start": 684624, - "end": 685706, + "start": 719583, + "end": 720665, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -719,20 +807,57 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } ] }, + { + "name": "borrow-or-share", + "version": "0.2.4", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT-0", + "members": [ + { + "sha256": "010492e490b92ec85db01daad2a290ea5959f29314a1c4ff25e7a172e223abf9", + "start": 15052, + "end": 15956, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, + { + "name": "bytecount", + "version": "0.6.9", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "Apache-2.0/MIT", + "members": [ + { + "sha256": "b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1", + "start": 625830, + "end": 637187, + "origin": "crate-archive", + "path": "LICENSE.Apache2" + }, + { + "sha256": "a5dea80c1f383cb5f80a6bb0da5e55a2beb9f24adb123ce6300af2cbaaa3bf65", + "start": 569948, + "end": 571016, + "origin": "crate-archive", + "path": "LICENSE.MIT" + } + ] + }, { "name": "bytemuck", "version": "1.25.1", @@ -741,22 +866,22 @@ "members": [ { "sha256": "e3ba223bb1423f0aad8c3dfce0fe3148db48926d41e6fbc3afbbf5ff9e1c89cb", - "start": 699388, - "end": 709786, + "start": 734347, + "end": 744745, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "9df9ba60a11af705f2e451b53762686e615d86f76b169cf075c3237730dbd7e2", - "start": 531126, - "end": 532236, + "start": 562884, + "end": 563994, "origin": "crate-archive", "path": "LICENSE-MIT" }, { "sha256": "84b34dd7608f7fb9b17bd588a6bf392bf7de504e2716f024a77d89f1b145a151", - "start": 458636, - "end": 459487, + "start": 488255, + "end": 489106, "origin": "crate-archive", "path": "LICENSE-ZLIB" } @@ -770,8 +895,8 @@ "members": [ { "sha256": "45f522cacecb1023856e46df79ca625dfc550c94910078bd8aec6e02880b3d42", - "start": 241839, - "end": 242894, + "start": 267546, + "end": 268601, "origin": "crate-archive", "path": "LICENSE" } @@ -785,15 +910,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", - "start": 218983, - "end": 220040, + "start": 222023, + "end": 223080, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -807,15 +932,15 @@ "members": [ { "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", - "start": 428416, - "end": 439266, + "start": 458035, + "end": 468885, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "090a294a492ab2f41388252312a65cf2f0e423330b721a68c6665ac64766753b", - "start": 40596, - "end": 41654, + "start": 42560, + "end": 43618, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -829,15 +954,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", - "start": 218983, - "end": 220040, + "start": 222023, + "end": 223080, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -851,8 +976,8 @@ "members": [ { "sha256": "946c9835d8034d24404f8cfec5f4654cee5dad17e944afc3d06d742cf2882831", - "start": 505893, - "end": 518200, + "start": 537651, + "end": 549958, "origin": "crate-archive", "path": "LICENSE.txt" } @@ -866,15 +991,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -888,15 +1013,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -910,15 +1035,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -932,15 +1057,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -954,8 +1079,8 @@ "members": [ { "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", - "start": 666980, - "end": 678338, + "start": 701939, + "end": 713297, "origin": "crate-archive", "path": "license" } @@ -969,15 +1094,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", - "start": 218983, - "end": 220040, + "start": 222023, + "end": 223080, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -991,15 +1116,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1013,15 +1138,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1035,15 +1160,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "bada9e7ed8dc00d63502053c455d7c8d7575dfb7e8277a2a832531844d900682", - "start": 607721, - "end": 608803, + "start": 642680, + "end": 643762, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1057,15 +1182,15 @@ "members": [ { "sha256": "2773e20df8f4c52a026b5b578c7f2457341f5aa3fb6612fd87e1d2e1bd8f48ad", - "start": 160084, - "end": 170947, + "start": 163124, + "end": 173987, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "f28420c1906af38be726cd7842798f0194b27c41d6051c0d1e9ee348b8a4a0ea", - "start": 741926, - "end": 743017, + "start": 776885, + "end": 777976, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1079,15 +1204,15 @@ "members": [ { "sha256": "b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1", - "start": 590871, - "end": 602228, + "start": 625830, + "end": 637187, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "afb11426e09da40a1ae4f8fa17ddcc6b6a52d14df04c29bc5bcd06eb8730624d", - "start": 586311, - "end": 587368, + "start": 621270, + "end": 622327, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1101,15 +1226,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "ae9baa7beea910273c2f384c2a6b721fb7bd02bda3436074a1072e4ee689f985", - "start": 585228, - "end": 586310, + "start": 620187, + "end": 621269, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1123,15 +1248,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "61d383b05b87d78f94d2937e2580cce47226d17823c0430fbcad09596537efcf", - "start": 329764, - "end": 330861, + "start": 355471, + "end": 356568, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1145,15 +1270,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "5734ed989dfca1f625b40281ee9f4530f91b2411ec01cb748223e7eb87e201ab", - "start": 287210, - "end": 288309, + "start": 312917, + "end": 314016, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1167,15 +1292,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "90c503b61dee04e1449c323ec34c229dfb68d7adcb96c7e140ee55f70fce2d8e", - "start": 502112, - "end": 503189, + "start": 532804, + "end": 533881, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1189,15 +1314,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "3521672491a3479422d5fe1aca6645dd2984090f85da6e5205abfb18fb7a6897", - "start": 195442, - "end": 196507, + "start": 198482, + "end": 199547, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1211,8 +1336,8 @@ "members": [ { "sha256": "b68ad1a3367b825447089e1f8d6829b97f47a89eb78d2f4ebaef4672f5606186", - "start": 603368, - "end": 604489, + "start": 638327, + "end": 639448, "origin": "crate-archive", "path": "LICENSE" } @@ -1226,15 +1351,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", - "start": 589298, - "end": 590370, + "start": 624257, + "end": 625329, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1248,15 +1373,15 @@ "members": [ { "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", - "start": 428416, - "end": 439266, + "start": 458035, + "end": 468885, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "2710a622a896bba67356913d4d0492cab5465f61b2ecce6d880aeb483834fb50", - "start": 148154, - "end": 149207, + "start": 151194, + "end": 152247, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1270,15 +1395,15 @@ "members": [ { "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", - "start": 428416, - "end": 439266, + "start": 458035, + "end": 468885, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "2710a622a896bba67356913d4d0492cab5465f61b2ecce6d880aeb483834fb50", - "start": 148154, - "end": 149207, + "start": 151194, + "end": 152247, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1292,15 +1417,15 @@ "members": [ { "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", - "start": 428416, - "end": 439266, + "start": 458035, + "end": 468885, "origin": "committed-override", "path": "LICENSE-APACHE" }, { "sha256": "2710a622a896bba67356913d4d0492cab5465f61b2ecce6d880aeb483834fb50", - "start": 148154, - "end": 149207, + "start": 151194, + "end": 152247, "origin": "committed-override", "path": "LICENSE-MIT" } @@ -1314,15 +1439,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "ad64fcb9589f162720f3cc5010ad76ca6ad3764e11861f9192c489df176bb71d", - "start": 583070, - "end": 584152, + "start": 618029, + "end": 619111, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1336,15 +1461,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513", - "start": 534997, - "end": 536057, + "start": 568887, + "end": 569947, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1358,15 +1483,15 @@ "members": [ { "sha256": "edd65bdd88957a205c47d53fa499eed8865a70320f0f03f6391668cb304ea376", - "start": 727266, - "end": 738615, + "start": 762225, + "end": 773574, "origin": "crate-archive", "path": "LICENSE-Apache" }, { "sha256": "231c837c45eb53f108fb48929e488965bc4fcc14e9ea21d35f50e6b99d98685b", - "start": 126322, - "end": 127384, + "start": 129362, + "end": 130424, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1380,15 +1505,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "9e0dfd2dd4173a530e238cb6adb37aa78c34c6bc7444e0e10c1ab5d8881f63ba", - "start": 533300, - "end": 534357, + "start": 565058, + "end": 566115, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1402,15 +1527,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1424,15 +1549,15 @@ "members": [ { "sha256": "074e6e32c86a4c0ef8b3ed25b721ca23aca83df277cd88106ef7177c354615ff", - "start": 29232, - "end": 39512, + "start": 31196, + "end": 41476, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "aa893340d14b9844625be6a50ac644169a01b52f0211cbf81b09e1874c8cd81d", - "start": 571137, - "end": 572219, + "start": 606096, + "end": 607178, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1446,15 +1571,15 @@ "members": [ { "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", - "start": 428416, - "end": 439266, + "start": 458035, + "end": 468885, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "fb252fecf98d9af8f1d9201c5860319a514f5a8a24700dc84fc6ea287a7a4958", - "start": 751518, - "end": 752591, + "start": 787549, + "end": 788622, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1468,15 +1593,15 @@ "members": [ { "sha256": "78779d420019e6b4630376af8e86b6b335ee8a2f89ede6e0411e0469a326aaa4", - "start": 408189, - "end": 419035, + "start": 437808, + "end": 448654, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "bdebaf9156a298f8fdab56dd26cb5144673de522d80f4c0d88e0039145f147f9", - "start": 608804, - "end": 609874, + "start": 643763, + "end": 644833, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1490,15 +1615,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "7576269ea71f767b99297934c0b2367532690f8c4badc695edf8e04ab6a1e545", - "start": 394787, - "end": 395830, + "start": 424406, + "end": 425449, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1512,20 +1637,35 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "d27687b51f2874822c1530976b7e837eac4f308d94bf3dd42047011b7d437b47", - "start": 680977, - "end": 682047, + "start": 715936, + "end": 717006, "origin": "crate-archive", "path": "LICENSE-MIT" } ] }, + { + "name": "email_address", + "version": "0.2.9", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "39712155836e5c10ef1b07037b606e4ad2d633e72205d162fdd7e2290aa54f15", + "start": 224145, + "end": 225216, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, { "name": "endi", "version": "1.1.1", @@ -1534,8 +1674,8 @@ "members": [ { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1549,15 +1689,15 @@ "members": [ { "sha256": "41ba8a366e4129d0b7551f8b98211f59635b2e30c0a3c7845e828e2a7ce42064", - "start": 224449, - "end": 234164, + "start": 250156, + "end": 259871, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "2e3edd2716fb15616e867bd53f724f0906732242e9ae76bb1faad1d88f7eeebd", - "start": 178643, - "end": 179720, + "start": 181683, + "end": 182760, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1571,15 +1711,15 @@ "members": [ { "sha256": "68d7c3ae5b131ec533fb33a9fd63753eb82fac77d7597bb5058a5af07adf9f06", - "start": 345671, - "end": 355367, + "start": 371378, + "end": 381074, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "d13669f0eaff9b7015bff0d8637297213b53d9f239826636c07a15633acf7052", - "start": 678339, - "end": 679393, + "start": 713298, + "end": 714352, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1593,15 +1733,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349", - "start": 381218, - "end": 382267, + "start": 407989, + "end": 409038, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1615,15 +1755,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "8764a597675778ddfd4e25f81b08a05dbcf089ac05662df7613fe67f150e3aa2", - "start": 461640, - "end": 462694, + "start": 491259, + "end": 492313, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1637,22 +1777,22 @@ "members": [ { "sha256": "ec82b96487e9e778ee610c7ab245162464782cfa1f555c2299333f8dbe5c036a", - "start": 725894, - "end": 726215, + "start": 760853, + "end": 761174, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "62065228e42caebca7e7d7db1204cbb867033de5982ca4009928915e4095f3a3", - "start": 330862, - "end": 331929, + "start": 356569, + "end": 357636, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1666,15 +1806,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1688,20 +1828,35 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } ] }, + { + "name": "fancy-regex", + "version": "0.19.2", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "3bc70e239e91272782006c638fc0452a714d384224f11f0923036b7be07cf9b5", + "start": 245730, + "end": 246811, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, { "name": "fastrand", "version": "2.4.1", @@ -1710,15 +1865,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1732,15 +1887,15 @@ "members": [ { "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "start": 48223, - "end": 58397, + "start": 50187, + "end": 60361, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "c77a4cf9da729987d0fe7ccd811e3bd27393914ddf3d23467c18cc22954513b3", - "start": 648157, - "end": 649193, + "start": 683116, + "end": 684152, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1754,15 +1909,15 @@ "members": [ { "sha256": "3708458dee7f359ac6c9c5558023ed481be87e5372c43ebd7f2ca7ad23c12026", - "start": 207280, - "end": 218128, + "start": 210320, + "end": 221168, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "8d0c1d1d4b2bebf5b9211f0e883d5a482b7808f3ed31320da14ddee95811f4ef", - "start": 501035, - "end": 502111, + "start": 531727, + "end": 532803, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1776,15 +1931,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", - "start": 218983, - "end": 220040, + "start": 222023, + "end": 223080, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1798,8 +1953,8 @@ "members": [ { "sha256": "e339134e53732f093f68ef00bcec3687ebf964ef8c6c812f31e74099c07d3f49", - "start": 698306, - "end": 699387, + "start": 733265, + "end": 734346, "origin": "crate-archive", "path": "LICENSE" } @@ -1813,15 +1968,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "025436edff4cfcdde17a5811fdea78892d8482efd1abdec5a17872d07a4f2112", - "start": 26034, - "end": 27096, + "start": 26940, + "end": 28002, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1835,8 +1990,23 @@ "members": [ { "sha256": "40be1e77825d7e49485a2e43d89bed29dfff29f8f529e71d3c683656021f0d08", - "start": 222159, - "end": 223234, + "start": 247866, + "end": 248941, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, + { + "name": "fluent-uri", + "version": "0.4.1", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "9fa38f610a00115dd1f612bdd3559e3b381ea579538b6ff25a5899da250a5fce", + "start": 566755, + "end": 567821, "origin": "crate-archive", "path": "LICENSE" } @@ -1850,8 +2020,8 @@ "members": [ { "sha256": "b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744", - "start": 587369, - "end": 588225, + "start": 622328, + "end": 623184, "origin": "crate-archive", "path": "LICENSE" } @@ -1865,15 +2035,37 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e", - "start": 114296, - "end": 115368, + "start": 117336, + "end": 118408, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "fraction", + "version": "0.17.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "start": 369211, + "end": 370282, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1887,15 +2079,15 @@ "members": [ { "sha256": "275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427", - "start": 149209, - "end": 160083, + "start": 152249, + "end": 163123, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd", - "start": 344576, - "end": 345670, + "start": 370283, + "end": 371377, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1909,15 +2101,15 @@ "members": [ { "sha256": "275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427", - "start": 149209, - "end": 160083, + "start": 152249, + "end": 163123, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd", - "start": 344576, - "end": 345670, + "start": 370283, + "end": 371377, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1931,15 +2123,15 @@ "members": [ { "sha256": "275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427", - "start": 149209, - "end": 160083, + "start": 152249, + "end": 163123, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd", - "start": 344576, - "end": 345670, + "start": 370283, + "end": 371377, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1953,15 +2145,15 @@ "members": [ { "sha256": "275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427", - "start": 149209, - "end": 160083, + "start": 152249, + "end": 163123, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd", - "start": 344576, - "end": 345670, + "start": 370283, + "end": 371377, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -1975,22 +2167,22 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" }, { "sha256": "6226d0632e2e1a80c23597e964da9812ae193c535fe058154afb034e94167aa5", - "start": 331930, - "end": 333779, + "start": 357637, + "end": 359486, "origin": "crate-archive", "path": "LICENSE-THIRD-PARTY" } @@ -2004,15 +2196,15 @@ "members": [ { "sha256": "275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427", - "start": 149209, - "end": 160083, + "start": 152249, + "end": 163123, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd", - "start": 344576, - "end": 345670, + "start": 370283, + "end": 371377, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2026,15 +2218,15 @@ "members": [ { "sha256": "275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427", - "start": 149209, - "end": 160083, + "start": 152249, + "end": 163123, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd", - "start": 344576, - "end": 345670, + "start": 370283, + "end": 371377, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2048,15 +2240,15 @@ "members": [ { "sha256": "275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427", - "start": 149209, - "end": 160083, + "start": 152249, + "end": 163123, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd", - "start": 344576, - "end": 345670, + "start": 370283, + "end": 371377, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2070,15 +2262,15 @@ "members": [ { "sha256": "275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427", - "start": 149209, - "end": 160083, + "start": 152249, + "end": 163123, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd", - "start": 344576, - "end": 345670, + "start": 370283, + "end": 371377, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2092,8 +2284,8 @@ "members": [ { "sha256": "c09aae9d3c77b531f56351a9947bc7446511d6b025b3255312d3e3442a9a7583", - "start": 609875, - "end": 610982, + "start": 644834, + "end": 645941, "origin": "crate-archive", "path": "LICENSE" } @@ -2107,8 +2299,8 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE" } @@ -2122,15 +2314,15 @@ "members": [ { "sha256": "aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf", - "start": 572220, - "end": 583069, + "start": 607179, + "end": 618028, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "42fa16951ce7f24b5a467a40e5b449a1d41e662f97ca779864f053f39e097737", - "start": 236307, - "end": 237437, + "start": 262014, + "end": 263144, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2144,15 +2336,15 @@ "members": [ { "sha256": "aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf", - "start": 572220, - "end": 583069, + "start": 607179, + "end": 618028, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4", - "start": 173161, - "end": 174291, + "start": 176201, + "end": 177331, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2166,15 +2358,15 @@ "members": [ { "sha256": "aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf", - "start": 572220, - "end": 583069, + "start": 607179, + "end": 618028, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "523a42c25d245dde9c015f882cec7f4555aad883382a6cf19b4b7d9b2cd5419b", - "start": 284450, - "end": 285580, + "start": 310157, + "end": 311287, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2188,8 +2380,8 @@ "members": [ { "sha256": "8cf56d10131ce201cf69ab74b111d3ebac1acca3833d7efb39ae357224b70edb", - "start": 500010, - "end": 501034, + "start": 530702, + "end": 531726, "origin": "crate-archive", "path": "LICENSE" } @@ -2203,15 +2395,15 @@ "members": [ { "sha256": "dae402989de65164815b7e2b6bc2b9576285434c3785934c8b6ece0fa055960d", - "start": 687840, - "end": 688491, + "start": 722799, + "end": 723450, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "8cf56d10131ce201cf69ab74b111d3ebac1acca3833d7efb39ae357224b70edb", - "start": 500010, - "end": 501034, + "start": 530702, + "end": 531726, "origin": "crate-archive", "path": "LICENSE" } @@ -2225,15 +2417,15 @@ "members": [ { "sha256": "dae402989de65164815b7e2b6bc2b9576285434c3785934c8b6ece0fa055960d", - "start": 687840, - "end": 688491, + "start": 722799, + "end": 723450, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "8cf56d10131ce201cf69ab74b111d3ebac1acca3833d7efb39ae357224b70edb", - "start": 500010, - "end": 501034, + "start": 530702, + "end": 531726, "origin": "crate-archive", "path": "LICENSE" } @@ -2247,8 +2439,8 @@ "members": [ { "sha256": "8cf56d10131ce201cf69ab74b111d3ebac1acca3833d7efb39ae357224b70edb", - "start": 500010, - "end": 501034, + "start": 530702, + "end": 531726, "origin": "crate-archive", "path": "LICENSE" } @@ -2262,8 +2454,8 @@ "members": [ { "sha256": "8cf56d10131ce201cf69ab74b111d3ebac1acca3833d7efb39ae357224b70edb", - "start": 500010, - "end": 501034, + "start": 530702, + "end": 531726, "origin": "crate-archive", "path": "LICENSE" } @@ -2277,22 +2469,22 @@ "members": [ { "sha256": "7429802d3e67e3308471b91fbea341e484c4e0a23d0fb230a8636c38184cec79", - "start": 383337, - "end": 383952, + "start": 410108, + "end": 410723, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2306,22 +2498,22 @@ "members": [ { "sha256": "09230790719b8034522109d0ec97ce72305c7a76cab07ef445e0732d456921f8", - "start": 41655, - "end": 42834, + "start": 43619, + "end": 44798, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2335,15 +2527,15 @@ "members": [ { "sha256": "09230790719b8034522109d0ec97ce72305c7a76cab07ef445e0732d456921f8", - "start": 41655, - "end": 42834, + "start": 43619, + "end": 44798, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE" } @@ -2357,15 +2549,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2", - "start": 753686, - "end": 754746, + "start": 790815, + "end": 791875, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2379,15 +2571,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0", - "start": 422094, - "end": 423165, + "start": 451713, + "end": 452784, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2401,15 +2593,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9", - "start": 748396, - "end": 749521, + "start": 784427, + "end": 785552, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2423,15 +2615,15 @@ "members": [ { "sha256": "59013a5c8d3a19c26a457579105915a5d51bb0c09d579f8cdedf12e4203c3018", - "start": 289396, - "end": 300244, + "start": 315103, + "end": 325951, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "d288f9c9b4590446ec18c22ead8f8b5a12a3d4025b68f62dc9015063eb9cca69", - "start": 682048, - "end": 683156, + "start": 717007, + "end": 718115, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2445,15 +2637,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "9e0dfd2dd4173a530e238cb6adb37aa78c34c6bc7444e0e10c1ab5d8881f63ba", - "start": 533300, - "end": 534357, + "start": 565058, + "end": 566115, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2467,15 +2659,15 @@ "members": [ { "sha256": "8bb1b50b0e5c9399ae33bd35fab2769010fa6c14e8860c729a52295d84896b7a", - "start": 487035, - "end": 497870, + "start": 516654, + "end": 527489, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "dc91f8200e4b2a1f9261035d4c18c33c246911a6c0f7b543d75347e61b249cff", - "start": 690515, - "end": 691574, + "start": 725474, + "end": 726533, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2489,8 +2681,8 @@ "members": [ { "sha256": "248378d0a3383c173fb925f17141b88e71580b3ba17ddc6ac3d2a344683232ab", - "start": 130536, - "end": 131619, + "start": 133576, + "end": 134659, "origin": "crate-archive", "path": "LICENSE" } @@ -2504,8 +2696,8 @@ "members": [ { "sha256": "248378d0a3383c173fb925f17141b88e71580b3ba17ddc6ac3d2a344683232ab", - "start": 130536, - "end": 131619, + "start": 133576, + "end": 134659, "origin": "crate-archive", "path": "LICENSE" } @@ -2519,15 +2711,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "391a5396cec6230bfabd4ef4eb2350eb895bc5efce377a2218f5702ed020d3e3", - "start": 220041, - "end": 221104, + "start": 223081, + "end": 224144, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2541,15 +2733,15 @@ "members": [ { "sha256": "4d10fe5f3aa176b05b229a248866bad70b834c173f1252a814ff4748d8a13837", - "start": 258018, - "end": 268273, + "start": 283725, + "end": 293980, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "934887691e05d69d7c86ad3f2c360980fa30c15b035e351f3c9865e99da4debc", - "start": 504841, - "end": 505892, + "start": 535533, + "end": 536584, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2563,8 +2755,8 @@ "members": [ { "sha256": "2d01890414494742ba4a509fcec8efa40f6d8be22cbd72be7cff08d6fda4ec89", - "start": 176431, - "end": 177493, + "start": 179471, + "end": 180533, "origin": "crate-archive", "path": "LICENSE" } @@ -2578,8 +2770,8 @@ "members": [ { "sha256": "9e0a97848ea543aef745c98e84fde696a9a3e0735538f6daefdd3cb1942effc1", - "start": 532237, - "end": 533299, + "start": 563995, + "end": 565057, "origin": "crate-archive", "path": "LICENSE" } @@ -2593,15 +2785,15 @@ "members": [ { "sha256": "696759d65dfe558ff7d9f031c76db19ec5c0767470fb67c4e8d990820d1e99c9", - "start": 355368, - "end": 366200, + "start": 381075, + "end": 391907, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "da28ccc6b158fc2d8cccc74e99794b1cff1d29bd7bbeb019442fcf0c04c6cad9", - "start": 686780, - "end": 687839, + "start": 721739, + "end": 722798, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2615,8 +2807,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -2630,8 +2822,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -2645,8 +2837,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -2660,8 +2852,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -2675,8 +2867,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -2690,8 +2882,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -2705,8 +2897,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -2720,15 +2912,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", - "start": 589298, - "end": 590370, + "start": 624257, + "end": 625329, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2742,15 +2934,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726", - "start": 485972, - "end": 487034, + "start": 515591, + "end": 516653, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2764,8 +2956,8 @@ "members": [ { "sha256": "436637bd4909fde7e14c899e9ff2cdd440b5968df12d431f1651f11db19bc2ef", - "start": 237438, - "end": 238523, + "start": 263145, + "end": 264230, "origin": "crate-archive", "path": "LICENSE" } @@ -2779,15 +2971,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "ecc269ef87fd38a1d98e30bfac9ba964a9dbd9315c3770fed98d4d7cb5882055", - "start": 726216, - "end": 727265, + "start": 761175, + "end": 762224, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2801,15 +2993,15 @@ "members": [ { "sha256": "87d9feb9238c6bd8e0024fc4733b06cff036f89f36d93b7df1c8a0549bbb7a5b", - "start": 462695, - "end": 474047, + "start": 492314, + "end": 503666, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "47dc9ff29128ddfb4d6a0435383c9f89120bc374dbcc1dd00b933a0b28aa7865", - "start": 254795, - "end": 255857, + "start": 280502, + "end": 281564, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2823,8 +3015,8 @@ "members": [ { "sha256": "7377e48a67377f0baa9ccee6425e125eb7b96d5b8e706f4c99479ca4ffa21b77", - "start": 382268, - "end": 383336, + "start": 409039, + "end": 410107, "origin": "crate-archive", "path": "LICENSE" } @@ -2838,8 +3030,8 @@ "members": [ { "sha256": "7377e48a67377f0baa9ccee6425e125eb7b96d5b8e706f4c99479ca4ffa21b77", - "start": 382268, - "end": 383336, + "start": 409039, + "end": 410107, "origin": "crate-archive", "path": "LICENSE" } @@ -2853,15 +3045,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2875,15 +3067,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "7576269ea71f767b99297934c0b2367532690f8c4badc695edf8e04ab6a1e545", - "start": 394787, - "end": 395830, + "start": 424406, + "end": 425449, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2897,15 +3089,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2919,15 +3111,15 @@ "members": [ { "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f", - "start": 15052, - "end": 15178, + "start": 15958, + "end": 16084, "origin": "crate-archive", "path": "COPYING" }, { "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f", - "start": 93999, - "end": 95080, + "start": 95963, + "end": 97044, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2941,15 +3133,15 @@ "members": [ { "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f", - "start": 15052, - "end": 15178, + "start": 15958, + "end": 16084, "origin": "crate-archive", "path": "COPYING" }, { "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f", - "start": 93999, - "end": 95080, + "start": 95963, + "end": 97044, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -2963,20 +3155,65 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", - "start": 218983, - "end": 220040, + "start": 222023, + "end": 223080, "origin": "crate-archive", "path": "LICENSE-MIT" } ] }, + { + "name": "jsonschema", + "version": "0.58.4", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "117829c3ca21efb132d81a44b55363d395ab8eea18526873bc828da4c0e5f038", + "start": 97045, + "end": 98120, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, + { + "name": "jsonschema-regex", + "version": "0.58.4", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "117829c3ca21efb132d81a44b55363d395ab8eea18526873bc828da4c0e5f038", + "start": 97045, + "end": 98120, + "origin": "committed-override", + "path": "LICENSE" + } + ] + }, + { + "name": "jsonschema-value", + "version": "0.58.4", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "117829c3ca21efb132d81a44b55363d395ab8eea18526873bc828da4c0e5f038", + "start": 97045, + "end": 98120, + "origin": "committed-override", + "path": "LICENSE" + } + ] + }, { "name": "ksni", "version": "0.3.6", @@ -2985,8 +3222,8 @@ "members": [ { "sha256": "7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c", - "start": 425005, - "end": 426216, + "start": 454624, + "end": 455835, "origin": "committed-override", "path": "UNLICENSE" } @@ -3000,15 +3237,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3022,15 +3259,15 @@ "members": [ { "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", - "start": 666980, - "end": 678338, + "start": 701939, + "end": 713297, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23c23145f6eac25c28cdcc03d38506a5712a9a8cffb5bd5301457f6173ea696e", - "start": 128456, - "end": 129511, + "start": 131496, + "end": 132551, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3044,15 +3281,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "0621878e61f0d0fda054bcbe02df75192c28bde1ecc8289cbd86aeba2dd72720", - "start": 27097, - "end": 28168, + "start": 29061, + "end": 30132, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3066,15 +3303,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e", - "start": 95081, - "end": 96147, + "start": 98121, + "end": 99187, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3088,43 +3325,43 @@ "members": [ { "sha256": "85b7daebe41ecc11f8cfd5fce1f654a73fdadec09f89fd61a9e0b2e3409865eb", - "start": 459488, - "end": 460973, + "start": 489107, + "end": 490592, "origin": "crate-archive", "path": "LICENSE" }, { "sha256": "c5d2b95c2538d2e41d8eedd9ecc750cd5fee085c7bb2bc3e2f95f4e59cc0b10d", - "start": 613842, - "end": 636796, + "start": 648801, + "end": 671755, "origin": "crate-archive", "path": "flac/COPYING.FDL" }, { "sha256": "8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643", - "start": 439267, - "end": 457359, + "start": 468886, + "end": 486978, "origin": "crate-archive", "path": "flac/COPYING.GPL" }, { "sha256": "5df07007198989c622f5d41de8d703e7bef3d0e79d62e24332ee739a452af62a", - "start": 302269, - "end": 328705, + "start": 327976, + "end": 354412, "origin": "crate-archive", "path": "flac/COPYING.LGPL" }, { "sha256": "7866ee98760fc1f0156b4fe6bf530257e02be487ab3fd94e2b63799dd32d6b2c", - "start": 406679, - "end": 408188, + "start": 436298, + "end": 437807, "origin": "crate-archive", "path": "flac/COPYING.Xiph" }, { "sha256": "d2ab5758336489da61c12cc5bb757da5339c4ae9001f9bb0562b4370249af814", - "start": 683157, - "end": 684623, + "start": 718116, + "end": 719582, "origin": "crate-archive", "path": "ogg/COPYING" } @@ -3138,15 +3375,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "20278f4e2697210305f0a25ef5f3b73fecce789096c0ae55287684ea9282cfcb", - "start": 112121, - "end": 113177, + "start": 115161, + "end": 116217, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3160,15 +3397,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "20278f4e2697210305f0a25ef5f3b73fecce789096c0ae55287684ea9282cfcb", - "start": 112121, - "end": 113177, + "start": 115161, + "end": 116217, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3182,29 +3419,29 @@ "members": [ { "sha256": "3290ae0fbc9ddb77d2239121d710f0bb9d31b3b4744e6d97fe01e652b4c1870b", - "start": 193477, - "end": 194358, + "start": 196517, + "end": 197398, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", - "start": 135910, - "end": 148153, + "start": 138950, + "end": 151193, "origin": "crate-archive", "path": "LICENSE-Apache-2.0_WITH_LLVM-exception" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3218,8 +3455,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -3233,15 +3470,37 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "7dc1552e88f49132cb358b1b962fc5e79fa42d70bcbb88c526d33e45b8e98036", - "start": 423942, - "end": 425004, + "start": 453561, + "end": 454623, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "lock_api", + "version": "0.4.14", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "c9a75f18b9ab2927829a208fc6aa2cf4e63b8420887ba29cdb265d6619ae82d5", + "start": 686615, + "end": 687686, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3255,15 +3514,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", - "start": 343504, - "end": 344575, + "start": 369211, + "end": 370282, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3277,20 +3536,35 @@ "members": [ { "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f", - "start": 15052, - "end": 15178, + "start": 15958, + "end": 16084, "origin": "crate-archive", "path": "COPYING" }, { "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f", - "start": 93999, - "end": 95080, + "start": 95963, + "end": 97044, "origin": "crate-archive", "path": "LICENSE-MIT" } ] }, + { + "name": "micromap", + "version": "0.3.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "9387c2d197e2370a740ca457dae1662c56de56b811ee4862024343961307f9bf", + "start": 536585, + "end": 537650, + "origin": "crate-archive", + "path": "LICENSE.txt" + } + ] + }, { "name": "mime", "version": "0.3.17", @@ -3299,15 +3573,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "df9cfd06d8a44d9a671eadd39ffd97f166481da015a30f45dfd27886209c5922", - "start": 691575, - "end": 692633, + "start": 726534, + "end": 727592, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3321,8 +3595,8 @@ "members": [ { "sha256": "816000f9c28f7005d931c77636eb79fa7020071eb0f6e6a995515dfccd3559cb", - "start": 427310, - "end": 428415, + "start": 456929, + "end": 458034, "origin": "crate-archive", "path": "LICENSE" } @@ -3336,22 +3610,22 @@ "members": [ { "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", - "start": 428416, - "end": 439266, + "start": 458035, + "end": 468885, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" }, { "sha256": "dbe1fff0fb1314b6af94f161511406275cf01c5a32441fbf24528a57a051d599", - "start": 688511, - "end": 690514, + "start": 723470, + "end": 725473, "origin": "crate-archive", "path": "LICENSE.md" } @@ -3365,29 +3639,29 @@ "members": [ { "sha256": "4108245a1f2df9d4e94df8abed5b4ba0759bb2f9b40a6b939f1be141077ae50b", - "start": 223235, - "end": 224448, + "start": 248942, + "end": 250155, "origin": "crate-archive", "path": "LICENSE" }, { "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "start": 48223, - "end": 58397, + "start": 50187, + "end": 60361, "origin": "crate-archive", "path": "LICENSE-APACHE.md" }, { "sha256": "799e9ca9d179295ef372f25d3769cdda7d25bb2668add6a6a1e22d1e4c678b8d", - "start": 419036, - "end": 420248, + "start": 448655, + "end": 449867, "origin": "crate-archive", "path": "LICENSE-MIT.md" }, { "sha256": "0a54e647fe54104658b5e563c04c6f9edf251710e47bce692e0bd990a4ddaa39", - "start": 42835, - "end": 43819, + "start": 44799, + "end": 45783, "origin": "crate-archive", "path": "LICENSE-ZLIB.md" } @@ -3401,8 +3675,8 @@ "members": [ { "sha256": "07919255c7e04793d8ea760d6c2ce32d19f9ff02bdbdde3ce90b1e1880929a9b", - "start": 39513, - "end": 40595, + "start": 41477, + "end": 42559, "origin": "crate-archive", "path": "LICENSE" } @@ -3416,8 +3690,8 @@ "members": [ { "sha256": "c524732311819f888ce57b1b56148feaa6e9b1705fcb158e7e5d71554ccf39ed", - "start": 612727, - "end": 613841, + "start": 647686, + "end": 648800, "origin": "crate-archive", "path": "LICENSE" } @@ -3431,8 +3705,8 @@ "members": [ { "sha256": "4dbda04344456f09a7a588140455413a9ac59b6b26a1ef7cdf9c800c012d87f0", - "start": 269346, - "end": 270411, + "start": 295053, + "end": 296118, "origin": "crate-archive", "path": "LICENSE" } @@ -3446,15 +3720,15 @@ "members": [ { "sha256": "b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1", - "start": 590871, - "end": 602228, + "start": 625830, + "end": 637187, "origin": "crate-archive", "path": "LICENSE-Apache" }, { "sha256": "d848c165548bd3f33419b71c3c3bb7cacec620d93383483393410688cba68431", - "start": 685707, - "end": 686779, + "start": 720666, + "end": 721738, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3468,13 +3742,35 @@ "members": [ { "sha256": "284465860407420254e39be4e1bdcaaf2e7f2d18e06f9bab75bc75341a5d8501", - "start": 172003, - "end": 173160, + "start": 175043, + "end": 176200, "origin": "crate-archive", "path": "LICENSE" } ] }, + { + "name": "num", + "version": "0.4.3", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "start": 369211, + "end": 370282, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, { "name": "num-bigint", "version": "0.4.8", @@ -3483,15 +3779,52 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", - "start": 343504, - "end": 344575, + "start": 369211, + "end": 370282, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "num-cmp", + "version": "0.1.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT/Apache-2.0", + "members": [ + { + "sha256": "3bb1e548250b3acc43febde65a5cdba9f2e0a3288018aa41f009a3c59ffab51a", + "start": 225217, + "end": 245729, + "origin": "crate-archive", + "path": "LICENSE.txt" + } + ] + }, + { + "name": "num-complex", + "version": "0.4.6", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "start": 369211, + "end": 370282, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3505,15 +3838,15 @@ "members": [ { "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "start": 48223, - "end": 58397, + "start": 50187, + "end": 60361, "origin": "crate-archive", "path": "LICENSE-Apache" }, { "sha256": "e2e245f2b566d0bfafb0f6a04c16b82d710c4e6e7d799e39bb4ef6e541b85b98", - "start": 697255, - "end": 698305, + "start": 732214, + "end": 733264, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3527,15 +3860,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", - "start": 343504, - "end": 344575, + "start": 369211, + "end": 370282, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3549,15 +3882,37 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", - "start": 343504, - "end": 344575, + "start": 369211, + "end": 370282, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "num-iter", + "version": "0.1.46", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "start": 369211, + "end": 370282, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3571,15 +3926,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", - "start": 343504, - "end": 344575, + "start": 369211, + "end": 370282, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3593,15 +3948,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", - "start": 343504, - "end": 344575, + "start": 369211, + "end": 370282, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3615,15 +3970,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513", - "start": 534997, - "end": 536057, + "start": 568887, + "end": 569947, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3637,15 +3992,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3659,8 +4014,8 @@ "members": [ { "sha256": "e63690624d604aa626c3c62b6ead1a9f4a916d2260ac8132022acbafe2556045", - "start": 721598, - "end": 722713, + "start": 756557, + "end": 757672, "origin": "crate-archive", "path": "LICENSE.md" } @@ -3674,15 +4029,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3696,20 +4051,35 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } ] }, + { + "name": "outref", + "version": "0.5.2", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "a00511478c83ebb464ef19d840f061c7444adc4636bb561900e75c5808cfc6cc", + "start": 567823, + "end": 568885, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, { "name": "p256", "version": "0.13.2", @@ -3718,15 +4088,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "233b95ccbf90dc67e32f3e8995c489f6312d9191ebd141a931c3b684f1e3be6d", - "start": 127385, - "end": 128455, + "start": 130425, + "end": 131495, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3740,27 +4110,71 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" }, { "sha256": "9a6d7a3c1b8edcc706c4b6c37f8dae9a77f02c7f9051df603f975db136b55419", - "start": 518201, - "end": 518652, + "start": 549959, + "end": 550410, "origin": "crate-archive", "path": "LICENSE-THIRD-PARTY" } ] }, + { + "name": "parking_lot", + "version": "0.12.5", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "c9a75f18b9ab2927829a208fc6aa2cf4e63b8420887ba29cdb265d6619ae82d5", + "start": 686615, + "end": 687686, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "parking_lot_core", + "version": "0.9.12", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "c9a75f18b9ab2927829a208fc6aa2cf4e63b8420887ba29cdb265d6619ae82d5", + "start": 686615, + "end": 687686, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, { "name": "password-hash", "version": "0.5.0", @@ -3769,15 +4183,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "233b95ccbf90dc67e32f3e8995c489f6312d9191ebd141a931c3b684f1e3be6d", - "start": 127385, - "end": 128455, + "start": 130425, + "end": 131495, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3791,15 +4205,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3813,8 +4227,8 @@ "members": [ { "sha256": "b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf", - "start": 604490, - "end": 605575, + "start": 639449, + "end": 640534, "origin": "crate-archive", "path": "LICENSE.md" } @@ -3828,15 +4242,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", - "start": 589298, - "end": 590370, + "start": 624257, + "end": 625329, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3850,8 +4264,8 @@ "members": [ { "sha256": "f71452d91682fdae0ca948b18cfed53a953d1237740dd80371c76a5c1c7bbe91", - "start": 747334, - "end": 748395, + "start": 783365, + "end": 784426, "origin": "crate-archive", "path": "LICENSE" } @@ -3865,15 +4279,15 @@ "members": [ { "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "start": 48223, - "end": 58397, + "start": 50187, + "end": 60361, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3887,15 +4301,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3909,15 +4323,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "ad64fcb9589f162720f3cc5010ad76ca6ad3764e11861f9192c489df176bb71d", - "start": 583070, - "end": 584152, + "start": 618029, + "end": 619111, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3931,15 +4345,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", - "start": 218983, - "end": 220040, + "start": 222023, + "end": 223080, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3953,15 +4367,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "eaf40297c75da471f7cda1f3458e8d91b4b2ec866e609527a13acfa93b638652", - "start": 723787, - "end": 724835, + "start": 758746, + "end": 759794, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3975,15 +4389,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -3997,15 +4411,15 @@ "members": [ { "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", - "start": 666980, - "end": 678338, + "start": 701939, + "end": 713297, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23c23145f6eac25c28cdcc03d38506a5712a9a8cffb5bd5301457f6173ea696e", - "start": 128456, - "end": 129511, + "start": 131496, + "end": 132551, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4019,8 +4433,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -4034,15 +4448,15 @@ "members": [ { "sha256": "155420c6403d4e0fca34105e3c03fdd6939b64c393c7ec6f95f5b72c5474eab0", - "start": 96148, - "end": 107497, + "start": 99188, + "end": 110537, "origin": "crate-archive", "path": "LICENSE-Apache" }, { "sha256": "070dbc7dda03a29296f2d58bdb9b7331af90f2abc9f31df22875d1eabaf29852", - "start": 28169, - "end": 29231, + "start": 30133, + "end": 31195, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4056,15 +4470,15 @@ "members": [ { "sha256": "0218327e7a480793ffdd4eb792379a9709e5c135c7ba267f709d6f6d4d70af0a", - "start": 15179, - "end": 26033, + "start": 16085, + "end": 26939, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "4cada0bd02ea3692eee6f16400d86c6508bbd3bafb2b65fed0419f36d4f83e8f", - "start": 256941, - "end": 258017, + "start": 282648, + "end": 283724, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4078,15 +4492,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "233b95ccbf90dc67e32f3e8995c489f6312d9191ebd141a931c3b684f1e3be6d", - "start": 127385, - "end": 128455, + "start": 130425, + "end": 131495, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4100,15 +4514,15 @@ "members": [ { "sha256": "8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb", - "start": 475111, - "end": 485971, + "start": 504730, + "end": 515590, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4122,15 +4536,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4144,15 +4558,15 @@ "members": [ { "sha256": "7b25e06666d3797292dbb4b1c3f5ba509ebe02fce102e1dca7ca81dd3cbfbeb7", - "start": 421019, - "end": 422093, + "start": 450638, + "end": 451712, "origin": "crate-archive", "path": "LICENSE" }, { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" } @@ -4166,15 +4580,15 @@ "members": [ { "sha256": "f09adf500d35265ee63ef552f8e61487e09ee549da66eaf0ec7924e3ad7cf14b", - "start": 739348, - "end": 740422, + "start": 774307, + "end": 775381, "origin": "crate-archive", "path": "LICENSE" }, { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" } @@ -4188,8 +4602,8 @@ "members": [ { "sha256": "5b2c207dcf571267ebe0bbdd5bf321f561ddc22ff597e7b363e96f4539507a29", - "start": 300246, - "end": 301351, + "start": 325953, + "end": 327058, "origin": "crate-archive", "path": "LICENSE-MIT.md" } @@ -4203,15 +4617,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4225,22 +4639,22 @@ "members": [ { "sha256": "90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5", - "start": 503190, - "end": 503759, + "start": 533882, + "end": 534451, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab", - "start": 196508, - "end": 206232, + "start": 199548, + "end": 209272, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b", - "start": 113178, - "end": 114295, + "start": 116218, + "end": 117335, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4254,22 +4668,22 @@ "members": [ { "sha256": "90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5", - "start": 503190, - "end": 503759, + "start": 533882, + "end": 534451, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab", - "start": 196508, - "end": 206232, + "start": 199548, + "end": 209272, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b", - "start": 113178, - "end": 114295, + "start": 116218, + "end": 117335, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4283,22 +4697,22 @@ "members": [ { "sha256": "90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5", - "start": 503190, - "end": 503759, + "start": 533882, + "end": 534451, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51", - "start": 367766, - "end": 378048, + "start": 393473, + "end": 403755, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b", - "start": 113178, - "end": 114295, + "start": 116218, + "end": 117335, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4312,22 +4726,22 @@ "members": [ { "sha256": "90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5", - "start": 503190, - "end": 503759, + "start": 533882, + "end": 534451, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51", - "start": 367766, - "end": 378048, + "start": 393473, + "end": 403755, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b", - "start": 113178, - "end": 114295, + "start": 116218, + "end": 117335, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4341,13 +4755,145 @@ "members": [ { "sha256": "2ff0e21d54b189664c884c142f7a608aae1515524b72e6b1ca251577760d44cf", - "start": 179721, - "end": 191391, + "start": 182761, + "end": 194431, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, + { + "name": "ref-cast", + "version": "1.0.26", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "start": 359487, + "end": 369210, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "start": 132552, + "end": 133575, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "ref-cast-impl", + "version": "1.0.26", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "start": 359487, + "end": 369210, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "start": 132552, + "end": 133575, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "referencing", + "version": "0.58.4", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "117829c3ca21efb132d81a44b55363d395ab8eea18526873bc828da4c0e5f038", + "start": 97045, + "end": 98120, "origin": "crate-archive", "path": "LICENSE" } ] }, + { + "name": "regex", + "version": "1.13.1", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "start": 369211, + "end": 370282, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "regex-automata", + "version": "0.4.16", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "start": 369211, + "end": 370282, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "regex-syntax", + "version": "0.8.11", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "start": 369211, + "end": 370282, + "origin": "crate-archive", + "path": "LICENSE-MIT" + }, + { + "sha256": "74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3", + "start": 410724, + "end": 413571, + "origin": "crate-archive", + "path": "src/unicode_tables/LICENSE-UNICODE" + } + ] + }, { "name": "reqwest", "version": "0.12.28", @@ -4356,15 +4902,15 @@ "members": [ { "sha256": "751963a8b88c0e3a9f27e98933079fbcf09b9998b2c13ac49c2e4d58444520d6", - "start": 383953, - "end": 394786, + "start": 413572, + "end": 424405, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "47d4e1803702728e03d30f8a848ae2249ba274bbd9f8443e803f7924d83cd371", - "start": 253731, - "end": 254794, + "start": 279438, + "end": 280501, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4378,15 +4924,15 @@ "members": [ { "sha256": "a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9", - "start": 547970, - "end": 558143, + "start": 582929, + "end": 593102, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "f5d934dc281b44e0003ee461ac740b18b6629a454decd872c774d34e4ee0b21d", - "start": 746276, - "end": 747333, + "start": 782307, + "end": 783364, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4400,8 +4946,8 @@ "members": [ { "sha256": "457b464bc0fba8a3d67d2458569104f5611869f0c1563a1a12d9c51a0bca9261", - "start": 240775, - "end": 241838, + "start": 266482, + "end": 267545, "origin": "crate-archive", "path": "LICENSE" } @@ -4415,8 +4961,8 @@ "members": [ { "sha256": "b3d734001a94efff3579978d953391aa7115f877657d25eb54037a43875d078a", - "start": 590371, - "end": 590870, + "start": 625330, + "end": 625829, "origin": "crate-archive", "path": "LICENSE" }, @@ -4429,29 +4975,29 @@ }, { "sha256": "f025ccfb7dfb6bdfedc75ca0f67acc69e6fb4998143d834f7c2f38a29989680f", - "start": 738616, - "end": 739347, + "start": 773575, + "end": 774306, "origin": "crate-archive", "path": "LICENSE-other-bits" }, { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "src/polyfill/once_cell/LICENSE-APACHE" }, { "sha256": "6ee2ed6c77710de911761acd5fc1ad1da00f476beb1a7ef27e78c2d1858deafc", - "start": 378049, - "end": 379071, + "start": 403756, + "end": 404778, "origin": "crate-archive", "path": "src/polyfill/once_cell/LICENSE-MIT" }, { "sha256": "9eacbcb81be660840c714a560a9d65ba07913db98dd4baf969f78dd499fdd60f", - "start": 534358, - "end": 534996, + "start": 566116, + "end": 566754, "origin": "crate-archive", "path": "third_party/fiat/LICENSE" } @@ -4465,15 +5011,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "fffe218c1e349e1d482a05677f2ce850502f88298174d77f6ac7c04821db7899", - "start": 754747, - "end": 755830, + "start": 791876, + "end": 792959, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4487,15 +5033,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513", - "start": 534997, - "end": 536057, + "start": 568887, + "end": 569947, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4509,29 +5055,29 @@ "members": [ { "sha256": "377c2e7c53250cc5905c0b0532d35973392af16ffb9596a41d99d202cf3617c9", - "start": 218129, - "end": 218982, + "start": 221169, + "end": 222022, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", - "start": 135910, - "end": 148153, + "start": 138950, + "end": 151193, "origin": "crate-archive", "path": "LICENSE-Apache-2.0_WITH_LLVM-exception" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4545,22 +5091,22 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "7cfafc877eccc46c0e346ccbaa5c51bb6b894d2b818e617d970211e232785ad4", - "start": 423166, - "end": 423941, + "start": 452785, + "end": 453560, "origin": "crate-archive", "path": "LICENSE-ISC" }, { "sha256": "709e3175b4212f7b13aa93971c9f62ff8c69ec45ad8c6532a7e0c41d7a7d6f8c", - "start": 380135, - "end": 381217, + "start": 405842, + "end": 406924, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4574,15 +5120,15 @@ "members": [ { "sha256": "45fd05c4865e7c350b98ad7ac50e1b15462d49af4a91e9b0c9dd933dc9a69742", - "start": 242895, - "end": 253730, + "start": 268602, + "end": 279437, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "9117d922e667125508dde62b02c1f57ed22f5ad21eb536aa2e2d99e1c796e639", - "start": 503760, - "end": 504840, + "start": 534452, + "end": 535532, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4596,8 +5142,8 @@ "members": [ { "sha256": "5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a", - "start": 301352, - "end": 302268, + "start": 327059, + "end": 327975, "origin": "crate-archive", "path": "LICENSE" } @@ -4611,20 +5157,42 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "c9bff75738922193e67fa726fa225535870d2aa1059f91452c411736284ad566", - "start": 652728, - "end": 654066, + "start": 687687, + "end": 689025, "origin": "crate-archive", "path": "LICENSE-BOOST" } ] }, + { + "name": "scopeguard", + "version": "1.2.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "start": 571017, + "end": 581864, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "fb77f0a9c53e473abe5103c8632ef9f0f2874d4fb3f17cb2d8c661aab9cee9d7", + "start": 788623, + "end": 789720, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, { "name": "sd-notify", "version": "0.5.0", @@ -4633,15 +5201,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "56340c5acb4a9359ab88df277780ef29b9388587ebdb43460ad19dc783f75a54", - "start": 286148, - "end": 287209, + "start": 311855, + "end": 312916, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4655,15 +5223,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "4a883ecc3bb1010faed542bf63d53e530fea5e5e12cf676aed588784298ba929", - "start": 255858, - "end": 256940, + "start": 281565, + "end": 282647, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4677,15 +5245,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4699,15 +5267,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4721,15 +5289,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4743,15 +5311,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4765,15 +5333,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4787,15 +5355,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4809,15 +5377,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "b9eb266294324f672cbe945fe8f2e32f85024f0d61a1a7d14382cdde0ac44769", - "start": 606662, - "end": 607720, + "start": 641621, + "end": 642679, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4831,15 +5399,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "b4eb00df6e2a4d22518fcaa6a2b4646f249b3a3c9814509b22bd2091f1392ff1", - "start": 602229, - "end": 603367, + "start": 637188, + "end": 638326, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4853,15 +5421,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "b4eb00df6e2a4d22518fcaa6a2b4646f249b3a3c9814509b22bd2091f1392ff1", - "start": 602229, - "end": 603367, + "start": 637188, + "end": 638326, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4875,8 +5443,8 @@ "members": [ { "sha256": "eafbfa606bc005ed7fd2f623d65af17ffe4ef7c017221ac14405bf4140771ea9", - "start": 724836, - "end": 725893, + "start": 759795, + "end": 760852, "origin": "crate-archive", "path": "LICENSE" } @@ -4890,15 +5458,15 @@ "members": [ { "sha256": "553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583", - "start": 285581, - "end": 286147, + "start": 311288, + "end": 311854, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1", - "start": 239682, - "end": 240774, + "start": 265389, + "end": 266481, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4912,15 +5480,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "503558bfefe66ca15e4e3f7955b3cb0ec87fd52f29bf24b336af7bd00e946d5c", - "start": 271476, - "end": 272544, + "start": 297183, + "end": 298251, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4934,15 +5502,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "b3470648aff02beb36d7a53240fc9260ed80ed93bd43bace6b67d7ef7336ee33", - "start": 588227, - "end": 589297, + "start": 623186, + "end": 624256, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4956,8 +5524,8 @@ "members": [ { "sha256": "42a35170233e83e18856792e748de4c1ce4a63b2afce9a370c89ef3fe23f9f2d", - "start": 235228, - "end": 236306, + "start": 260935, + "end": 262013, "origin": "crate-archive", "path": "LICENSE.md" } @@ -4971,15 +5539,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "0b5f24524360e15bcf9fb79bca875f5aa62f01f94f2988e23238c7ec0e6e8784", - "start": 46037, - "end": 47119, + "start": 48001, + "end": 49083, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -4993,8 +5561,8 @@ "members": [ { "sha256": "c962ee4d1d05ddc138b202b2540219ebc57893fcf97b364852094a9a94ce1365", - "start": 650291, - "end": 650572, + "start": 685250, + "end": 685531, "origin": "crate-archive", "path": "COPYING" } @@ -5008,8 +5576,8 @@ "members": [ { "sha256": "8ce0830173fdac609dfb4ea603fdc002c2f4af0dc9b1a005653f5da9cf534b18", - "start": 498954, - "end": 500009, + "start": 529646, + "end": 530701, "origin": "crate-archive", "path": "LICENSE" } @@ -5023,15 +5591,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9", - "start": 44964, - "end": 46036, + "start": 46928, + "end": 48000, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5045,15 +5613,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", - "start": 218983, - "end": 220040, + "start": 222023, + "end": 223080, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5067,15 +5635,15 @@ "members": [ { "sha256": "a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5", - "start": 560287, - "end": 571136, + "start": 595246, + "end": 606095, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "c995204cc6bad2ed67dd41f7d89bb9f1a9d48e0edd745732b30640d7912089a4", - "start": 650573, - "end": 651655, + "start": 685532, + "end": 686614, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5089,8 +5657,8 @@ "members": [ { "sha256": "e14071c7b7a16d742419ad7f408700719bda2d1f99538f5c3da312d7e772cd22", - "start": 692634, - "end": 693825, + "start": 727593, + "end": 728784, "origin": "crate-archive", "path": "assets/mark_assets/NOTICE.md" } @@ -5104,8 +5672,8 @@ "members": [ { "sha256": "0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0", - "start": 58398, - "end": 92921, + "start": 60362, + "end": 94885, "origin": "committed-override", "path": "LICENSE" } @@ -5119,8 +5687,8 @@ "members": [ { "sha256": "0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0", - "start": 58398, - "end": 92921, + "start": 60362, + "end": 94885, "origin": "committed-override", "path": "LICENSE" } @@ -5134,15 +5702,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7", - "start": 328706, - "end": 329762, + "start": 354413, + "end": 355469, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5156,15 +5724,15 @@ "members": [ { "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", - "start": 666980, - "end": 678338, + "start": 701939, + "end": 713297, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061", - "start": 722714, - "end": 723786, + "start": 757673, + "end": 758745, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5178,8 +5746,8 @@ "members": [ { "sha256": "3085885678bb161f6c472bbd331f66a2550b555eb76128f7f8247779128199c0", - "start": 191392, - "end": 192452, + "start": 194432, + "end": 195492, "origin": "crate-archive", "path": "LICENSE" } @@ -5193,8 +5761,38 @@ "members": [ { "sha256": "1e697ce8d21401fbf1bddd9b5c3fd4c4c79ae1e3bdf51f81761c85e11d5a89cd", - "start": 110954, - "end": 112120, + "start": 113994, + "end": 115160, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, + { + "name": "strum", + "version": "0.28.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42", + "start": 527490, + "end": 528562, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, + { + "name": "strum_macros", + "version": "0.28.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42", + "start": 527490, + "end": 528562, "origin": "crate-archive", "path": "LICENSE" } @@ -5208,8 +5806,8 @@ "members": [ { "sha256": "d1fc1bc0d155df60b2e7705b6b2ae02a05c96f948e1cec6e2fb86360b09f346b", - "start": 679394, - "end": 680976, + "start": 714353, + "end": 715935, "origin": "crate-archive", "path": "LICENSE" } @@ -5223,15 +5821,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "f3c9fe731c701ed77fd5fbaac573872af04dc5869385eb9bd5e3c73c42713814", - "start": 745214, - "end": 746275, + "start": 780173, + "end": 781234, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5245,15 +5843,37 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, + "origin": "crate-archive", + "path": "LICENSE-MIT" + } + ] + }, + { + "name": "syn", + "version": "3.0.2", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT OR Apache-2.0", + "members": [ + { + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "start": 359487, + "end": 369210, + "origin": "crate-archive", + "path": "LICENSE-APACHE" + }, + { + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5267,8 +5887,8 @@ "members": [ { "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "start": 48223, - "end": 58397, + "start": 50187, + "end": 60361, "origin": "crate-archive", "path": "LICENSE" } @@ -5282,8 +5902,8 @@ "members": [ { "sha256": "219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59", - "start": 125269, - "end": 126321, + "start": 128309, + "end": 129361, "origin": "crate-archive", "path": "LICENSE" } @@ -5297,15 +5917,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5319,8 +5939,8 @@ "members": [ { "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", - "start": 135910, - "end": 148153, + "start": 138950, + "end": 151193, "origin": "crate-archive", "path": "LICENSE" } @@ -5334,15 +5954,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5356,15 +5976,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5378,15 +5998,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5400,15 +6020,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5422,15 +6042,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "c9a75f18b9ab2927829a208fc6aa2cf4e63b8420887ba29cdb265d6619ae82d5", - "start": 651656, - "end": 652727, + "start": 686615, + "end": 687686, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5444,15 +6064,15 @@ "members": [ { "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "start": 48223, - "end": 58397, + "start": 50187, + "end": 60361, "origin": "crate-archive", "path": "LICENSE-Apache" }, { "sha256": "2537228d9a1b44a5dc595241349cae7090b326c8de165aaf89bfddef4a00d0fc", - "start": 132700, - "end": 133757, + "start": 135740, + "end": 136797, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5466,15 +6086,15 @@ "members": [ { "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "start": 48223, - "end": 58397, + "start": 50187, + "end": 60361, "origin": "crate-archive", "path": "LICENSE-Apache" }, { "sha256": "2537228d9a1b44a5dc595241349cae7090b326c8de165aaf89bfddef4a00d0fc", - "start": 132700, - "end": 133757, + "start": 135740, + "end": 136797, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5488,15 +6108,15 @@ "members": [ { "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "start": 48223, - "end": 58397, + "start": 50187, + "end": 60361, "origin": "crate-archive", "path": "LICENSE-Apache" }, { "sha256": "2537228d9a1b44a5dc595241349cae7090b326c8de165aaf89bfddef4a00d0fc", - "start": 132700, - "end": 133757, + "start": 135740, + "end": 136797, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5510,8 +6130,8 @@ "members": [ { "sha256": "6d41e05f1d54fe726997cb0334ec539f1dff50600b1d5d90462ec2b392770ced", - "start": 366201, - "end": 367765, + "start": 391908, + "end": 393472, "origin": "crate-archive", "path": "LICENSE" } @@ -5525,8 +6145,8 @@ "members": [ { "sha256": "6d41e05f1d54fe726997cb0334ec539f1dff50600b1d5d90462ec2b392770ced", - "start": 366201, - "end": 367765, + "start": 391908, + "end": 393472, "origin": "crate-archive", "path": "LICENSE" } @@ -5540,8 +6160,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -5555,8 +6175,8 @@ "members": [ { "sha256": "253cd04c6714889df2d32f3f64d669179a1c95c76ac43c40882c52eb06bc3552", - "start": 133758, - "end": 134828, + "start": 136798, + "end": 137868, "origin": "crate-archive", "path": "LICENSE" } @@ -5570,8 +6190,8 @@ "members": [ { "sha256": "0b83dc40cba89b9922bb84b0a9c7d2768ce37c1d7e138b7424fd4549915778c9", - "start": 47120, - "end": 48222, + "start": 49084, + "end": 50186, "origin": "crate-archive", "path": "LICENSE" } @@ -5585,15 +6205,15 @@ "members": [ { "sha256": "cc117d90b498b32b11a886f279b359da16a73c3b01efbb2f5cc004b20262334e", - "start": 655091, - "end": 665923, + "start": 690050, + "end": 700882, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "e20fa2b8e0a2565f24a792b94b4bf4b6c2b9d36f781d8a9516e218a036e6677a", - "start": 693826, - "end": 694882, + "start": 728785, + "end": 729841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5607,8 +6227,8 @@ "members": [ { "sha256": "fdd55e2b2da854b0fbdc1e607df7c2ba1e1ebf91ecb77c515511ebeef972bc8f", - "start": 752592, - "end": 753685, + "start": 789721, + "end": 790814, "origin": "crate-archive", "path": "LICENSE" } @@ -5622,8 +6242,8 @@ "members": [ { "sha256": "253cd04c6714889df2d32f3f64d669179a1c95c76ac43c40882c52eb06bc3552", - "start": 133758, - "end": 134828, + "start": 136798, + "end": 137868, "origin": "crate-archive", "path": "LICENSE" } @@ -5637,15 +6257,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5659,15 +6279,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5681,15 +6301,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5703,15 +6323,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5725,15 +6345,15 @@ "members": [ { "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", - "start": 636798, - "end": 648156, + "start": 671757, + "end": 683115, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6", - "start": 379072, - "end": 380134, + "start": 404779, + "end": 405841, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5747,8 +6367,8 @@ "members": [ { "sha256": "4249c8e6c5ebb85f97c77e6457c6fafc1066406eb8f1ef61e796fbdc5ff18482", - "start": 234165, - "end": 235227, + "start": 259872, + "end": 260934, "origin": "crate-archive", "path": "LICENSE" } @@ -5762,8 +6382,8 @@ "members": [ { "sha256": "5049cf464977eff4b4fcfa7988d84e74116956a3eb9d5f1d451b3f828f945233", - "start": 272545, - "end": 273612, + "start": 298252, + "end": 299319, "origin": "crate-archive", "path": "LICENSE" } @@ -5777,8 +6397,8 @@ "members": [ { "sha256": "4249c8e6c5ebb85f97c77e6457c6fafc1066406eb8f1ef61e796fbdc5ff18482", - "start": 234165, - "end": 235227, + "start": 259872, + "end": 260934, "origin": "crate-archive", "path": "LICENSE" } @@ -5792,8 +6412,8 @@ "members": [ { "sha256": "4249c8e6c5ebb85f97c77e6457c6fafc1066406eb8f1ef61e796fbdc5ff18482", - "start": 234165, - "end": 235227, + "start": 259872, + "end": 260934, "origin": "crate-archive", "path": "LICENSE" } @@ -5807,8 +6427,8 @@ "members": [ { "sha256": "898b1ae9821e98daf8964c8d6c7f61641f5f5aa78ad500020771c0939ee0dea1", - "start": 474048, - "end": 475110, + "start": 503667, + "end": 504729, "origin": "crate-archive", "path": "LICENSE" } @@ -5822,8 +6442,8 @@ "members": [ { "sha256": "898b1ae9821e98daf8964c8d6c7f61641f5f5aa78ad500020771c0939ee0dea1", - "start": 474048, - "end": 475110, + "start": 503667, + "end": 504729, "origin": "crate-archive", "path": "LICENSE" } @@ -5837,15 +6457,15 @@ "members": [ { "sha256": "898b1ae9821e98daf8964c8d6c7f61641f5f5aa78ad500020771c0939ee0dea1", - "start": 474048, - "end": 475110, + "start": 503667, + "end": 504729, "origin": "crate-archive", "path": "LICENSE" }, { "sha256": "58545fed1565e42d687aecec6897d35c6d37ccb71479a137c0deb2203e125c79", - "start": 288310, - "end": 289395, + "start": 314017, + "end": 315102, "origin": "crate-archive", "path": "src/spin/LICENSE" } @@ -5859,8 +6479,8 @@ "members": [ { "sha256": "898b1ae9821e98daf8964c8d6c7f61641f5f5aa78ad500020771c0939ee0dea1", - "start": 474048, - "end": 475110, + "start": 503667, + "end": 504729, "origin": "crate-archive", "path": "LICENSE" } @@ -5874,8 +6494,8 @@ "members": [ { "sha256": "898b1ae9821e98daf8964c8d6c7f61641f5f5aa78ad500020771c0939ee0dea1", - "start": 474048, - "end": 475110, + "start": 503667, + "end": 504729, "origin": "crate-archive", "path": "LICENSE" } @@ -5889,8 +6509,8 @@ "members": [ { "sha256": "c816a0749cdc6bf062a5111c159723de51b2bfac66a1dac2655abd9e6b1583eb", - "start": 649194, - "end": 650290, + "start": 684153, + "end": 685249, "origin": "crate-archive", "path": "LICENSE" } @@ -5904,15 +6524,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "7fea0ee51a4ca5d5cea7464135fd55e8b09caf3a61da3d451ac8a22af95c033f", - "start": 426217, - "end": 427309, + "start": 455836, + "end": 456928, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5926,22 +6546,22 @@ "members": [ { "sha256": "db11fec9946737df39ca3898d9cd8c10ec6f6c3a884a6802b0ad0b81b4e8f23a", - "start": 688492, - "end": 688509, + "start": 723451, + "end": 723468, "origin": "crate-archive", "path": "LICENSE" }, { "sha256": "516b24e051bf5630880ebbd55c40a25ce9552ebaf8970a53e8976eb70e522406", - "start": 273613, - "end": 284448, + "start": 299320, + "end": 310155, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "a825bd853ab71619a4923d7b4311221427848070ff44d990da39b0b274c1683f", - "start": 559203, - "end": 560286, + "start": 594162, + "end": 595245, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -5955,20 +6575,35 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "4f6bd11a0f17fe5b085ace1daaedb7b002a08316850dd9577c246c6a68a68e8c", - "start": 270412, - "end": 271475, + "start": 296119, + "end": 297182, "origin": "crate-archive", "path": "LICENSE-MIT" } ] }, + { + "name": "unicode-general-category", + "version": "1.1.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "Apache-2.0", + "members": [ + { + "sha256": "b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1", + "start": 625830, + "end": 637187, + "origin": "crate-archive", + "path": "LICENSE" + } + ] + }, { "name": "unicode-ident", "version": "1.0.24", @@ -5977,22 +6612,22 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" }, { "sha256": "f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1", - "start": 749522, - "end": 751517, + "start": 785553, + "end": 787548, "origin": "crate-archive", "path": "LICENSE-UNICODE" } @@ -6006,8 +6641,8 @@ "members": [ { "sha256": "7abd9b6960dcf7d4d0a48606a5b71bfe37d472db68d70637f3a58a56785f1621", - "start": 420249, - "end": 421018, + "start": 449868, + "end": 450637, "origin": "crate-archive", "path": "LICENSE.txt" } @@ -6021,15 +6656,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", - "start": 589298, - "end": 590370, + "start": 624257, + "end": 625329, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6043,15 +6678,15 @@ "members": [ { "sha256": "a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9", - "start": 547970, - "end": 558143, + "start": 582929, + "end": 593102, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "f5d934dc281b44e0003ee461ac740b18b6629a454decd872c774d34e4ee0b21d", - "start": 746276, - "end": 747333, + "start": 782307, + "end": 783364, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6065,15 +6700,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6087,22 +6722,22 @@ "members": [ { "sha256": "c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c", - "start": 610984, - "end": 612726, + "start": 645943, + "end": 647685, "origin": "crate-archive", "path": "COPYRIGHT" }, { "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", - "start": 666980, - "end": 678338, + "start": 701939, + "end": 713297, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c", - "start": 221105, - "end": 222158, + "start": 246812, + "end": 247865, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6116,15 +6751,15 @@ "members": [ { "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", - "start": 333780, - "end": 343503, + "start": 359487, + "end": 369210, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "e4c9b06fa850cb9b540a5e400e9f6394cf15efcf4098144de477d1d3dae10150", - "start": 720545, - "end": 721597, + "start": 755504, + "end": 756556, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6138,20 +6773,35 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d", - "start": 238524, - "end": 239681, + "start": 264231, + "end": 265388, "origin": "crate-archive", "path": "LICENSE-MIT" } ] }, + { + "name": "uuid-simd", + "version": "0.8.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "71674605ec4c087fe9eb534e3e4f9e26eb2e4aabcd76a29fd156c6a844d44b3d", + "start": 406925, + "end": 407987, + "origin": "committed-override", + "path": "LICENSE" + } + ] + }, { "name": "version-compare", "version": "0.2.1", @@ -6160,8 +6810,8 @@ "members": [ { "sha256": "cedfcc7ace1639adb054bd4a19b6f8585ccb2429a60b4bf51bfeb0601058c22c", - "start": 665924, - "end": 666979, + "start": 700883, + "end": 701938, "origin": "crate-archive", "path": "LICENSE" } @@ -6175,20 +6825,35 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e", - "start": 605576, - "end": 606661, + "start": 640535, + "end": 641620, "origin": "crate-archive", "path": "LICENSE-MIT" } ] }, + { + "name": "vsimd", + "version": "0.8.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "license": "MIT", + "members": [ + { + "sha256": "71674605ec4c087fe9eb534e3e4f9e26eb2e4aabcd76a29fd156c6a844d44b3d", + "start": 406925, + "end": 407987, + "origin": "committed-override", + "path": "LICENSE" + } + ] + }, { "name": "want", "version": "0.3.1", @@ -6197,8 +6862,8 @@ "members": [ { "sha256": "a65f5d0a945d267751344c95665945b90c030ea107faf5c85d518929886187da", - "start": 546906, - "end": 547969, + "start": 581865, + "end": 582928, "origin": "crate-archive", "path": "LICENSE" } @@ -6212,8 +6877,8 @@ "members": [ { "sha256": "1cb6f4dee8c649f175736380495f0af0c753c87f22d50931692968bb24bf2161", - "start": 109896, - "end": 110953, + "start": 112936, + "end": 113993, "origin": "crate-archive", "path": "LICENSE.txt" } @@ -6227,8 +6892,8 @@ "members": [ { "sha256": "1cb6f4dee8c649f175736380495f0af0c753c87f22d50931692968bb24bf2161", - "start": 109896, - "end": 110953, + "start": 112936, + "end": 113993, "origin": "crate-archive", "path": "LICENSE.txt" } @@ -6242,15 +6907,15 @@ "members": [ { "sha256": "1cb6f4dee8c649f175736380495f0af0c753c87f22d50931692968bb24bf2161", - "start": 109896, - "end": 110953, + "start": 112936, + "end": 113993, "origin": "crate-archive", "path": "LICENSE.txt" }, { "sha256": "f1a2b233e8a9a71c40f4aa885be08a0842ac85bb8588703c1dd7e6e6502e3124", - "start": 740423, - "end": 741925, + "start": 775382, + "end": 776884, "origin": "crate-archive", "path": "protocols/COPYING" } @@ -6264,8 +6929,8 @@ "members": [ { "sha256": "1cb6f4dee8c649f175736380495f0af0c753c87f22d50931692968bb24bf2161", - "start": 109896, - "end": 110953, + "start": 112936, + "end": 113993, "origin": "crate-archive", "path": "LICENSE.txt" } @@ -6279,8 +6944,8 @@ "members": [ { "sha256": "1cb6f4dee8c649f175736380495f0af0c753c87f22d50931692968bb24bf2161", - "start": 109896, - "end": 110953, + "start": 112936, + "end": 113993, "origin": "crate-archive", "path": "LICENSE.txt" } @@ -6294,8 +6959,8 @@ "members": [ { "sha256": "e271993808fec50ab29350b39539cdec611a9103f827e0aa26d61da70e2d33f8", - "start": 694883, - "end": 697254, + "start": 729842, + "end": 732213, "origin": "crate-archive", "path": "LICENSE" } @@ -6309,8 +6974,8 @@ "members": [ { "sha256": "cb5aedb296c5246d1f22e9099f925a65146f9f0d6b4eebba97fd27a6cdbbab2d", - "start": 654067, - "end": 655090, + "start": 689026, + "end": 690049, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6324,8 +6989,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -6339,15 +7004,15 @@ "members": [ { "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", - "start": 666980, - "end": 678338, + "start": 701939, + "end": 713297, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "a72561f47c1f665535930f606739d9098a6186802d5e2e8ec491faf00f4add5b", - "start": 558144, - "end": 559202, + "start": 593103, + "end": 594161, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6361,15 +7026,15 @@ "members": [ { "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", - "start": 666980, - "end": 678338, + "start": 701939, + "end": 713297, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "a72561f47c1f665535930f606739d9098a6186802d5e2e8ec491faf00f4add5b", - "start": 558144, - "end": 559202, + "start": 593103, + "end": 594161, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6383,15 +7048,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513", - "start": 534997, - "end": 536057, + "start": 568887, + "end": 569947, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6405,8 +7070,8 @@ "members": [ { "sha256": "2cdce1ff4ca8f455ef169691ea493407e23c32ecdea5fd9b3a6fc82be5b49b5a", - "start": 175348, - "end": 176430, + "start": 178388, + "end": 179470, "origin": "crate-archive", "path": "LICENSE" } @@ -6420,15 +7085,15 @@ "members": [ { "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", - "start": 536058, - "end": 546905, + "start": 571017, + "end": 581864, "origin": "committed-override", "path": "LICENSE-APACHE" }, { "sha256": "2a7df90e60fd5512b8bca35d3ce90e068f21c52d962855053fd1d9e2e7ca0f16", - "start": 174292, - "end": 175347, + "start": 177332, + "end": 178387, "origin": "committed-override", "path": "LICENSE-MIT" } @@ -6442,8 +7107,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -6457,8 +7122,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -6472,8 +7137,8 @@ "members": [ { "sha256": "260e4ff3fc493695880c90774283eb1e3d018f898fabe655bbd52dd2cbd2fcad", - "start": 134829, - "end": 135909, + "start": 137869, + "end": 138949, "origin": "crate-archive", "path": "LICENSE" } @@ -6487,8 +7152,8 @@ "members": [ { "sha256": "260e4ff3fc493695880c90774283eb1e3d018f898fabe655bbd52dd2cbd2fcad", - "start": 134829, - "end": 135909, + "start": 137869, + "end": 138949, "origin": "crate-archive", "path": "LICENSE" } @@ -6502,8 +7167,8 @@ "members": [ { "sha256": "260e4ff3fc493695880c90774283eb1e3d018f898fabe655bbd52dd2cbd2fcad", - "start": 134829, - "end": 135909, + "start": 137869, + "end": 138949, "origin": "crate-archive", "path": "LICENSE" } @@ -6517,22 +7182,22 @@ "members": [ { "sha256": "9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3", - "start": 519775, - "end": 531125, + "start": 551533, + "end": 562883, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32", - "start": 457360, - "end": 458635, + "start": 486979, + "end": 488254, "origin": "crate-archive", "path": "LICENSE-BSD" }, { "sha256": "1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df", - "start": 107498, - "end": 108558, + "start": 110538, + "end": 111598, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6546,8 +7211,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -6561,8 +7226,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -6576,15 +7241,15 @@ "members": [ { "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", - "start": 666980, - "end": 678338, + "start": 701939, + "end": 713297, "origin": "crate-archive", "path": "LICENSE-APACHE" }, { "sha256": "8c7516d4b27b1e495be5e38b612298b63de48d05f49cdac94f70f3cd70f8864b", - "start": 497871, - "end": 498953, + "start": 528563, + "end": 529645, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6598,8 +7263,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -6613,8 +7278,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -6628,8 +7293,8 @@ "members": [ { "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", - "start": 743018, - "end": 745213, + "start": 777977, + "end": 780172, "origin": "crate-archive", "path": "LICENSE" } @@ -6643,8 +7308,8 @@ "members": [ { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE-MIT" } @@ -6658,8 +7323,8 @@ "members": [ { "sha256": "260e4ff3fc493695880c90774283eb1e3d018f898fabe655bbd52dd2cbd2fcad", - "start": 134829, - "end": 135909, + "start": 137869, + "end": 138949, "origin": "crate-archive", "path": "LICENSE" } @@ -6673,8 +7338,8 @@ "members": [ { "sha256": "260e4ff3fc493695880c90774283eb1e3d018f898fabe655bbd52dd2cbd2fcad", - "start": 134829, - "end": 135909, + "start": 137869, + "end": 138949, "origin": "crate-archive", "path": "LICENSE" } @@ -6688,8 +7353,8 @@ "members": [ { "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", - "start": 129512, - "end": 130535, + "start": 132552, + "end": 133575, "origin": "crate-archive", "path": "LICENSE" } diff --git a/vendor/contracts/native-browser/adoption.json b/vendor/contracts/native-browser/adoption.json new file mode 100644 index 0000000..b800b61 --- /dev/null +++ b/vendor/contracts/native-browser/adoption.json @@ -0,0 +1,10 @@ +{ + "bundle_path": "contracts/native-browser", + "source_revision": "16ad2636cec6c447e3ca0f858234d6c786cd045c", + "bundle_version": "1.1.0", + "wire_protocol": 1, + "manifest_sha256": "fef157d57a561bd72714d049245ede7317821ca790558ca917aef2efb9df4b73", + "journal_schema_id": "solstone-journal-format:browser-jsonl", + "journal_schema_sha256": "c14e66318587b8fa451ef1363e113885bdd80a41ce680b0c62fcc9033460a2e1", + "journal_schema_revision": "3b99f1af61d4e82c5c9c3748d90c1242c28c1e5b" +} diff --git a/vendor/contracts/native-browser/adoption.schema.json b/vendor/contracts/native-browser/adoption.schema.json new file mode 100644 index 0000000..bef1481 --- /dev/null +++ b/vendor/contracts/native-browser/adoption.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "solstone-native-browser:adoption", + "title": "Solstone Native Browser Adoption Pin", + "type": "object", + "additionalProperties": false, + "required": [ + "bundle_path", + "source_revision", + "bundle_version", + "wire_protocol", + "manifest_sha256", + "journal_schema_id", + "journal_schema_sha256", + "journal_schema_revision" + ], + "properties": { + "bundle_path": { + "const": "contracts/native-browser" + }, + "source_revision": { + "type": "string", + "minLength": 1 + }, + "bundle_version": { + "const": "1.1.0" + }, + "wire_protocol": { + "const": 1 + }, + "manifest_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "journal_schema_id": { + "const": "solstone-journal-format:browser-jsonl" + }, + "journal_schema_sha256": { + "const": "c14e66318587b8fa451ef1363e113885bdd80a41ce680b0c62fcc9033460a2e1" + }, + "journal_schema_revision": { + "const": "3b99f1af61d4e82c5c9c3748d90c1242c28c1e5b" + } + } +} diff --git a/vendor/contracts/native-browser/authority.json b/vendor/contracts/native-browser/authority.json new file mode 100644 index 0000000..1426e68 --- /dev/null +++ b/vendor/contracts/native-browser/authority.json @@ -0,0 +1,328 @@ +{ + "bundle_version": "1.1.0", + "wire_protocol": 1, + "journal": { + "id": "solstone-journal-format:browser-jsonl", + "revision": "3b99f1af61d4e82c5c9c3748d90c1242c28c1e5b", + "path": "contracts/native-browser/browser.schema.json", + "sha256": "c14e66318587b8fa451ef1363e113885bdd80a41ce680b0c62fcc9033460a2e1" + }, + "caps": { + "extension_to_host": 33554432, + "host_to_extension": 65536, + "control": 65536, + "delta_records": 3000, + "batch_id_hex": 32, + "json_max_depth": 127 + }, + "policy": { + "file": 50331648, + "outbox_bytes": 67108864, + "outbox_age_ms": 600000, + "spool_bytes": 536870912, + "spool_age_ms": 604800000, + "future_skew_ms": 60000, + "accepted_retention_ms": 1200000, + "handshake_ms": 5000, + "state_renewal_ms": 5000, + "freshness_max_ms": 15000, + "partial_frame_ms": 30000, + "timestamp_max": 9007199254740991 + }, + "directions": { + "extension_to_host": [ + "hello", + "batch" + ], + "host_to_extension": [ + "hello_ack", + "unsupported", + "state", + "boundary", + "accepted", + "bye" + ] + }, + "enums": { + "brand": [ + "chrome", + "edge", + "firefox" + ], + "capture": [ + "unavailable", + "not_paired", + "permitted", + "paused", + "intake_off" + ], + "delivery": [ + "unknown", + "kept_locally", + "delivered", + "idle", + "failed" + ], + "failure": [ + "relay_unavailable", + "journal_rejected", + "local_io", + "resource_exhausted", + "queue_full", + "age_policy", + "unaccepted_lost" + ], + "bye_reason": [ + "shutdown", + "replaced", + "update" + ], + "snapshot_reason": [ + "delivery_recovery" + ], + "behind": [ + "app", + "extension" + ], + "result": [ + "accepted", + "duplicate", + "rejected" + ] + }, + "hosts_and_ids": { + "production": { + "host": "app.solstone.browser", + "chrome_id": "eibbeeoifjoabddfmgeggnageolkcnim", + "edge_id": "eibbeeoifjoabddfmgeggnageolkcnim", + "firefox_id": "browser@solstone.app" + }, + "dev": { + "host": "app.solstone.browser.dev", + "chrome_id": "fgfnkcefedeheoeamppkiiloncfekakf", + "edge_id": "fgfnkcefedeheoeamppkiiloncfekakf", + "firefox_id": "browser.dev@solstone.app" + } + }, + "string_bounds": { + "version": 64, + "generation": 128, + "period_id": 128, + "failure_code": 64 + }, + "registration": { + "description": "Solstone browser host", + "type": "stdio", + "path_placeholder": "__PATH__", + "suffixes": { + "chrome_linux": ".config/google-chrome/NativeMessagingHosts/.json", + "edge_linux": ".config/microsoft-edge/NativeMessagingHosts/.json", + "firefox_linux": ".mozilla/native-messaging-hosts/.json", + "chrome_macos": "Library/Application Support/Google/Chrome/NativeMessagingHosts/.json", + "edge_macos": "Library/Application Support/Microsoft Edge/NativeMessagingHosts/.json", + "firefox_macos": "Library/Application Support/Mozilla/NativeMessagingHosts/.json", + "chrome_windows": "Software\\Google\\Chrome\\NativeMessagingHosts\\", + "edge_windows": "Software\\Microsoft\\Edge\\NativeMessagingHosts\\", + "firefox_windows": "Software\\Mozilla\\NativeMessagingHosts\\" + }, + "argv": { + "chrome_macos": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "chrome_linux": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "chrome_windows": { + "arguments_after_executable": [ + "origin", + "parent_window" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "edge_macos": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "edge_linux": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "edge_windows": { + "arguments_after_executable": [ + "origin", + "parent_window" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "firefox_macos": { + "arguments_after_executable": [ + "manifest_path", + "extension_id" + ], + "identity_argument": 1, + "identity_source": "allowed_extensions" + }, + "firefox_linux": { + "arguments_after_executable": [ + "manifest_path", + "extension_id" + ], + "identity_argument": 1, + "identity_source": "allowed_extensions" + }, + "firefox_windows": { + "arguments_after_executable": [ + "manifest_path", + "extension_id" + ], + "identity_argument": 1, + "identity_source": "allowed_extensions" + } + }, + "windows": { + "hive": "HKEY_CURRENT_USER", + "value_name": "", + "value_type": "REG_SZ", + "value": "absolute_manifest_path", + "registry_views": [ + "32", + "64" + ], + "path_contains_view": false + } + }, + "receipt_classes": { + "retryable": [ + "snapshot_required", + "resource_exhausted", + "queue_full", + "age_policy" + ], + "permanent": [ + "malformed", + "oversize", + "stale_generation", + "expired_unaccepted", + "unaccepted_lost" + ] + }, + "canonical_key_order": { + "hello": [ + "type", + "protocol", + "version", + "brand", + "inst" + ], + "hello_ack": [ + "type", + "capture", + "delivery", + "freshness_ms", + "destination_generation", + "period_id", + "failure", + "custody", + "version" + ], + "unsupported": [ + "type", + "protocol", + "behind" + ], + "state": [ + "type", + "capture", + "delivery", + "freshness_ms", + "destination_generation", + "period_id", + "failure", + "custody", + "version" + ], + "batch": [ + "type", + "destination_generation", + "inst", + "batch_id", + "queued_at_ms", + "records" + ], + "boundary": [ + "type", + "destination_generation", + "period_id" + ], + "accepted": [ + "type", + "result", + "destination_generation", + "inst", + "batch_id", + "period_id", + "reason", + "class" + ], + "bye": [ + "type", + "reason" + ], + "snapshot_record": [ + "t", + "ts", + "rel", + "site", + "url", + "title", + "adapter", + "ctx", + "inst", + "n", + "blocks", + "snapshot_reason" + ], + "delta_record": [ + "t", + "ts", + "rel", + "site", + "ctx", + "inst", + "op", + "block" + ], + "block": [ + "id", + "text", + "type", + "depth", + "attrs" + ], + "block_attrs": [ + "label", + "level", + "linkHost" + ] + }, + "json_depth": { + "count": "object_and_array_containers_including_root", + "overflow_code": "bad_json" + } +} diff --git a/vendor/contracts/native-browser/browser.schema.json b/vendor/contracts/native-browser/browser.schema.json new file mode 100644 index 0000000..616b6da --- /dev/null +++ b/vendor/contracts/native-browser/browser.schema.json @@ -0,0 +1,305 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "solstone-journal-format:browser-jsonl", + "title": "Browser stream JSONL", + "oneOf": [ + { + "$ref": "#/$defs/snapshot" + }, + { + "$ref": "#/$defs/delta" + } + ], + "$defs": { + "timestamp": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "textBlock": { + "type": "object", + "additionalProperties": true, + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "maxLength": 2001 + }, + "id": { + "$ref": "#/$defs/idString" + }, + "type": { + "$ref": "#/$defs/typeString" + }, + "attrs": { + "$ref": "#/$defs/blockAttributes" + }, + "depth": { + "$ref": "#/$defs/blockDepth" + } + } + }, + "removeBlock": { + "type": "object", + "additionalProperties": true, + "required": [ + "id" + ], + "properties": { + "id": { + "$ref": "#/$defs/idString" + }, + "text": { + "type": "string", + "maxLength": 2001 + }, + "type": { + "$ref": "#/$defs/typeString" + }, + "depth": { + "$ref": "#/$defs/blockDepth" + }, + "attrs": { + "$ref": "#/$defs/blockAttributes" + } + } + }, + "snapshot": { + "type": "object", + "additionalProperties": true, + "required": [ + "t", + "ts", + "blocks" + ], + "properties": { + "t": { + "const": "segment_start" + }, + "ts": { + "$ref": "#/$defs/timestamp" + }, + "inst": { + "$ref": "#/$defs/instString" + }, + "ctx": { + "$ref": "#/$defs/ctxString" + }, + "title": { + "$ref": "#/$defs/titleString" + }, + "url": { + "$ref": "#/$defs/urlString" + }, + "site": { + "$ref": "#/$defs/siteString" + }, + "adapter": { + "$ref": "#/$defs/adapterString" + }, + "rel": { + "type": "number" + }, + "n": { + "type": "integer", + "minimum": 0, + "maximum": 1500 + }, + "blocks": { + "type": "array", + "maxItems": 1500, + "items": { + "$ref": "#/$defs/textBlock" + } + } + } + }, + "addUpdateDelta": { + "type": "object", + "additionalProperties": true, + "required": [ + "t", + "ts", + "op", + "block" + ], + "properties": { + "t": { + "const": "delta" + }, + "ts": { + "$ref": "#/$defs/timestamp" + }, + "op": { + "enum": [ + "add", + "update" + ] + }, + "block": { + "$ref": "#/$defs/textBlock" + }, + "inst": { + "$ref": "#/$defs/instString" + }, + "ctx": { + "$ref": "#/$defs/ctxString" + }, + "title": { + "$ref": "#/$defs/titleString" + }, + "url": { + "$ref": "#/$defs/urlString" + }, + "site": { + "$ref": "#/$defs/siteString" + }, + "adapter": { + "$ref": "#/$defs/adapterString" + }, + "rel": { + "type": "number" + } + } + }, + "removeDelta": { + "type": "object", + "additionalProperties": true, + "required": [ + "t", + "ts", + "op", + "block" + ], + "properties": { + "t": { + "const": "delta" + }, + "ts": { + "$ref": "#/$defs/timestamp" + }, + "op": { + "const": "remove" + }, + "block": { + "$ref": "#/$defs/removeBlock" + }, + "inst": { + "$ref": "#/$defs/instString" + }, + "ctx": { + "$ref": "#/$defs/ctxString" + }, + "title": { + "$ref": "#/$defs/titleString" + }, + "url": { + "$ref": "#/$defs/urlString" + }, + "site": { + "$ref": "#/$defs/siteString" + }, + "adapter": { + "$ref": "#/$defs/adapterString" + }, + "rel": { + "type": "number" + } + } + }, + "delta": { + "oneOf": [ + { + "$ref": "#/$defs/addUpdateDelta" + }, + { + "$ref": "#/$defs/removeDelta" + } + ] + }, + "titleString": { + "type": "string", + "maxLength": 8192 + }, + "urlString": { + "type": "string", + "maxLength": 32768 + }, + "siteString": { + "type": "string", + "maxLength": 512 + }, + "adapterString": { + "type": "string", + "maxLength": 64 + }, + "ctxString": { + "type": "string", + "maxLength": 256 + }, + "instString": { + "type": "string", + "maxLength": 128 + }, + "idString": { + "type": "string", + "maxLength": 256 + }, + "typeString": { + "type": "string", + "maxLength": 64 + }, + "linkHostString": { + "type": "string", + "maxLength": 512 + }, + "levelString": { + "type": "string", + "maxLength": 16 + }, + "blockAttributes": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "maxLength": 300 + }, + "level": { + "$ref": "#/$defs/levelString" + }, + "linkHost": { + "$ref": "#/$defs/linkHostString" + } + } + }, + "blockDepth": { + "type": "integer", + "minimum": 0, + "maximum": 4096 + } + }, + "x-journal-contract": { + "format_id": "browser-jsonl", + "schema_owner": "solstone-core-ingest-contract", + "reference_writer": "native desktop apps via authenticated ingest", + "allowed_producers": [ + "authenticated linked-device clients" + ], + "write_discipline": "immutable finalized JSONL, one file for the segment period. Allocation may disambiguate the stream name from {label}_browser. The extension is not a linked-device identity.", + "file_kind": "browser_jsonl", + "key_fields": [ + "t", + "ts", + "site", + "op", + "block", + "blocks" + ], + "producer_write_paths": [ + "chronicle/YYYYMMDD/{label}_browser/HHMMSS_LEN/browser_pages.jsonl" + ] + } +} diff --git a/vendor/contracts/native-browser/constants.js b/vendor/contracts/native-browser/constants.js new file mode 100644 index 0000000..e3232da --- /dev/null +++ b/vendor/contracts/native-browser/constants.js @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +(function () { + "use strict"; + + globalThis.SolstoneNativeBrowserConstants = Object.freeze({ + "BUNDLE_VERSION": "1.1.0", + "WIRE_PROTOCOL": 1, + "EXTENSION_TO_HOST_MAX": 33554432, + "HOST_TO_EXTENSION_MAX": 65536, + "CONTROL_MAX": 65536, + "DELTA_RECORDS_MAX": 3000, + "BATCH_ID_HEX_LEN": 32, + "JSON_MAX_DEPTH": 127, + "FILE_MAX": 50331648, + "OUTBOX_BYTES_MAX": 67108864, + "OUTBOX_AGE_MS_MAX": 600000, + "SPOOL_BYTES_MAX": 536870912, + "SPOOL_AGE_MS_MAX": 604800000, + "FUTURE_SKEW_MS_MAX": 60000, + "ACCEPTED_RETENTION_MS_MIN": 1200000, + "HANDSHAKE_MS_BUDGET": 5000, + "STATE_RENEWAL_MS_INTERVAL": 5000, + "FRESHNESS_MS_MAX": 15000, + "PARTIAL_FRAME_MS_LIFETIME": 30000, + "TIMESTAMP_MAX": 9007199254740991, + "VERSION_MAX": 64, + "GENERATION_MAX": 128, + "PERIOD_ID_MAX": 128, + "FAILURE_CODE_MAX": 64, + "INST_STRING_MAX": 128, + "ID_STRING_MAX": 256, + "TITLE_STRING_MAX": 8192, + "URL_STRING_MAX": 32768, + "SITE_STRING_MAX": 512, + "ADAPTER_STRING_MAX": 64, + "CTX_STRING_MAX": 256, + "TYPE_STRING_MAX": 64, + "LINK_HOST_STRING_MAX": 512, + "LEVEL_STRING_MAX": 16, + "LABEL_STRING_MAX": 300, + "TEXT_MAX": 2001, + "BLOCK_DEPTH_MAX": 4096, + "BLOCKS_MAX": 1500, + "DIRECTIONS": { + "extension_to_host": [ + "hello", + "batch" + ], + "host_to_extension": [ + "hello_ack", + "unsupported", + "state", + "boundary", + "accepted", + "bye" + ] + }, + "BRAND_ENUM": [ + "chrome", + "edge", + "firefox" + ], + "CAPTURE_ENUM": [ + "unavailable", + "not_paired", + "permitted", + "paused", + "intake_off" + ], + "DELIVERY_ENUM": [ + "unknown", + "kept_locally", + "delivered", + "idle", + "failed" + ], + "FAILURE_ENUM": [ + "relay_unavailable", + "journal_rejected", + "local_io", + "resource_exhausted", + "queue_full", + "age_policy", + "unaccepted_lost" + ], + "BYE_REASON_ENUM": [ + "shutdown", + "replaced", + "update" + ], + "SNAPSHOT_REASON_ENUM": [ + "delivery_recovery" + ], + "BEHIND_ENUM": [ + "app", + "extension" + ], + "RESULT_ENUM": [ + "accepted", + "duplicate", + "rejected" + ], + "HOSTS_AND_IDS": { + "production": { + "host": "app.solstone.browser", + "chrome_id": "eibbeeoifjoabddfmgeggnageolkcnim", + "edge_id": "eibbeeoifjoabddfmgeggnageolkcnim", + "firefox_id": "browser@solstone.app" + }, + "dev": { + "host": "app.solstone.browser.dev", + "chrome_id": "fgfnkcefedeheoeamppkiiloncfekakf", + "edge_id": "fgfnkcefedeheoeamppkiiloncfekakf", + "firefox_id": "browser.dev@solstone.app" + } + }, + "REGISTRATION": { + "description": "Solstone browser host", + "type": "stdio", + "path_placeholder": "__PATH__", + "suffixes": { + "chrome_linux": ".config/google-chrome/NativeMessagingHosts/.json", + "edge_linux": ".config/microsoft-edge/NativeMessagingHosts/.json", + "firefox_linux": ".mozilla/native-messaging-hosts/.json", + "chrome_macos": "Library/Application Support/Google/Chrome/NativeMessagingHosts/.json", + "edge_macos": "Library/Application Support/Microsoft Edge/NativeMessagingHosts/.json", + "firefox_macos": "Library/Application Support/Mozilla/NativeMessagingHosts/.json", + "chrome_windows": "Software\\Google\\Chrome\\NativeMessagingHosts\\", + "edge_windows": "Software\\Microsoft\\Edge\\NativeMessagingHosts\\", + "firefox_windows": "Software\\Mozilla\\NativeMessagingHosts\\" + }, + "argv": { + "chrome_macos": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "chrome_linux": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "chrome_windows": { + "arguments_after_executable": [ + "origin", + "parent_window" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "edge_macos": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "edge_linux": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "edge_windows": { + "arguments_after_executable": [ + "origin", + "parent_window" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "firefox_macos": { + "arguments_after_executable": [ + "manifest_path", + "extension_id" + ], + "identity_argument": 1, + "identity_source": "allowed_extensions" + }, + "firefox_linux": { + "arguments_after_executable": [ + "manifest_path", + "extension_id" + ], + "identity_argument": 1, + "identity_source": "allowed_extensions" + }, + "firefox_windows": { + "arguments_after_executable": [ + "manifest_path", + "extension_id" + ], + "identity_argument": 1, + "identity_source": "allowed_extensions" + } + }, + "windows": { + "hive": "HKEY_CURRENT_USER", + "value_name": "", + "value_type": "REG_SZ", + "value": "absolute_manifest_path", + "registry_views": [ + "32", + "64" + ], + "path_contains_view": false + } + }, + "RECEIPT_CLASSES": { + "retryable": [ + "snapshot_required", + "resource_exhausted", + "queue_full", + "age_policy" + ], + "permanent": [ + "malformed", + "oversize", + "stale_generation", + "expired_unaccepted", + "unaccepted_lost" + ] + }, + "CANONICAL_KEY_ORDER": { + "hello": [ + "type", + "protocol", + "version", + "brand", + "inst" + ], + "hello_ack": [ + "type", + "capture", + "delivery", + "freshness_ms", + "destination_generation", + "period_id", + "failure", + "custody", + "version" + ], + "unsupported": [ + "type", + "protocol", + "behind" + ], + "state": [ + "type", + "capture", + "delivery", + "freshness_ms", + "destination_generation", + "period_id", + "failure", + "custody", + "version" + ], + "batch": [ + "type", + "destination_generation", + "inst", + "batch_id", + "queued_at_ms", + "records" + ], + "boundary": [ + "type", + "destination_generation", + "period_id" + ], + "accepted": [ + "type", + "result", + "destination_generation", + "inst", + "batch_id", + "period_id", + "reason", + "class" + ], + "bye": [ + "type", + "reason" + ], + "snapshot_record": [ + "t", + "ts", + "rel", + "site", + "url", + "title", + "adapter", + "ctx", + "inst", + "n", + "blocks", + "snapshot_reason" + ], + "delta_record": [ + "t", + "ts", + "rel", + "site", + "ctx", + "inst", + "op", + "block" + ], + "block": [ + "id", + "text", + "type", + "depth", + "attrs" + ], + "block_attrs": [ + "label", + "level", + "linkHost" + ] + } +}); +})(); diff --git a/vendor/contracts/native-browser/corpus.json b/vendor/contracts/native-browser/corpus.json new file mode 100644 index 0000000..a35d2e1 --- /dev/null +++ b/vendor/contracts/native-browser/corpus.json @@ -0,0 +1,1164 @@ +[ + { + "id": "hello_positive", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"chrome\",\"inst\":\"desktop_inst_1\"}" + }, + { + "id": "hello_wrong_direction", + "direction": "host_to_extension", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"chrome\",\"inst\":\"desktop_inst_1\"}" + }, + { + "id": "hello_missing_field", + "direction": "extension_to_host", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"brand\":\"chrome\",\"inst\":\"desktop_inst_1\"}" + }, + { + "id": "hello_ack_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "hello_ack_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "hello_ack_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"delivery\":\"delivered\",\"freshness_ms\":15000}" + }, + { + "id": "unsupported_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"unsupported\",\"protocol\":2,\"behind\":\"app\"}" + }, + { + "id": "unsupported_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"unsupported\",\"protocol\":2,\"behind\":\"app\"}" + }, + { + "id": "unsupported_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"unsupported\",\"protocol\":2}" + }, + { + "id": "state_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"freshness_ms\":15000}" + }, + { + "id": "boundary_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"boundary\",\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "boundary_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"boundary\",\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "boundary_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"boundary\",\"destination_generation\":\"g1\"}" + }, + { + "id": "accepted_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p1\"}" + }, + { + "id": "accepted_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"accepted\",\"result\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p1\"}" + }, + { + "id": "accepted_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p1\"}" + }, + { + "id": "bye_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"bye\",\"reason\":\"shutdown\"}" + }, + { + "id": "bye_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"bye\",\"reason\":\"shutdown\"}" + }, + { + "id": "bye_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"bye\"}" + }, + { + "id": "batch_positive", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"b1\",\"text\":\"Hello\"}]}]}" + }, + { + "id": "batch_wrong_direction", + "direction": "host_to_extension", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_missing_field", + "direction": "extension_to_host", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "hello_invalid_brand", + "direction": "extension_to_host", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"safari\",\"inst\":\"inst1\"}" + }, + { + "id": "hello_ack_invalid_capture", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"unknown_capture\",\"delivery\":\"idle\",\"freshness_ms\":15000}" + }, + { + "id": "unsupported_invalid_behind", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"unsupported\",\"protocol\":2,\"behind\":\"unknown_behind\"}" + }, + { + "id": "state_invalid_capture", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"state\",\"capture\":\"unknown_capture\",\"delivery\":\"idle\",\"freshness_ms\":15000}" + }, + { + "id": "batch_record_invalid_snapshot_reason", + "direction": "extension_to_host", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"b1\",\"text\":\"text\"}],\"snapshot_reason\":\"unknown_reason\"}]}" + }, + { + "id": "bye_invalid_reason", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"bye\",\"reason\":\"crash\"}" + }, + { + "id": "accepted_invalid_result_enum", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"invalid\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p1\"}" + }, + { + "id": "hello_protocol_unsupported_app", + "direction": "extension_to_host", + "expect": "unsupported", + "behind": "app", + "payload": "{\"type\":\"hello\",\"protocol\":2,\"version\":\"2.0.0\",\"brand\":\"chrome\",\"inst\":\"inst_app\"}" + }, + { + "id": "hello_protocol_unsupported_extension", + "direction": "extension_to_host", + "expect": "unsupported", + "behind": "extension", + "payload": "{\"type\":\"hello\",\"protocol\":0,\"version\":\"0.1.0\",\"brand\":\"chrome\",\"inst\":\"inst_ext\"}" + }, + { + "id": "hello_protocol_version_skew_accepted", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"9.9.9\",\"brand\":\"edge\",\"inst\":\"inst2\"}" + }, + { + "id": "hello_protocol_float_integral", + "direction": "extension_to_host", + "raw": "{\"type\":\"hello\",\"protocol\":1.0,\"version\":\"1.0.0\",\"brand\":\"firefox\",\"inst\":\"inst3\"}", + "expect": "accept" + }, + { + "id": "hello_protocol_bad_number", + "direction": "extension_to_host", + "raw": "{\"type\":\"hello\",\"protocol\":2.5,\"version\":\"2.5.0\",\"brand\":\"chrome\",\"inst\":\"inst\"}", + "expect": "refuse", + "code": "bad_number" + }, + { + "id": "state_matrix_unavailable_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"unknown\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_unavailable_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_unavailable_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"delivered\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_unavailable_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"idle\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_unavailable_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_matrix_not_paired_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"unknown\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_not_paired_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_not_paired_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"delivered\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_not_paired_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"idle\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_not_paired_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_matrix_permitted_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"unknown\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_permitted_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_permitted_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_permitted_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"idle\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_permitted_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_matrix_paused_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"unknown\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_paused_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_paused_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_paused_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"idle\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_paused_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_matrix_intake_off_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"unknown\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_intake_off_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_intake_off_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_intake_off_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"idle\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_intake_off_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_freshness_boundary_0", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":0,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_freshness_boundary_15000", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_freshness_out_of_range_15001", + "direction": "host_to_extension", + "expect": "refuse", + "code": "freshness_range", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15001,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_failure_independent_of_delivery", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_illegal_ids_when_not_paired", + "direction": "host_to_extension", + "expect": "refuse", + "code": "bad_state_ids", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"idle\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_illegal_missing_ids_when_permitted", + "direction": "host_to_extension", + "expect": "refuse", + "code": "bad_state_ids", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000}" + }, + { + "id": "id_missing_snapshot", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"text\":\"content\"}]}]}" + }, + { + "id": "id_empty_snapshot", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"\",\"text\":\"content\"}]}]}" + }, + { + "id": "id_max_len_snapshot", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}]}]}" + }, + { + "id": "id_over_max_len_snapshot", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "too_long", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}]}]}" + }, + { + "id": "id_short_snapshot", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"b1\",\"text\":\"content\"}]}]}" + }, + { + "id": "id_missing_add", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"text\":\"content\"}}]}" + }, + { + "id": "id_empty_add", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"\",\"text\":\"content\"}}]}" + }, + { + "id": "id_max_len_add", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}}]}" + }, + { + "id": "id_over_max_len_add", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "too_long", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}}]}" + }, + { + "id": "id_short_add", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"b1\",\"text\":\"content\"}}]}" + }, + { + "id": "id_missing_update", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"text\":\"content\"}}]}" + }, + { + "id": "id_empty_update", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"id\":\"\",\"text\":\"content\"}}]}" + }, + { + "id": "id_max_len_update", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}}]}" + }, + { + "id": "id_over_max_len_update", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "too_long", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}}]}" + }, + { + "id": "id_short_update", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"id\":\"b1\",\"text\":\"content\"}}]}" + }, + { + "id": "id_missing_remove", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{}}]}" + }, + { + "id": "id_empty_remove", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"\"}}]}" + }, + { + "id": "id_max_len_remove", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}}]}" + }, + { + "id": "id_over_max_len_remove", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "too_long", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}}]}" + }, + { + "id": "id_short_remove", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"b1\"}}]}" + }, + { + "id": "batch_id_short_31", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_batch_id", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcde\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_id_long_33", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_batch_id", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef0\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_id_uppercase", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_batch_id", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789ABCDEF0123456789ABCDEF\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_id_hyphenated", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_batch_id", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"01234567-89ab-cdef-0123-456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_queued_at_ms_fraction_1000_0", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1000.0,\"records\":[{\"t\":\"segment_start\",\"ctx\":\"c\",\"ts\":0,\"blocks\":[{\"id\":\"b1\",\"text\":\"x\"}]}]}", + "expect": "accept" + }, + { + "id": "batch_queued_at_ms_exponential_1e3", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1e3,\"records\":[{\"t\":\"segment_start\",\"ctx\":\"c\",\"ts\":0,\"blocks\":[{\"id\":\"b1\",\"text\":\"x\"}]}]}", + "expect": "accept" + }, + { + "id": "batch_queued_at_ms_above_max", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":9007199254740992,\"records\":[{\"t\":\"segment_start\",\"ctx\":\"c\",\"ts\":0,\"blocks\":[{\"id\":\"b1\",\"text\":\"x\"}]}]}", + "expect": "refuse", + "code": "bad_number" + }, + { + "id": "batch_queued_at_ms_negative", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":-10,\"records\":[{\"t\":\"delta\",\"ctx\":\"c\",\"ts\":100,\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}", + "expect": "refuse", + "code": "bad_number" + }, + { + "id": "batch_retry_1", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"retry_gen\",\"inst\":\"retry_inst\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1700000000000,\"records\":[{\"t\":\"delta\",\"ts\":1700000000000,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"b1\",\"text\":\"try1\"}}]}" + }, + { + "id": "batch_retry_2", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"retry_gen\",\"inst\":\"retry_inst\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1700000000000,\"records\":[{\"t\":\"delta\",\"ts\":1700000000000,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"b1\",\"text\":\"try2\"}}]}" + }, + { + "id": "batch_recovery_snapshot", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"retry_gen\",\"inst\":\"retry_inst\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1700000000000,\"records\":[{\"t\":\"segment_start\",\"ts\":1700000000000,\"site\":\"example.com\",\"ctx\":\"c\",\"n\":1,\"blocks\":[{\"id\":\"b1\",\"text\":\"Recovery snapshot\"}],\"snapshot_reason\":\"delivery_recovery\"}]}" + }, + { + "id": "boundary_recovery", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"boundary\",\"destination_generation\":\"retry_gen\",\"period_id\":\"p_boundary\"}" + }, + { + "id": "accepted_recovery_duplicate", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"duplicate\",\"destination_generation\":\"retry_gen\",\"inst\":\"retry_inst\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\"}" + }, + { + "id": "batch_inst_mismatch_record_inst", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "mismatch", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"inst\":\"mismatched_inst\",\"n\":1,\"blocks\":[{\"id\":\"b1\",\"text\":\"x\"}]}]}" + }, + { + "id": "batch_mixed_context", + "direction": "extension_to_host", + "expect": "refuse", + "code": "mixed_context", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"ctx_a\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}},{\"t\":\"delta\",\"ts\":100,\"ctx\":\"ctx_b\",\"op\":\"remove\",\"block\":{\"id\":\"d2\"}}]}" + }, + { + "id": "unicode_astral_and_escapes", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"n\":1,\"blocks\":[{\"id\":\"b1\",\"text\":\"Astral: 😀, Control: \\u0001, Separators: 

\"}]}]}" + }, + { + "id": "lone_surrogate_raw", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ctx\":\"c\",\"ts\":100,\"n\":1,\"blocks\":[{\"id\":\"b1\",\"text\":\"\\uD800 alone\"}]}]}", + "expect": "refuse", + "code": "lone_surrogate" + }, + { + "id": "sentinel_planted_in_failing_record", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"url\":\"https://example.com/ZQ_SENTINEL_do_not_echo\",\"title\":\"ZQ_SENTINEL_do_not_echo in title\",\"ctx\":\"c\",\"n\":1,\"blocks\":[{\"id\":\"\",\"text\":\"ZQ_SENTINEL_do_not_echo in text\"}]}]}" + }, + { + "id": "receipt_rejected_snapshot_required", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_wrong_class_snapshot_required", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"snapshot_required\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_rejected_resource_exhausted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"resource_exhausted\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_wrong_class_resource_exhausted", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"resource_exhausted\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_rejected_queue_full", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"queue_full\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_wrong_class_queue_full", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"queue_full\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_rejected_age_policy", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"age_policy\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_wrong_class_age_policy", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"age_policy\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_rejected_malformed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"malformed\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_malformed", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"malformed\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_oversize", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"oversize\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_oversize", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"oversize\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_stale_generation", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"stale_generation\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_stale_generation", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"stale_generation\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_expired_unaccepted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"expired_unaccepted\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_expired_unaccepted", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"expired_unaccepted\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_unaccepted_lost", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"unaccepted_lost\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_unaccepted_lost", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"unaccepted_lost\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_success_accepted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\"}" + }, + { + "id": "receipt_success_with_reason_accepted", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_success_duplicate", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"duplicate\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\"}" + }, + { + "id": "receipt_success_with_reason_duplicate", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"duplicate\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_with_period", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_wrong\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_unknown_reason", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"unknown\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_missing_destination_generation", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_missing_inst", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_missing_batch_id", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "state_capture_pressure_resource_exhausted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"resource_exhausted\"}" + }, + { + "id": "state_capture_pressure_queue_full", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"queue_full\"}" + }, + { + "id": "state_capture_pressure_age_policy", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"age_policy\"}" + }, + { + "id": "state_capture_pressure_unaccepted_lost", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"unaccepted_lost\"}" + }, + { + "id": "state_custody_false_false", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":false}}" + }, + { + "id": "state_custody_false_true", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":true}}" + }, + { + "id": "state_custody_true_false", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true,\"stale\":false}}" + }, + { + "id": "state_custody_true_true", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true,\"stale\":true}}" + }, + { + "id": "state_custody_omitted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_custody_additive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"future_fact\":\"preserved\",\"stale\":true}}" + }, + { + "id": "state_custody_invalid_null", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":null}" + }, + { + "id": "state_custody_invalid_array", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":[]}" + }, + { + "id": "state_custody_invalid_empty", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{}}" + }, + { + "id": "state_custody_invalid_missing_full", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"stale\":true}}" + }, + { + "id": "state_custody_invalid_missing_stale", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true}}" + }, + { + "id": "state_custody_invalid_full_type", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":1,\"stale\":false}}" + }, + { + "id": "state_custody_invalid_stale_type", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":\"true\"}}" + }, + { + "id": "state_null_unpaired", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"unknown\",\"freshness_ms\":0,\"destination_generation\":null,\"period_id\":null}" + }, + { + "id": "state_no_freshness", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"unknown\"}" + }, + { + "id": "hello_ack_capture_pressure_resource_exhausted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"resource_exhausted\"}" + }, + { + "id": "hello_ack_capture_pressure_queue_full", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"queue_full\"}" + }, + { + "id": "hello_ack_capture_pressure_age_policy", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"age_policy\"}" + }, + { + "id": "hello_ack_capture_pressure_unaccepted_lost", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"unaccepted_lost\"}" + }, + { + "id": "hello_ack_custody_false_false", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":false}}" + }, + { + "id": "hello_ack_custody_false_true", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":true}}" + }, + { + "id": "hello_ack_custody_true_false", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true,\"stale\":false}}" + }, + { + "id": "hello_ack_custody_true_true", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true,\"stale\":true}}" + }, + { + "id": "hello_ack_custody_omitted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "hello_ack_custody_additive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"future_fact\":\"preserved\",\"stale\":true}}" + }, + { + "id": "hello_ack_custody_invalid_null", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":null}" + }, + { + "id": "hello_ack_custody_invalid_array", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":[]}" + }, + { + "id": "hello_ack_custody_invalid_empty", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{}}" + }, + { + "id": "hello_ack_custody_invalid_missing_full", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"stale\":true}}" + }, + { + "id": "hello_ack_custody_invalid_missing_stale", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true}}" + }, + { + "id": "hello_ack_custody_invalid_full_type", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":1,\"stale\":false}}" + }, + { + "id": "hello_ack_custody_invalid_stale_type", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":\"true\"}}" + }, + { + "id": "hello_ack_null_unpaired", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"not_paired\",\"delivery\":\"unknown\",\"freshness_ms\":0,\"destination_generation\":null,\"period_id\":null}" + }, + { + "id": "hello_ack_no_freshness", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"not_paired\",\"delivery\":\"unknown\"}" + }, + { + "id": "batch_ctx_missing", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"op\":\"remove\",\"block\":{\"id\":\"b\"}}]}" + }, + { + "id": "batch_ctx_empty", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"\",\"op\":\"remove\",\"block\":{\"id\":\"b\"}}]}" + }, + { + "id": "batch_ctx_missing_first", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"op\":\"remove\",\"block\":{\"id\":\"b\"}},{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"b\"}}]}" + }, + { + "id": "batch_ctx_missing_last", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"b\"}},{\"t\":\"delta\",\"ts\":100,\"op\":\"remove\",\"block\":{\"id\":\"b\"}}]}" + }, + { + "id": "json_depth_at_limit", + "direction": "extension_to_host", + "raw": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"chrome\",\"inst\":\"depth\",\"extra\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}", + "expect": "accept" + }, + { + "id": "json_depth_over_limit", + "direction": "extension_to_host", + "raw": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"chrome\",\"inst\":\"depth\",\"extra\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}", + "expect": "refuse", + "code": "bad_json" + } +] diff --git a/vendor/contracts/native-browser/envelope.schema.json b/vendor/contracts/native-browser/envelope.schema.json new file mode 100644 index 0000000..63a1126 --- /dev/null +++ b/vendor/contracts/native-browser/envelope.schema.json @@ -0,0 +1,699 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "solstone-native-browser:envelope", + "title": "Solstone Native Browser Wire Envelope", + "oneOf": [ + { + "$ref": "#/$defs/hello" + }, + { + "$ref": "#/$defs/hello_ack" + }, + { + "$ref": "#/$defs/unsupported" + }, + { + "$ref": "#/$defs/state" + }, + { + "$ref": "#/$defs/batch" + }, + { + "$ref": "#/$defs/boundary" + }, + { + "$ref": "#/$defs/accepted" + }, + { + "$ref": "#/$defs/bye" + } + ], + "$defs": { + "hello": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "protocol", + "version", + "brand", + "inst" + ], + "properties": { + "type": { + "const": "hello" + }, + "protocol": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "version": { + "type": "string", + "maxLength": 64 + }, + "brand": { + "enum": [ + "chrome", + "edge", + "firefox" + ] + }, + "inst": { + "$ref": "solstone-journal-format:browser-jsonl#/$defs/instString", + "minLength": 1 + } + } + }, + "hello_ack": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "capture", + "delivery", + "freshness_ms" + ], + "properties": { + "type": { + "const": "hello_ack" + }, + "capture": { + "enum": [ + "unavailable", + "not_paired", + "permitted", + "paused", + "intake_off" + ] + }, + "delivery": { + "enum": [ + "unknown", + "kept_locally", + "delivered", + "idle", + "failed" + ] + }, + "freshness_ms": { + "type": "integer", + "minimum": 0, + "maximum": 15000 + }, + "destination_generation": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + { + "type": "null" + } + ] + }, + "period_id": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + { + "type": "null" + } + ] + }, + "failure": { + "enum": [ + "relay_unavailable", + "journal_rejected", + "local_io", + "resource_exhausted", + "queue_full", + "age_policy", + "unaccepted_lost" + ] + }, + "version": { + "type": "string", + "maxLength": 64 + }, + "custody": { + "type": "object", + "additionalProperties": true, + "required": [ + "full", + "stale" + ], + "properties": { + "full": { + "type": "boolean" + }, + "stale": { + "type": "boolean" + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "capture": { + "enum": [ + "unavailable", + "not_paired" + ] + } + } + }, + "then": { + "properties": { + "destination_generation": { + "type": "null" + }, + "period_id": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "capture": { + "const": "permitted" + } + } + }, + "then": { + "required": [ + "destination_generation", + "period_id" + ], + "properties": { + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "period_id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + } + }, + { + "if": { + "properties": { + "capture": { + "enum": [ + "paused", + "intake_off" + ] + } + } + }, + "then": { + "required": [ + "destination_generation" + ], + "properties": { + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + } + }, + { + "if": { + "properties": { + "delivery": { + "const": "failed" + } + } + }, + "then": { + "required": [ + "failure" + ] + } + } + ] + }, + "unsupported": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "protocol", + "behind" + ], + "properties": { + "type": { + "const": "unsupported" + }, + "protocol": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "version": { + "type": "string", + "maxLength": 64 + }, + "behind": { + "enum": [ + "app", + "extension" + ] + } + } + }, + "state": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "capture", + "delivery", + "freshness_ms" + ], + "properties": { + "type": { + "const": "state" + }, + "capture": { + "enum": [ + "unavailable", + "not_paired", + "permitted", + "paused", + "intake_off" + ] + }, + "delivery": { + "enum": [ + "unknown", + "kept_locally", + "delivered", + "idle", + "failed" + ] + }, + "freshness_ms": { + "type": "integer", + "minimum": 0, + "maximum": 15000 + }, + "destination_generation": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + { + "type": "null" + } + ] + }, + "period_id": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + { + "type": "null" + } + ] + }, + "failure": { + "enum": [ + "relay_unavailable", + "journal_rejected", + "local_io", + "resource_exhausted", + "queue_full", + "age_policy", + "unaccepted_lost" + ] + }, + "version": { + "type": "string", + "maxLength": 64 + }, + "custody": { + "type": "object", + "additionalProperties": true, + "required": [ + "full", + "stale" + ], + "properties": { + "full": { + "type": "boolean" + }, + "stale": { + "type": "boolean" + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "capture": { + "enum": [ + "unavailable", + "not_paired" + ] + } + } + }, + "then": { + "properties": { + "destination_generation": { + "type": "null" + }, + "period_id": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "capture": { + "const": "permitted" + } + } + }, + "then": { + "required": [ + "destination_generation", + "period_id" + ], + "properties": { + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "period_id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + } + }, + { + "if": { + "properties": { + "capture": { + "enum": [ + "paused", + "intake_off" + ] + } + } + }, + "then": { + "required": [ + "destination_generation" + ], + "properties": { + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + } + }, + { + "if": { + "properties": { + "delivery": { + "const": "failed" + } + } + }, + "then": { + "required": [ + "failure" + ] + } + } + ] + }, + "batch": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "destination_generation", + "inst", + "batch_id", + "queued_at_ms", + "records" + ], + "properties": { + "type": { + "const": "batch" + }, + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "inst": { + "$ref": "solstone-journal-format:browser-jsonl#/$defs/instString", + "minLength": 1 + }, + "batch_id": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "queued_at_ms": { + "$ref": "solstone-journal-format:browser-jsonl#/$defs/timestamp" + }, + "records": { + "type": "array", + "minItems": 1, + "maxItems": 3000, + "items": { + "$ref": "solstone-journal-format:browser-jsonl" + } + } + } + }, + "boundary": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "destination_generation", + "period_id" + ], + "properties": { + "type": { + "const": "boundary" + }, + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "period_id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + }, + "accepted": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "result", + "destination_generation", + "inst", + "batch_id" + ], + "properties": { + "type": { + "const": "accepted" + }, + "result": { + "enum": [ + "accepted", + "duplicate", + "rejected" + ] + }, + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "inst": { + "$ref": "solstone-journal-format:browser-jsonl#/$defs/instString", + "minLength": 1 + }, + "batch_id": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "period_id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "reason": { + "enum": [ + "snapshot_required", + "resource_exhausted", + "queue_full", + "age_policy", + "malformed", + "oversize", + "stale_generation", + "expired_unaccepted", + "unaccepted_lost" + ] + }, + "class": { + "enum": [ + "retryable", + "permanent" + ] + } + }, + "oneOf": [ + { + "properties": { + "result": { + "enum": [ + "accepted", + "duplicate" + ] + } + }, + "required": [ + "period_id" + ], + "not": { + "anyOf": [ + { + "required": [ + "reason" + ] + }, + { + "required": [ + "class" + ] + } + ] + } + }, + { + "properties": { + "result": { + "const": "rejected" + }, + "reason": { + "enum": [ + "snapshot_required", + "resource_exhausted", + "queue_full", + "age_policy" + ] + }, + "class": { + "const": "retryable" + } + }, + "required": [ + "reason", + "class" + ], + "not": { + "anyOf": [ + { + "required": [ + "period_id" + ] + } + ] + } + }, + { + "properties": { + "result": { + "const": "rejected" + }, + "reason": { + "enum": [ + "malformed", + "oversize", + "stale_generation", + "expired_unaccepted", + "unaccepted_lost" + ] + }, + "class": { + "const": "permanent" + } + }, + "required": [ + "reason", + "class" + ], + "not": { + "anyOf": [ + { + "required": [ + "period_id" + ] + } + ] + } + } + ] + }, + "bye": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "reason" + ], + "properties": { + "type": { + "const": "bye" + }, + "reason": { + "enum": [ + "shutdown", + "replaced", + "update" + ] + } + } + } + } +} diff --git a/vendor/contracts/native-browser/manifest.json b/vendor/contracts/native-browser/manifest.json new file mode 100644 index 0000000..c086f00 --- /dev/null +++ b/vendor/contracts/native-browser/manifest.json @@ -0,0 +1,45 @@ +{ + "generator": { + "name": "solstone-native-browser-gen", + "version": "1.1.0" + }, + "bundle_version": "1.1.0", + "wire_protocol": 1, + "journal": { + "id": "solstone-journal-format:browser-jsonl", + "revision": "3b99f1af61d4e82c5c9c3748d90c1242c28c1e5b", + "path": "contracts/native-browser/browser.schema.json", + "sha256": "c14e66318587b8fa451ef1363e113885bdd80a41ce680b0c62fcc9033460a2e1" + }, + "artifacts": { + "contracts/native-browser/authority.json": "19d673f70bd2258d28a7dc863e381c3a6c56dd8b3377f9b2e108e3d62db20e9c", + "contracts/native-browser/browser.schema.json": "c14e66318587b8fa451ef1363e113885bdd80a41ce680b0c62fcc9033460a2e1", + "contracts/native-browser/envelope.schema.json": "67e997c5980592bd26f0a8928ebec79418fead01cdcaaf14dcaed42c470d3c1f", + "contracts/native-browser/schemas.js": "42e16e31b35f245eddcd3c5d5c0439181462bcce5cd596ab095ce1475802586c", + "contracts/native-browser/constants.js": "e02de8290851350b9c76b8185da1a98712312817aa3790ebd31c3b6484a79c8f", + "crates/native-browser-frame/src/constants.rs": "8563c587924e88f752dbea7f002bbcd64225f1f3aefafc32d1553bee64fb59a5", + "contracts/native-browser/corpus.json": "3bf11e1a7ba27132c5fdf30975fdf9b683901eb826ed6ff3bab9a14c7c7a70ac", + "contracts/native-browser/recipes.json": "90edf430e0854fff26eedf18b2108f5151e2ef931cc00b50a492cd435714b737", + "contracts/native-browser/swift.json": "697c3c9d21cacf24f78587a5f1bb7e2d8087bcaecb6d8040d70fa69bc00127e6", + "contracts/native-browser/adoption.schema.json": "877094d7e89547922d1acbb26553f9fa643af41b7932a9feae6f4570cf96c536", + "contracts/native-browser/registration/production/chrome/linux.json": "e7875ae358fe40c95821e9a5420af9aba668141c4c2c662ace30c57e5091b9e8", + "contracts/native-browser/registration/production/chrome/macos.json": "e7875ae358fe40c95821e9a5420af9aba668141c4c2c662ace30c57e5091b9e8", + "contracts/native-browser/registration/production/chrome/windows.json": "e7875ae358fe40c95821e9a5420af9aba668141c4c2c662ace30c57e5091b9e8", + "contracts/native-browser/registration/production/edge/linux.json": "e7875ae358fe40c95821e9a5420af9aba668141c4c2c662ace30c57e5091b9e8", + "contracts/native-browser/registration/production/edge/macos.json": "e7875ae358fe40c95821e9a5420af9aba668141c4c2c662ace30c57e5091b9e8", + "contracts/native-browser/registration/production/edge/windows.json": "e7875ae358fe40c95821e9a5420af9aba668141c4c2c662ace30c57e5091b9e8", + "contracts/native-browser/registration/production/firefox/linux.json": "ee991f599a40f4583765000c90351b16aeff18bfbdbb880b427a79f1bc4f543b", + "contracts/native-browser/registration/production/firefox/macos.json": "ee991f599a40f4583765000c90351b16aeff18bfbdbb880b427a79f1bc4f543b", + "contracts/native-browser/registration/production/firefox/windows.json": "ee991f599a40f4583765000c90351b16aeff18bfbdbb880b427a79f1bc4f543b", + "contracts/native-browser/registration/dev/chrome/linux.json": "c8fed9d08db03b8b5b4a54b7114949abe877fdf6a927411ce46edbf146e006bd", + "contracts/native-browser/registration/dev/chrome/macos.json": "c8fed9d08db03b8b5b4a54b7114949abe877fdf6a927411ce46edbf146e006bd", + "contracts/native-browser/registration/dev/chrome/windows.json": "c8fed9d08db03b8b5b4a54b7114949abe877fdf6a927411ce46edbf146e006bd", + "contracts/native-browser/registration/dev/edge/linux.json": "c8fed9d08db03b8b5b4a54b7114949abe877fdf6a927411ce46edbf146e006bd", + "contracts/native-browser/registration/dev/edge/macos.json": "c8fed9d08db03b8b5b4a54b7114949abe877fdf6a927411ce46edbf146e006bd", + "contracts/native-browser/registration/dev/edge/windows.json": "c8fed9d08db03b8b5b4a54b7114949abe877fdf6a927411ce46edbf146e006bd", + "contracts/native-browser/registration/dev/firefox/linux.json": "dc981e662fc7a2b0841e33f736f6c883917acae0e08975b84c95f9828b707444", + "contracts/native-browser/registration/dev/firefox/macos.json": "dc981e662fc7a2b0841e33f736f6c883917acae0e08975b84c95f9828b707444", + "contracts/native-browser/registration/dev/firefox/windows.json": "dc981e662fc7a2b0841e33f736f6c883917acae0e08975b84c95f9828b707444" + }, + "swift_check": "swift test --filter SolstoneNativeBrowserContract" +} diff --git a/vendor/contracts/native-browser/recipes.json b/vendor/contracts/native-browser/recipes.json new file mode 100644 index 0000000..1b59810 --- /dev/null +++ b/vendor/contracts/native-browser/recipes.json @@ -0,0 +1,41 @@ +[ + { + "id": "extension_to_host_batch_max", + "target_length": 33554432, + "sha256": "65bca8031a98e859770ab5c4b23912246e6848355e641abb51559248126959ea", + "expect": "accept" + }, + { + "id": "extension_to_host_batch_oversize", + "target_length": 33554433, + "sha256": "d3053d1d6c8d080160239a350d78d78bac3b8f18f8af269aaee726d6baa4148f", + "expect": "refuse", + "code": "oversize" + }, + { + "id": "control_payload_max", + "target_length": 65536, + "sha256": "e4c5c7026ea89ae1f65cd6dac066abe37e80378b4a24293256233b956064ca91", + "expect": "accept" + }, + { + "id": "control_payload_oversize", + "target_length": 65537, + "sha256": "7cff9856600683e3bff701472ae4e3f6fda433e0e0b6f9459060c9ffc663fc8c", + "expect": "refuse", + "code": "oversize" + }, + { + "id": "batch_delta_cap_3000", + "target_length": 213911, + "sha256": "0186d9170294f32f5e2d7b54c12c0476e771686b88c9d48ae1dbc83d3e4e4594", + "expect": "accept" + }, + { + "id": "batch_delta_oversize_3001", + "target_length": 213987, + "sha256": "399e731770fcfd8fbbc71da8ec3fa877300980a7962693f0182865118368ec57", + "expect": "refuse", + "code": "too_many_deltas" + } +] diff --git a/vendor/contracts/native-browser/registration/dev/chrome/linux.json b/vendor/contracts/native-browser/registration/dev/chrome/linux.json new file mode 100644 index 0000000..edb5550 --- /dev/null +++ b/vendor/contracts/native-browser/registration/dev/chrome/linux.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser.dev", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://fgfnkcefedeheoeamppkiiloncfekakf/" + ] +} diff --git a/vendor/contracts/native-browser/registration/dev/chrome/macos.json b/vendor/contracts/native-browser/registration/dev/chrome/macos.json new file mode 100644 index 0000000..edb5550 --- /dev/null +++ b/vendor/contracts/native-browser/registration/dev/chrome/macos.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser.dev", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://fgfnkcefedeheoeamppkiiloncfekakf/" + ] +} diff --git a/vendor/contracts/native-browser/registration/dev/chrome/windows.json b/vendor/contracts/native-browser/registration/dev/chrome/windows.json new file mode 100644 index 0000000..edb5550 --- /dev/null +++ b/vendor/contracts/native-browser/registration/dev/chrome/windows.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser.dev", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://fgfnkcefedeheoeamppkiiloncfekakf/" + ] +} diff --git a/vendor/contracts/native-browser/registration/dev/edge/linux.json b/vendor/contracts/native-browser/registration/dev/edge/linux.json new file mode 100644 index 0000000..edb5550 --- /dev/null +++ b/vendor/contracts/native-browser/registration/dev/edge/linux.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser.dev", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://fgfnkcefedeheoeamppkiiloncfekakf/" + ] +} diff --git a/vendor/contracts/native-browser/registration/dev/edge/macos.json b/vendor/contracts/native-browser/registration/dev/edge/macos.json new file mode 100644 index 0000000..edb5550 --- /dev/null +++ b/vendor/contracts/native-browser/registration/dev/edge/macos.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser.dev", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://fgfnkcefedeheoeamppkiiloncfekakf/" + ] +} diff --git a/vendor/contracts/native-browser/registration/dev/edge/windows.json b/vendor/contracts/native-browser/registration/dev/edge/windows.json new file mode 100644 index 0000000..edb5550 --- /dev/null +++ b/vendor/contracts/native-browser/registration/dev/edge/windows.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser.dev", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://fgfnkcefedeheoeamppkiiloncfekakf/" + ] +} diff --git a/vendor/contracts/native-browser/registration/dev/firefox/linux.json b/vendor/contracts/native-browser/registration/dev/firefox/linux.json new file mode 100644 index 0000000..351660c --- /dev/null +++ b/vendor/contracts/native-browser/registration/dev/firefox/linux.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser.dev", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_extensions": [ + "browser.dev@solstone.app" + ] +} diff --git a/vendor/contracts/native-browser/registration/dev/firefox/macos.json b/vendor/contracts/native-browser/registration/dev/firefox/macos.json new file mode 100644 index 0000000..351660c --- /dev/null +++ b/vendor/contracts/native-browser/registration/dev/firefox/macos.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser.dev", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_extensions": [ + "browser.dev@solstone.app" + ] +} diff --git a/vendor/contracts/native-browser/registration/dev/firefox/windows.json b/vendor/contracts/native-browser/registration/dev/firefox/windows.json new file mode 100644 index 0000000..351660c --- /dev/null +++ b/vendor/contracts/native-browser/registration/dev/firefox/windows.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser.dev", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_extensions": [ + "browser.dev@solstone.app" + ] +} diff --git a/vendor/contracts/native-browser/registration/production/chrome/linux.json b/vendor/contracts/native-browser/registration/production/chrome/linux.json new file mode 100644 index 0000000..bba464c --- /dev/null +++ b/vendor/contracts/native-browser/registration/production/chrome/linux.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim/" + ] +} diff --git a/vendor/contracts/native-browser/registration/production/chrome/macos.json b/vendor/contracts/native-browser/registration/production/chrome/macos.json new file mode 100644 index 0000000..bba464c --- /dev/null +++ b/vendor/contracts/native-browser/registration/production/chrome/macos.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim/" + ] +} diff --git a/vendor/contracts/native-browser/registration/production/chrome/windows.json b/vendor/contracts/native-browser/registration/production/chrome/windows.json new file mode 100644 index 0000000..bba464c --- /dev/null +++ b/vendor/contracts/native-browser/registration/production/chrome/windows.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim/" + ] +} diff --git a/vendor/contracts/native-browser/registration/production/edge/linux.json b/vendor/contracts/native-browser/registration/production/edge/linux.json new file mode 100644 index 0000000..bba464c --- /dev/null +++ b/vendor/contracts/native-browser/registration/production/edge/linux.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim/" + ] +} diff --git a/vendor/contracts/native-browser/registration/production/edge/macos.json b/vendor/contracts/native-browser/registration/production/edge/macos.json new file mode 100644 index 0000000..bba464c --- /dev/null +++ b/vendor/contracts/native-browser/registration/production/edge/macos.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim/" + ] +} diff --git a/vendor/contracts/native-browser/registration/production/edge/windows.json b/vendor/contracts/native-browser/registration/production/edge/windows.json new file mode 100644 index 0000000..bba464c --- /dev/null +++ b/vendor/contracts/native-browser/registration/production/edge/windows.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://eibbeeoifjoabddfmgeggnageolkcnim/" + ] +} diff --git a/vendor/contracts/native-browser/registration/production/firefox/linux.json b/vendor/contracts/native-browser/registration/production/firefox/linux.json new file mode 100644 index 0000000..4553bdf --- /dev/null +++ b/vendor/contracts/native-browser/registration/production/firefox/linux.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_extensions": [ + "browser@solstone.app" + ] +} diff --git a/vendor/contracts/native-browser/registration/production/firefox/macos.json b/vendor/contracts/native-browser/registration/production/firefox/macos.json new file mode 100644 index 0000000..4553bdf --- /dev/null +++ b/vendor/contracts/native-browser/registration/production/firefox/macos.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_extensions": [ + "browser@solstone.app" + ] +} diff --git a/vendor/contracts/native-browser/registration/production/firefox/windows.json b/vendor/contracts/native-browser/registration/production/firefox/windows.json new file mode 100644 index 0000000..4553bdf --- /dev/null +++ b/vendor/contracts/native-browser/registration/production/firefox/windows.json @@ -0,0 +1,9 @@ +{ + "name": "app.solstone.browser", + "description": "Solstone browser host", + "path": "__PATH__", + "type": "stdio", + "allowed_extensions": [ + "browser@solstone.app" + ] +} diff --git a/vendor/contracts/native-browser/schemas.js b/vendor/contracts/native-browser/schemas.js new file mode 100644 index 0000000..b17eb18 --- /dev/null +++ b/vendor/contracts/native-browser/schemas.js @@ -0,0 +1,1009 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +globalThis.SolstoneNativeBrowserSchemas = { + "envelope": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "solstone-native-browser:envelope", + "title": "Solstone Native Browser Wire Envelope", + "oneOf": [ + { + "$ref": "#/$defs/hello" + }, + { + "$ref": "#/$defs/hello_ack" + }, + { + "$ref": "#/$defs/unsupported" + }, + { + "$ref": "#/$defs/state" + }, + { + "$ref": "#/$defs/batch" + }, + { + "$ref": "#/$defs/boundary" + }, + { + "$ref": "#/$defs/accepted" + }, + { + "$ref": "#/$defs/bye" + } + ], + "$defs": { + "hello": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "protocol", + "version", + "brand", + "inst" + ], + "properties": { + "type": { + "const": "hello" + }, + "protocol": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "version": { + "type": "string", + "maxLength": 64 + }, + "brand": { + "enum": [ + "chrome", + "edge", + "firefox" + ] + }, + "inst": { + "$ref": "solstone-journal-format:browser-jsonl#/$defs/instString", + "minLength": 1 + } + } + }, + "hello_ack": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "capture", + "delivery", + "freshness_ms" + ], + "properties": { + "type": { + "const": "hello_ack" + }, + "capture": { + "enum": [ + "unavailable", + "not_paired", + "permitted", + "paused", + "intake_off" + ] + }, + "delivery": { + "enum": [ + "unknown", + "kept_locally", + "delivered", + "idle", + "failed" + ] + }, + "freshness_ms": { + "type": "integer", + "minimum": 0, + "maximum": 15000 + }, + "destination_generation": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + { + "type": "null" + } + ] + }, + "period_id": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + { + "type": "null" + } + ] + }, + "failure": { + "enum": [ + "relay_unavailable", + "journal_rejected", + "local_io", + "resource_exhausted", + "queue_full", + "age_policy", + "unaccepted_lost" + ] + }, + "version": { + "type": "string", + "maxLength": 64 + }, + "custody": { + "type": "object", + "additionalProperties": true, + "required": [ + "full", + "stale" + ], + "properties": { + "full": { + "type": "boolean" + }, + "stale": { + "type": "boolean" + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "capture": { + "enum": [ + "unavailable", + "not_paired" + ] + } + } + }, + "then": { + "properties": { + "destination_generation": { + "type": "null" + }, + "period_id": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "capture": { + "const": "permitted" + } + } + }, + "then": { + "required": [ + "destination_generation", + "period_id" + ], + "properties": { + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "period_id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + } + }, + { + "if": { + "properties": { + "capture": { + "enum": [ + "paused", + "intake_off" + ] + } + } + }, + "then": { + "required": [ + "destination_generation" + ], + "properties": { + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + } + }, + { + "if": { + "properties": { + "delivery": { + "const": "failed" + } + } + }, + "then": { + "required": [ + "failure" + ] + } + } + ] + }, + "unsupported": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "protocol", + "behind" + ], + "properties": { + "type": { + "const": "unsupported" + }, + "protocol": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "version": { + "type": "string", + "maxLength": 64 + }, + "behind": { + "enum": [ + "app", + "extension" + ] + } + } + }, + "state": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "capture", + "delivery", + "freshness_ms" + ], + "properties": { + "type": { + "const": "state" + }, + "capture": { + "enum": [ + "unavailable", + "not_paired", + "permitted", + "paused", + "intake_off" + ] + }, + "delivery": { + "enum": [ + "unknown", + "kept_locally", + "delivered", + "idle", + "failed" + ] + }, + "freshness_ms": { + "type": "integer", + "minimum": 0, + "maximum": 15000 + }, + "destination_generation": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + { + "type": "null" + } + ] + }, + "period_id": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + { + "type": "null" + } + ] + }, + "failure": { + "enum": [ + "relay_unavailable", + "journal_rejected", + "local_io", + "resource_exhausted", + "queue_full", + "age_policy", + "unaccepted_lost" + ] + }, + "version": { + "type": "string", + "maxLength": 64 + }, + "custody": { + "type": "object", + "additionalProperties": true, + "required": [ + "full", + "stale" + ], + "properties": { + "full": { + "type": "boolean" + }, + "stale": { + "type": "boolean" + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "capture": { + "enum": [ + "unavailable", + "not_paired" + ] + } + } + }, + "then": { + "properties": { + "destination_generation": { + "type": "null" + }, + "period_id": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "capture": { + "const": "permitted" + } + } + }, + "then": { + "required": [ + "destination_generation", + "period_id" + ], + "properties": { + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "period_id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + } + }, + { + "if": { + "properties": { + "capture": { + "enum": [ + "paused", + "intake_off" + ] + } + } + }, + "then": { + "required": [ + "destination_generation" + ], + "properties": { + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + } + }, + { + "if": { + "properties": { + "delivery": { + "const": "failed" + } + } + }, + "then": { + "required": [ + "failure" + ] + } + } + ] + }, + "batch": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "destination_generation", + "inst", + "batch_id", + "queued_at_ms", + "records" + ], + "properties": { + "type": { + "const": "batch" + }, + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "inst": { + "$ref": "solstone-journal-format:browser-jsonl#/$defs/instString", + "minLength": 1 + }, + "batch_id": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "queued_at_ms": { + "$ref": "solstone-journal-format:browser-jsonl#/$defs/timestamp" + }, + "records": { + "type": "array", + "minItems": 1, + "maxItems": 3000, + "items": { + "$ref": "solstone-journal-format:browser-jsonl" + } + } + } + }, + "boundary": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "destination_generation", + "period_id" + ], + "properties": { + "type": { + "const": "boundary" + }, + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "period_id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + }, + "accepted": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "result", + "destination_generation", + "inst", + "batch_id" + ], + "properties": { + "type": { + "const": "accepted" + }, + "result": { + "enum": [ + "accepted", + "duplicate", + "rejected" + ] + }, + "destination_generation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "inst": { + "$ref": "solstone-journal-format:browser-jsonl#/$defs/instString", + "minLength": 1 + }, + "batch_id": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "period_id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "reason": { + "enum": [ + "snapshot_required", + "resource_exhausted", + "queue_full", + "age_policy", + "malformed", + "oversize", + "stale_generation", + "expired_unaccepted", + "unaccepted_lost" + ] + }, + "class": { + "enum": [ + "retryable", + "permanent" + ] + } + }, + "oneOf": [ + { + "properties": { + "result": { + "enum": [ + "accepted", + "duplicate" + ] + } + }, + "required": [ + "period_id" + ], + "not": { + "anyOf": [ + { + "required": [ + "reason" + ] + }, + { + "required": [ + "class" + ] + } + ] + } + }, + { + "properties": { + "result": { + "const": "rejected" + }, + "reason": { + "enum": [ + "snapshot_required", + "resource_exhausted", + "queue_full", + "age_policy" + ] + }, + "class": { + "const": "retryable" + } + }, + "required": [ + "reason", + "class" + ], + "not": { + "anyOf": [ + { + "required": [ + "period_id" + ] + } + ] + } + }, + { + "properties": { + "result": { + "const": "rejected" + }, + "reason": { + "enum": [ + "malformed", + "oversize", + "stale_generation", + "expired_unaccepted", + "unaccepted_lost" + ] + }, + "class": { + "const": "permanent" + } + }, + "required": [ + "reason", + "class" + ], + "not": { + "anyOf": [ + { + "required": [ + "period_id" + ] + } + ] + } + } + ] + }, + "bye": { + "type": "object", + "additionalProperties": true, + "required": [ + "type", + "reason" + ], + "properties": { + "type": { + "const": "bye" + }, + "reason": { + "enum": [ + "shutdown", + "replaced", + "update" + ] + } + } + } + } + }, + "journal": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "solstone-journal-format:browser-jsonl", + "title": "Browser stream JSONL", + "oneOf": [ + { + "$ref": "#/$defs/snapshot" + }, + { + "$ref": "#/$defs/delta" + } + ], + "$defs": { + "timestamp": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "textBlock": { + "type": "object", + "additionalProperties": true, + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "maxLength": 2001 + }, + "id": { + "$ref": "#/$defs/idString" + }, + "type": { + "$ref": "#/$defs/typeString" + }, + "attrs": { + "$ref": "#/$defs/blockAttributes" + }, + "depth": { + "$ref": "#/$defs/blockDepth" + } + } + }, + "removeBlock": { + "type": "object", + "additionalProperties": true, + "required": [ + "id" + ], + "properties": { + "id": { + "$ref": "#/$defs/idString" + }, + "text": { + "type": "string", + "maxLength": 2001 + }, + "type": { + "$ref": "#/$defs/typeString" + }, + "depth": { + "$ref": "#/$defs/blockDepth" + }, + "attrs": { + "$ref": "#/$defs/blockAttributes" + } + } + }, + "snapshot": { + "type": "object", + "additionalProperties": true, + "required": [ + "t", + "ts", + "blocks" + ], + "properties": { + "t": { + "const": "segment_start" + }, + "ts": { + "$ref": "#/$defs/timestamp" + }, + "inst": { + "$ref": "#/$defs/instString" + }, + "ctx": { + "$ref": "#/$defs/ctxString" + }, + "title": { + "$ref": "#/$defs/titleString" + }, + "url": { + "$ref": "#/$defs/urlString" + }, + "site": { + "$ref": "#/$defs/siteString" + }, + "adapter": { + "$ref": "#/$defs/adapterString" + }, + "rel": { + "type": "number" + }, + "n": { + "type": "integer", + "minimum": 0, + "maximum": 1500 + }, + "blocks": { + "type": "array", + "maxItems": 1500, + "items": { + "$ref": "#/$defs/textBlock" + } + } + } + }, + "addUpdateDelta": { + "type": "object", + "additionalProperties": true, + "required": [ + "t", + "ts", + "op", + "block" + ], + "properties": { + "t": { + "const": "delta" + }, + "ts": { + "$ref": "#/$defs/timestamp" + }, + "op": { + "enum": [ + "add", + "update" + ] + }, + "block": { + "$ref": "#/$defs/textBlock" + }, + "inst": { + "$ref": "#/$defs/instString" + }, + "ctx": { + "$ref": "#/$defs/ctxString" + }, + "title": { + "$ref": "#/$defs/titleString" + }, + "url": { + "$ref": "#/$defs/urlString" + }, + "site": { + "$ref": "#/$defs/siteString" + }, + "adapter": { + "$ref": "#/$defs/adapterString" + }, + "rel": { + "type": "number" + } + } + }, + "removeDelta": { + "type": "object", + "additionalProperties": true, + "required": [ + "t", + "ts", + "op", + "block" + ], + "properties": { + "t": { + "const": "delta" + }, + "ts": { + "$ref": "#/$defs/timestamp" + }, + "op": { + "const": "remove" + }, + "block": { + "$ref": "#/$defs/removeBlock" + }, + "inst": { + "$ref": "#/$defs/instString" + }, + "ctx": { + "$ref": "#/$defs/ctxString" + }, + "title": { + "$ref": "#/$defs/titleString" + }, + "url": { + "$ref": "#/$defs/urlString" + }, + "site": { + "$ref": "#/$defs/siteString" + }, + "adapter": { + "$ref": "#/$defs/adapterString" + }, + "rel": { + "type": "number" + } + } + }, + "delta": { + "oneOf": [ + { + "$ref": "#/$defs/addUpdateDelta" + }, + { + "$ref": "#/$defs/removeDelta" + } + ] + }, + "titleString": { + "type": "string", + "maxLength": 8192 + }, + "urlString": { + "type": "string", + "maxLength": 32768 + }, + "siteString": { + "type": "string", + "maxLength": 512 + }, + "adapterString": { + "type": "string", + "maxLength": 64 + }, + "ctxString": { + "type": "string", + "maxLength": 256 + }, + "instString": { + "type": "string", + "maxLength": 128 + }, + "idString": { + "type": "string", + "maxLength": 256 + }, + "typeString": { + "type": "string", + "maxLength": 64 + }, + "linkHostString": { + "type": "string", + "maxLength": 512 + }, + "levelString": { + "type": "string", + "maxLength": 16 + }, + "blockAttributes": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "maxLength": 300 + }, + "level": { + "$ref": "#/$defs/levelString" + }, + "linkHost": { + "$ref": "#/$defs/linkHostString" + } + } + }, + "blockDepth": { + "type": "integer", + "minimum": 0, + "maximum": 4096 + } + }, + "x-journal-contract": { + "format_id": "browser-jsonl", + "schema_owner": "solstone-core-ingest-contract", + "reference_writer": "native desktop apps via authenticated ingest", + "allowed_producers": [ + "authenticated linked-device clients" + ], + "write_discipline": "immutable finalized JSONL, one file for the segment period. Allocation may disambiguate the stream name from {label}_browser. The extension is not a linked-device identity.", + "file_kind": "browser_jsonl", + "key_fields": [ + "t", + "ts", + "site", + "op", + "block", + "blocks" + ], + "producer_write_paths": [ + "chronicle/YYYYMMDD/{label}_browser/HHMMSS_LEN/browser_pages.jsonl" + ] + } + } +}; diff --git a/vendor/contracts/native-browser/swift.json b/vendor/contracts/native-browser/swift.json new file mode 100644 index 0000000..d027cd9 --- /dev/null +++ b/vendor/contracts/native-browser/swift.json @@ -0,0 +1,1490 @@ +{ + "constants": { + "BUNDLE_VERSION": "1.1.0", + "WIRE_PROTOCOL": 1, + "EXTENSION_TO_HOST_MAX": 33554432, + "HOST_TO_EXTENSION_MAX": 65536, + "CONTROL_MAX": 65536, + "DELTA_RECORDS_MAX": 3000, + "BATCH_ID_HEX_LEN": 32, + "JSON_MAX_DEPTH": 127, + "FILE_MAX": 50331648, + "OUTBOX_BYTES_MAX": 67108864, + "OUTBOX_AGE_MS_MAX": 600000, + "SPOOL_BYTES_MAX": 536870912, + "SPOOL_AGE_MS_MAX": 604800000, + "FUTURE_SKEW_MS_MAX": 60000, + "ACCEPTED_RETENTION_MS_MIN": 1200000, + "HANDSHAKE_MS_BUDGET": 5000, + "STATE_RENEWAL_MS_INTERVAL": 5000, + "FRESHNESS_MS_MAX": 15000, + "PARTIAL_FRAME_MS_LIFETIME": 30000, + "TIMESTAMP_MAX": 9007199254740991, + "VERSION_MAX": 64, + "GENERATION_MAX": 128, + "PERIOD_ID_MAX": 128, + "FAILURE_CODE_MAX": 64, + "INST_STRING_MAX": 128, + "ID_STRING_MAX": 256, + "TITLE_STRING_MAX": 8192, + "URL_STRING_MAX": 32768, + "SITE_STRING_MAX": 512, + "ADAPTER_STRING_MAX": 64, + "CTX_STRING_MAX": 256, + "TYPE_STRING_MAX": 64, + "LINK_HOST_STRING_MAX": 512, + "LEVEL_STRING_MAX": 16, + "LABEL_STRING_MAX": 300, + "TEXT_MAX": 2001, + "BLOCK_DEPTH_MAX": 4096, + "BLOCKS_MAX": 1500, + "DIRECTIONS": { + "extension_to_host": [ + "hello", + "batch" + ], + "host_to_extension": [ + "hello_ack", + "unsupported", + "state", + "boundary", + "accepted", + "bye" + ] + }, + "BRAND_ENUM": [ + "chrome", + "edge", + "firefox" + ], + "CAPTURE_ENUM": [ + "unavailable", + "not_paired", + "permitted", + "paused", + "intake_off" + ], + "DELIVERY_ENUM": [ + "unknown", + "kept_locally", + "delivered", + "idle", + "failed" + ], + "FAILURE_ENUM": [ + "relay_unavailable", + "journal_rejected", + "local_io", + "resource_exhausted", + "queue_full", + "age_policy", + "unaccepted_lost" + ], + "BYE_REASON_ENUM": [ + "shutdown", + "replaced", + "update" + ], + "SNAPSHOT_REASON_ENUM": [ + "delivery_recovery" + ], + "BEHIND_ENUM": [ + "app", + "extension" + ], + "RESULT_ENUM": [ + "accepted", + "duplicate", + "rejected" + ], + "HOSTS_AND_IDS": { + "production": { + "host": "app.solstone.browser", + "chrome_id": "eibbeeoifjoabddfmgeggnageolkcnim", + "edge_id": "eibbeeoifjoabddfmgeggnageolkcnim", + "firefox_id": "browser@solstone.app" + }, + "dev": { + "host": "app.solstone.browser.dev", + "chrome_id": "fgfnkcefedeheoeamppkiiloncfekakf", + "edge_id": "fgfnkcefedeheoeamppkiiloncfekakf", + "firefox_id": "browser.dev@solstone.app" + } + }, + "REGISTRATION": { + "description": "Solstone browser host", + "type": "stdio", + "path_placeholder": "__PATH__", + "suffixes": { + "chrome_linux": ".config/google-chrome/NativeMessagingHosts/.json", + "edge_linux": ".config/microsoft-edge/NativeMessagingHosts/.json", + "firefox_linux": ".mozilla/native-messaging-hosts/.json", + "chrome_macos": "Library/Application Support/Google/Chrome/NativeMessagingHosts/.json", + "edge_macos": "Library/Application Support/Microsoft Edge/NativeMessagingHosts/.json", + "firefox_macos": "Library/Application Support/Mozilla/NativeMessagingHosts/.json", + "chrome_windows": "Software\\Google\\Chrome\\NativeMessagingHosts\\", + "edge_windows": "Software\\Microsoft\\Edge\\NativeMessagingHosts\\", + "firefox_windows": "Software\\Mozilla\\NativeMessagingHosts\\" + }, + "argv": { + "chrome_macos": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "chrome_linux": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "chrome_windows": { + "arguments_after_executable": [ + "origin", + "parent_window" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "edge_macos": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "edge_linux": { + "arguments_after_executable": [ + "origin" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "edge_windows": { + "arguments_after_executable": [ + "origin", + "parent_window" + ], + "identity_argument": 0, + "identity_source": "allowed_origins" + }, + "firefox_macos": { + "arguments_after_executable": [ + "manifest_path", + "extension_id" + ], + "identity_argument": 1, + "identity_source": "allowed_extensions" + }, + "firefox_linux": { + "arguments_after_executable": [ + "manifest_path", + "extension_id" + ], + "identity_argument": 1, + "identity_source": "allowed_extensions" + }, + "firefox_windows": { + "arguments_after_executable": [ + "manifest_path", + "extension_id" + ], + "identity_argument": 1, + "identity_source": "allowed_extensions" + } + }, + "windows": { + "hive": "HKEY_CURRENT_USER", + "value_name": "", + "value_type": "REG_SZ", + "value": "absolute_manifest_path", + "registry_views": [ + "32", + "64" + ], + "path_contains_view": false + } + }, + "RECEIPT_CLASSES": { + "retryable": [ + "snapshot_required", + "resource_exhausted", + "queue_full", + "age_policy" + ], + "permanent": [ + "malformed", + "oversize", + "stale_generation", + "expired_unaccepted", + "unaccepted_lost" + ] + }, + "CANONICAL_KEY_ORDER": { + "hello": [ + "type", + "protocol", + "version", + "brand", + "inst" + ], + "hello_ack": [ + "type", + "capture", + "delivery", + "freshness_ms", + "destination_generation", + "period_id", + "failure", + "custody", + "version" + ], + "unsupported": [ + "type", + "protocol", + "behind" + ], + "state": [ + "type", + "capture", + "delivery", + "freshness_ms", + "destination_generation", + "period_id", + "failure", + "custody", + "version" + ], + "batch": [ + "type", + "destination_generation", + "inst", + "batch_id", + "queued_at_ms", + "records" + ], + "boundary": [ + "type", + "destination_generation", + "period_id" + ], + "accepted": [ + "type", + "result", + "destination_generation", + "inst", + "batch_id", + "period_id", + "reason", + "class" + ], + "bye": [ + "type", + "reason" + ], + "snapshot_record": [ + "t", + "ts", + "rel", + "site", + "url", + "title", + "adapter", + "ctx", + "inst", + "n", + "blocks", + "snapshot_reason" + ], + "delta_record": [ + "t", + "ts", + "rel", + "site", + "ctx", + "inst", + "op", + "block" + ], + "block": [ + "id", + "text", + "type", + "depth", + "attrs" + ], + "block_attrs": [ + "label", + "level", + "linkHost" + ] + } + }, + "corpus": [ + { + "id": "hello_positive", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"chrome\",\"inst\":\"desktop_inst_1\"}" + }, + { + "id": "hello_wrong_direction", + "direction": "host_to_extension", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"chrome\",\"inst\":\"desktop_inst_1\"}" + }, + { + "id": "hello_missing_field", + "direction": "extension_to_host", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"brand\":\"chrome\",\"inst\":\"desktop_inst_1\"}" + }, + { + "id": "hello_ack_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "hello_ack_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "hello_ack_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"delivery\":\"delivered\",\"freshness_ms\":15000}" + }, + { + "id": "unsupported_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"unsupported\",\"protocol\":2,\"behind\":\"app\"}" + }, + { + "id": "unsupported_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"unsupported\",\"protocol\":2,\"behind\":\"app\"}" + }, + { + "id": "unsupported_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"unsupported\",\"protocol\":2}" + }, + { + "id": "state_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"freshness_ms\":15000}" + }, + { + "id": "boundary_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"boundary\",\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "boundary_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"boundary\",\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "boundary_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"boundary\",\"destination_generation\":\"g1\"}" + }, + { + "id": "accepted_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p1\"}" + }, + { + "id": "accepted_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"accepted\",\"result\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p1\"}" + }, + { + "id": "accepted_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p1\"}" + }, + { + "id": "bye_positive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"bye\",\"reason\":\"shutdown\"}" + }, + { + "id": "bye_wrong_direction", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"bye\",\"reason\":\"shutdown\"}" + }, + { + "id": "bye_missing_field", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"bye\"}" + }, + { + "id": "batch_positive", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"b1\",\"text\":\"Hello\"}]}]}" + }, + { + "id": "batch_wrong_direction", + "direction": "host_to_extension", + "expect": "refuse", + "code": "bad_direction", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_missing_field", + "direction": "extension_to_host", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "hello_invalid_brand", + "direction": "extension_to_host", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"safari\",\"inst\":\"inst1\"}" + }, + { + "id": "hello_ack_invalid_capture", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"unknown_capture\",\"delivery\":\"idle\",\"freshness_ms\":15000}" + }, + { + "id": "unsupported_invalid_behind", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"unsupported\",\"protocol\":2,\"behind\":\"unknown_behind\"}" + }, + { + "id": "state_invalid_capture", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"state\",\"capture\":\"unknown_capture\",\"delivery\":\"idle\",\"freshness_ms\":15000}" + }, + { + "id": "batch_record_invalid_snapshot_reason", + "direction": "extension_to_host", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"b1\",\"text\":\"text\"}],\"snapshot_reason\":\"unknown_reason\"}]}" + }, + { + "id": "bye_invalid_reason", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_enum", + "payload": "{\"type\":\"bye\",\"reason\":\"crash\"}" + }, + { + "id": "accepted_invalid_result_enum", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"invalid\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p1\"}" + }, + { + "id": "hello_protocol_unsupported_app", + "direction": "extension_to_host", + "expect": "unsupported", + "behind": "app", + "payload": "{\"type\":\"hello\",\"protocol\":2,\"version\":\"2.0.0\",\"brand\":\"chrome\",\"inst\":\"inst_app\"}" + }, + { + "id": "hello_protocol_unsupported_extension", + "direction": "extension_to_host", + "expect": "unsupported", + "behind": "extension", + "payload": "{\"type\":\"hello\",\"protocol\":0,\"version\":\"0.1.0\",\"brand\":\"chrome\",\"inst\":\"inst_ext\"}" + }, + { + "id": "hello_protocol_version_skew_accepted", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"9.9.9\",\"brand\":\"edge\",\"inst\":\"inst2\"}" + }, + { + "id": "hello_protocol_float_integral", + "direction": "extension_to_host", + "raw": "{\"type\":\"hello\",\"protocol\":1.0,\"version\":\"1.0.0\",\"brand\":\"firefox\",\"inst\":\"inst3\"}", + "expect": "accept" + }, + { + "id": "hello_protocol_bad_number", + "direction": "extension_to_host", + "raw": "{\"type\":\"hello\",\"protocol\":2.5,\"version\":\"2.5.0\",\"brand\":\"chrome\",\"inst\":\"inst\"}", + "expect": "refuse", + "code": "bad_number" + }, + { + "id": "state_matrix_unavailable_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"unknown\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_unavailable_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_unavailable_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"delivered\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_unavailable_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"idle\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_unavailable_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"unavailable\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_matrix_not_paired_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"unknown\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_not_paired_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_not_paired_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"delivered\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_not_paired_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"idle\",\"freshness_ms\":15000}" + }, + { + "id": "state_matrix_not_paired_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_matrix_permitted_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"unknown\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_permitted_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_permitted_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_permitted_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"idle\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_permitted_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_matrix_paused_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"unknown\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_paused_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_paused_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_paused_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"idle\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_paused_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"paused\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_matrix_intake_off_unknown", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"unknown\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_intake_off_kept_locally", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_intake_off_delivered", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"period_id\":\"per_1\"}" + }, + { + "id": "state_matrix_intake_off_idle", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"idle\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\"}" + }, + { + "id": "state_matrix_intake_off_failed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"gen_1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_freshness_boundary_0", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":0,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_freshness_boundary_15000", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_freshness_out_of_range_15001", + "direction": "host_to_extension", + "expect": "refuse", + "code": "freshness_range", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15001,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_failure_independent_of_delivery", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_illegal_ids_when_not_paired", + "direction": "host_to_extension", + "expect": "refuse", + "code": "bad_state_ids", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"idle\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\"}" + }, + { + "id": "state_illegal_missing_ids_when_permitted", + "direction": "host_to_extension", + "expect": "refuse", + "code": "bad_state_ids", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"delivered\",\"freshness_ms\":15000}" + }, + { + "id": "id_missing_snapshot", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"text\":\"content\"}]}]}" + }, + { + "id": "id_empty_snapshot", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"\",\"text\":\"content\"}]}]}" + }, + { + "id": "id_max_len_snapshot", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}]}]}" + }, + { + "id": "id_over_max_len_snapshot", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "too_long", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}]}]}" + }, + { + "id": "id_short_snapshot", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"blocks\":[{\"id\":\"b1\",\"text\":\"content\"}]}]}" + }, + { + "id": "id_missing_add", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"text\":\"content\"}}]}" + }, + { + "id": "id_empty_add", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"\",\"text\":\"content\"}}]}" + }, + { + "id": "id_max_len_add", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}}]}" + }, + { + "id": "id_over_max_len_add", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "too_long", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}}]}" + }, + { + "id": "id_short_add", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"b1\",\"text\":\"content\"}}]}" + }, + { + "id": "id_missing_update", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"text\":\"content\"}}]}" + }, + { + "id": "id_empty_update", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"id\":\"\",\"text\":\"content\"}}]}" + }, + { + "id": "id_max_len_update", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}}]}" + }, + { + "id": "id_over_max_len_update", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "too_long", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"text\":\"content\"}}]}" + }, + { + "id": "id_short_update", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"update\",\"block\":{\"id\":\"b1\",\"text\":\"content\"}}]}" + }, + { + "id": "id_missing_remove", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{}}]}" + }, + { + "id": "id_empty_remove", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"\"}}]}" + }, + { + "id": "id_max_len_remove", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}}]}" + }, + { + "id": "id_over_max_len_remove", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "too_long", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}}]}" + }, + { + "id": "id_short_remove", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"b1\"}}]}" + }, + { + "id": "batch_id_short_31", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_batch_id", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcde\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_id_long_33", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_batch_id", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef0\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_id_uppercase", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_batch_id", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789ABCDEF0123456789ABCDEF\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_id_hyphenated", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_batch_id", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"01234567-89ab-cdef-0123-456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}" + }, + { + "id": "batch_queued_at_ms_fraction_1000_0", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1000.0,\"records\":[{\"t\":\"segment_start\",\"ctx\":\"c\",\"ts\":0,\"blocks\":[{\"id\":\"b1\",\"text\":\"x\"}]}]}", + "expect": "accept" + }, + { + "id": "batch_queued_at_ms_exponential_1e3", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1e3,\"records\":[{\"t\":\"segment_start\",\"ctx\":\"c\",\"ts\":0,\"blocks\":[{\"id\":\"b1\",\"text\":\"x\"}]}]}", + "expect": "accept" + }, + { + "id": "batch_queued_at_ms_above_max", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":9007199254740992,\"records\":[{\"t\":\"segment_start\",\"ctx\":\"c\",\"ts\":0,\"blocks\":[{\"id\":\"b1\",\"text\":\"x\"}]}]}", + "expect": "refuse", + "code": "bad_number" + }, + { + "id": "batch_queued_at_ms_negative", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":-10,\"records\":[{\"t\":\"delta\",\"ctx\":\"c\",\"ts\":100,\"op\":\"remove\",\"block\":{\"id\":\"d1\"}}]}", + "expect": "refuse", + "code": "bad_number" + }, + { + "id": "batch_retry_1", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"retry_gen\",\"inst\":\"retry_inst\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1700000000000,\"records\":[{\"t\":\"delta\",\"ts\":1700000000000,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"b1\",\"text\":\"try1\"}}]}" + }, + { + "id": "batch_retry_2", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"retry_gen\",\"inst\":\"retry_inst\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1700000000000,\"records\":[{\"t\":\"delta\",\"ts\":1700000000000,\"ctx\":\"c\",\"op\":\"add\",\"block\":{\"id\":\"b1\",\"text\":\"try2\"}}]}" + }, + { + "id": "batch_recovery_snapshot", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"retry_gen\",\"inst\":\"retry_inst\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":1700000000000,\"records\":[{\"t\":\"segment_start\",\"ts\":1700000000000,\"site\":\"example.com\",\"ctx\":\"c\",\"n\":1,\"blocks\":[{\"id\":\"b1\",\"text\":\"Recovery snapshot\"}],\"snapshot_reason\":\"delivery_recovery\"}]}" + }, + { + "id": "boundary_recovery", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"boundary\",\"destination_generation\":\"retry_gen\",\"period_id\":\"p_boundary\"}" + }, + { + "id": "accepted_recovery_duplicate", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"duplicate\",\"destination_generation\":\"retry_gen\",\"inst\":\"retry_inst\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\"}" + }, + { + "id": "batch_inst_mismatch_record_inst", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "mismatch", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"inst\":\"mismatched_inst\",\"n\":1,\"blocks\":[{\"id\":\"b1\",\"text\":\"x\"}]}]}" + }, + { + "id": "batch_mixed_context", + "direction": "extension_to_host", + "expect": "refuse", + "code": "mixed_context", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"ctx_a\",\"op\":\"remove\",\"block\":{\"id\":\"d1\"}},{\"t\":\"delta\",\"ts\":100,\"ctx\":\"ctx_b\",\"op\":\"remove\",\"block\":{\"id\":\"d2\"}}]}" + }, + { + "id": "unicode_astral_and_escapes", + "direction": "extension_to_host", + "expect": "accept", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"ctx\":\"c\",\"n\":1,\"blocks\":[{\"id\":\"b1\",\"text\":\"Astral: 😀, Control: \\u0001, Separators: 

\"}]}]}" + }, + { + "id": "lone_surrogate_raw", + "direction": "extension_to_host", + "raw": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ctx\":\"c\",\"ts\":100,\"n\":1,\"blocks\":[{\"id\":\"b1\",\"text\":\"\\uD800 alone\"}]}]}", + "expect": "refuse", + "code": "lone_surrogate" + }, + { + "id": "sentinel_planted_in_failing_record", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"segment_start\",\"ts\":100,\"url\":\"https://example.com/ZQ_SENTINEL_do_not_echo\",\"title\":\"ZQ_SENTINEL_do_not_echo in title\",\"ctx\":\"c\",\"n\":1,\"blocks\":[{\"id\":\"\",\"text\":\"ZQ_SENTINEL_do_not_echo in text\"}]}]}" + }, + { + "id": "receipt_rejected_snapshot_required", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_wrong_class_snapshot_required", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"snapshot_required\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_rejected_resource_exhausted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"resource_exhausted\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_wrong_class_resource_exhausted", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"resource_exhausted\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_rejected_queue_full", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"queue_full\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_wrong_class_queue_full", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"queue_full\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_rejected_age_policy", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"age_policy\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_wrong_class_age_policy", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"age_policy\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_rejected_malformed", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"malformed\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_malformed", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"malformed\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_oversize", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"oversize\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_oversize", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"oversize\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_stale_generation", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"stale_generation\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_stale_generation", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"stale_generation\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_expired_unaccepted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"expired_unaccepted\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_expired_unaccepted", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"expired_unaccepted\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_unaccepted_lost", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"unaccepted_lost\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_wrong_class_unaccepted_lost", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"unaccepted_lost\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_success_accepted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\"}" + }, + { + "id": "receipt_success_with_reason_accepted", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"accepted\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_success_duplicate", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"accepted\",\"result\":\"duplicate\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\"}" + }, + { + "id": "receipt_success_with_reason_duplicate", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"duplicate\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_original\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_rejected_with_period", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"period_id\":\"p_wrong\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_unknown_reason", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"unknown\",\"class\":\"permanent\"}" + }, + { + "id": "receipt_missing_destination_generation", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_missing_inst", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "receipt_missing_batch_id", + "direction": "host_to_extension", + "expect": "refuse", + "code": "invalid_receipt", + "payload": "{\"type\":\"accepted\",\"result\":\"rejected\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"reason\":\"snapshot_required\",\"class\":\"retryable\"}" + }, + { + "id": "state_capture_pressure_resource_exhausted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"resource_exhausted\"}" + }, + { + "id": "state_capture_pressure_queue_full", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"queue_full\"}" + }, + { + "id": "state_capture_pressure_age_policy", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"age_policy\"}" + }, + { + "id": "state_capture_pressure_unaccepted_lost", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"unaccepted_lost\"}" + }, + { + "id": "state_custody_false_false", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":false}}" + }, + { + "id": "state_custody_false_true", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":true}}" + }, + { + "id": "state_custody_true_false", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true,\"stale\":false}}" + }, + { + "id": "state_custody_true_true", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true,\"stale\":true}}" + }, + { + "id": "state_custody_omitted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "state_custody_additive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"future_fact\":\"preserved\",\"stale\":true}}" + }, + { + "id": "state_custody_invalid_null", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":null}" + }, + { + "id": "state_custody_invalid_array", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":[]}" + }, + { + "id": "state_custody_invalid_empty", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{}}" + }, + { + "id": "state_custody_invalid_missing_full", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"stale\":true}}" + }, + { + "id": "state_custody_invalid_missing_stale", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true}}" + }, + { + "id": "state_custody_invalid_full_type", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":1,\"stale\":false}}" + }, + { + "id": "state_custody_invalid_stale_type", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":\"true\"}}" + }, + { + "id": "state_null_unpaired", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"unknown\",\"freshness_ms\":0,\"destination_generation\":null,\"period_id\":null}" + }, + { + "id": "state_no_freshness", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"state\",\"capture\":\"not_paired\",\"delivery\":\"unknown\"}" + }, + { + "id": "hello_ack_capture_pressure_resource_exhausted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"resource_exhausted\"}" + }, + { + "id": "hello_ack_capture_pressure_queue_full", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"queue_full\"}" + }, + { + "id": "hello_ack_capture_pressure_age_policy", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"age_policy\"}" + }, + { + "id": "hello_ack_capture_pressure_unaccepted_lost", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"intake_off\",\"delivery\":\"kept_locally\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":null,\"failure\":\"unaccepted_lost\"}" + }, + { + "id": "hello_ack_custody_false_false", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":false}}" + }, + { + "id": "hello_ack_custody_false_true", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":true}}" + }, + { + "id": "hello_ack_custody_true_false", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true,\"stale\":false}}" + }, + { + "id": "hello_ack_custody_true_true", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true,\"stale\":true}}" + }, + { + "id": "hello_ack_custody_omitted", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\"}" + }, + { + "id": "hello_ack_custody_additive", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"future_fact\":\"preserved\",\"stale\":true}}" + }, + { + "id": "hello_ack_custody_invalid_null", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":null}" + }, + { + "id": "hello_ack_custody_invalid_array", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":[]}" + }, + { + "id": "hello_ack_custody_invalid_empty", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{}}" + }, + { + "id": "hello_ack_custody_invalid_missing_full", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"stale\":true}}" + }, + { + "id": "hello_ack_custody_invalid_missing_stale", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":true}}" + }, + { + "id": "hello_ack_custody_invalid_full_type", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":1,\"stale\":false}}" + }, + { + "id": "hello_ack_custody_invalid_stale_type", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"permitted\",\"delivery\":\"failed\",\"freshness_ms\":15000,\"destination_generation\":\"g1\",\"period_id\":\"p1\",\"failure\":\"relay_unavailable\",\"custody\":{\"full\":false,\"stale\":\"true\"}}" + }, + { + "id": "hello_ack_null_unpaired", + "direction": "host_to_extension", + "expect": "accept", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"not_paired\",\"delivery\":\"unknown\",\"freshness_ms\":0,\"destination_generation\":null,\"period_id\":null}" + }, + { + "id": "hello_ack_no_freshness", + "direction": "host_to_extension", + "expect": "refuse", + "code": "missing_field", + "payload": "{\"type\":\"hello_ack\",\"capture\":\"not_paired\",\"delivery\":\"unknown\"}" + }, + { + "id": "batch_ctx_missing", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"op\":\"remove\",\"block\":{\"id\":\"b\"}}]}" + }, + { + "id": "batch_ctx_empty", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "empty", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"\",\"op\":\"remove\",\"block\":{\"id\":\"b\"}}]}" + }, + { + "id": "batch_ctx_missing_first", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"op\":\"remove\",\"block\":{\"id\":\"b\"}},{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"b\"}}]}" + }, + { + "id": "batch_ctx_missing_last", + "direction": "extension_to_host", + "expect": "refuse", + "code": "bad_record", + "cause": "missing", + "payload": "{\"type\":\"batch\",\"destination_generation\":\"g1\",\"inst\":\"inst1\",\"batch_id\":\"0123456789abcdef0123456789abcdef\",\"queued_at_ms\":100,\"records\":[{\"t\":\"delta\",\"ts\":100,\"ctx\":\"c\",\"op\":\"remove\",\"block\":{\"id\":\"b\"}},{\"t\":\"delta\",\"ts\":100,\"op\":\"remove\",\"block\":{\"id\":\"b\"}}]}" + }, + { + "id": "json_depth_at_limit", + "direction": "extension_to_host", + "raw": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"chrome\",\"inst\":\"depth\",\"extra\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}", + "expect": "accept" + }, + { + "id": "json_depth_over_limit", + "direction": "extension_to_host", + "raw": "{\"type\":\"hello\",\"protocol\":1,\"version\":\"1.0.0\",\"brand\":\"chrome\",\"inst\":\"depth\",\"extra\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}", + "expect": "refuse", + "code": "bad_json" + } + ] +} diff --git a/vendor/crates/native-browser-frame/Cargo.toml b/vendor/crates/native-browser-frame/Cargo.toml new file mode 100644 index 0000000..93592fe --- /dev/null +++ b/vendor/crates/native-browser-frame/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "native-browser-frame" +version = "1.0.0" +edition = "2021" +rust-version = "1.97.1" +license = "AGPL-3.0-only" +publish = false + +[dependencies] +serde_json = "1.0" +jsonschema = { version = "0.58.2", default-features = false } diff --git a/vendor/crates/native-browser-frame/src/codec.rs b/vendor/crates/native-browser-frame/src/codec.rs new file mode 100644 index 0000000..bbdfbb5 --- /dev/null +++ b/vendor/crates/native-browser-frame/src/codec.rs @@ -0,0 +1,692 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +use crate::constants::*; +use crate::frame::Direction; +use serde_json::Value; + +pub const ENVELOPE_SCHEMA_STR: &str = + include_str!("../../../contracts/native-browser/envelope.schema.json"); +pub const JOURNAL_SCHEMA_STR: &str = + include_str!("../../../contracts/native-browser/browser.schema.json"); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DecodeOutcome { + Accept(Value), + Unsupported { + protocol: u64, + version: String, + behind: String, + }, + Refuse(DecodeError), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DecodeError { + pub code: String, + pub class: Option, + pub row: Option, + pub field: Option, + pub cause: Option, +} + +impl std::fmt::Display for DecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.code)?; + if let Some(ref cause) = self.cause { + write!(f, " cause: {}", cause)?; + } + if let Some(row) = self.row { + write!(f, " row: {}", row)?; + } + if let Some(ref field) = self.field { + write!(f, " field: {}", field)?; + } + Ok(()) + } +} + +impl std::error::Error for DecodeError {} + +impl DecodeError { + pub fn new(code: &str) -> Self { + Self { + code: code.to_string(), + class: None, + row: None, + field: None, + cause: None, + } + } + + pub fn with_record_error(code: &str, row: usize, field: &str, cause: &str) -> Self { + Self { + code: code.to_string(), + class: None, + row: Some(row), + field: Some(field.to_string()), + cause: Some(cause.to_string()), + } + } +} + +use std::sync::OnceLock; + +static VALIDATOR: OnceLock> = OnceLock::new(); + +pub fn get_validator() -> Result<&'static jsonschema::Validator, DecodeError> { + let res = VALIDATOR.get_or_init(|| { + let journal_value: Value = serde_json::from_str(JOURNAL_SCHEMA_STR) + .map_err(|e| format!("journal schema parse error: {}", e))?; + let envelope_value: Value = serde_json::from_str(ENVELOPE_SCHEMA_STR) + .map_err(|e| format!("envelope schema parse error: {}", e))?; + let registry = jsonschema::Registry::new() + .add("solstone-journal-format:browser-jsonl", journal_value) + .map_err(|e| format!("registry add error: {}", e))? + .prepare() + .map_err(|e| format!("registry prepare error: {}", e))?; + let validator = jsonschema::options() + .with_draft(jsonschema::Draft::Draft202012) + .offline() + .with_registry(®istry) + .build(&envelope_value) + .map_err(|e| format!("validator build error: {}", e))?; + Ok(validator) + }); + match res { + Ok(v) => Ok(v), + Err(_) => Err(DecodeError::new("bad_json")), + } +} + +/// JSON Schema integers may arrive as integral decimal/exponent values. +pub fn nonnegative_integer(value: &Value) -> Option { + if let Some(number) = value.as_u64() { + return (number <= TIMESTAMP_MAX).then_some(number); + } + let number = value.as_f64()?; + (number.is_finite() && number >= 0.0 && number <= TIMESTAMP_MAX as f64 && number.fract() == 0.0) + .then_some(number as u64) +} + +static RECORD_VALIDATOR: OnceLock> = OnceLock::new(); +fn record_validator() -> Result<&'static jsonschema::Validator, DecodeError> { + RECORD_VALIDATOR + .get_or_init(|| { + let schema: Value = + serde_json::from_str(JOURNAL_SCHEMA_STR).map_err(|_| "schema".to_string())?; + jsonschema::options() + .with_draft(jsonschema::Draft::Draft202012) + .offline() + .build(&schema) + .map_err(|_| "schema".to_string()) + }) + .as_ref() + .map_err(|_| DecodeError::new("bad_json")) +} + +fn block_id_error(records: &[Value]) -> Option { + for (row, record) in records.iter().enumerate() { + let blocks: Vec<&Value> = + if record.get("t").and_then(Value::as_str) == Some("segment_start") { + record + .get("blocks") + .and_then(Value::as_array) + .map(|blocks| blocks.iter().collect()) + .unwrap_or_default() + } else { + record.get("block").into_iter().collect() + }; + for block in blocks { + let cause = match block.get("id") { + None => Some("missing"), + Some(Value::String(id)) if id.is_empty() => Some("empty"), + Some(Value::String(id)) if id.chars().count() > ID_STRING_MAX => Some("too_long"), + Some(Value::String(_)) => None, + Some(_) => Some("type"), + }; + if let Some(cause) = cause { + return Some(DecodeError::with_record_error( + "bad_record", + row, + "id", + cause, + )); + } + } + } + None +} + +fn state_ids_allowed(value: &Value) -> bool { + let generation = value.get("destination_generation").filter(|v| !v.is_null()); + let period = value.get("period_id").filter(|v| !v.is_null()); + let nonempty = |v: Option<&Value>| v.and_then(Value::as_str).is_some_and(|s| !s.is_empty()); + match value.get("capture").and_then(Value::as_str) { + Some("unavailable" | "not_paired") => generation.is_none() && period.is_none(), + Some("permitted") => nonempty(generation) && nonempty(period), + Some("paused" | "intake_off") => { + nonempty(generation) && (period.is_none() || nonempty(period)) + } + _ => false, + } +} + +// Map only bounded machine fields; never stringify a schema error or input value. +fn schema_error(value: &Value, message_type: &str) -> DecodeError { + if message_type == "accepted" { + return DecodeError::new("invalid_receipt"); + } + static ENVELOPE: OnceLock = OnceLock::new(); + let schema = ENVELOPE + .get_or_init(|| serde_json::from_str(ENVELOPE_SCHEMA_STR).expect("embedded schema")); + if schema["$defs"][message_type]["required"] + .as_array() + .is_some_and(|fields| { + fields + .iter() + .any(|f| value.get(f.as_str().unwrap()).is_none()) + }) + { + return DecodeError::new("missing_field"); + } + for field in ["protocol", "queued_at_ms"] { + if value + .get(field) + .is_some_and(|v| nonnegative_integer(v).is_none()) + { + return DecodeError::new("bad_number"); + } + } + if matches!(message_type, "state" | "hello_ack") { + if value + .get("freshness_ms") + .is_some_and(|v| nonnegative_integer(v).is_none_or(|v| v > FRESHNESS_MS_MAX)) + { + return DecodeError::new("freshness_range"); + } + for (field, allowed) in [ + ("capture", CAPTURE_ENUM), + ("delivery", DELIVERY_ENUM), + ("failure", FAILURE_ENUM), + ] { + if value + .get(field) + .is_some_and(|v| !v.is_null() && v.as_str().is_none_or(|s| !allowed.contains(&s))) + { + return DecodeError::new("invalid_enum"); + } + } + if !state_ids_allowed(value) { + return DecodeError::new("bad_state_ids"); + } + } + for (field, allowed) in [ + ("brand", BRAND_ENUM), + ("behind", BEHIND_ENUM), + ("reason", BYE_REASON_ENUM), + ] { + if value + .get(field) + .is_some_and(|v| v.as_str().is_none_or(|s| !allowed.contains(&s))) + { + return DecodeError::new("invalid_enum"); + } + } + if message_type == "batch" { + if value + .get("batch_id") + .and_then(Value::as_str) + .is_none_or(|id| { + id.len() != BATCH_ID_HEX_LEN + || !id + .bytes() + .all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) + }) + { + return DecodeError::new("bad_batch_id"); + } + if let Some(records) = value.get("records").and_then(Value::as_array) { + if records.len() > DELTA_RECORDS_MAX { + return DecodeError::new("too_many_deltas"); + } + if records.is_empty() { + return DecodeError::new("bad_record"); + } + if record_validator() + .is_ok_and(|validator| records.iter().any(|record| !validator.is_valid(record))) + { + return block_id_error(records).unwrap_or_else(|| DecodeError::new("bad_record")); + } + } else { + return DecodeError::new("bad_record"); + } + } + DecodeError::new("missing_field") +} + +// --- Canonical JSON Encoder --- + +fn escape_canonical_string(s: &str, out: &mut String) -> Result<(), &'static str> { + out.push('"'); + for c in s.chars() { + match c { + '"' => out.push_str("\\\""), + '\\' => out.push_str("\\\\"), + '\x00'..='\x1f' => { + out.push_str(&format!("\\u{:04x}", c as u32)); + } + _ => out.push(c), + } + } + out.push('"'); + Ok(()) +} + +fn depth_allowed(value: &Value) -> bool { + let mut pending = vec![(value, 0)]; + while let Some((value, parent_depth)) = pending.pop() { + match value { + Value::Array(items) => { + if parent_depth >= JSON_MAX_DEPTH { + return false; + } + pending.extend(items.iter().map(|item| (item, parent_depth + 1))); + } + Value::Object(items) => { + if parent_depth >= JSON_MAX_DEPTH { + return false; + } + pending.extend(items.values().map(|item| (item, parent_depth + 1))); + } + _ => {} + } + } + true +} + +pub fn canonical_stringify(val: &Value, out: &mut String) -> Result<(), &'static str> { + if !depth_allowed(val) { + return Err("bad_json"); + } + write_canonical(val, out) +} + +fn write_canonical(val: &Value, out: &mut String) -> Result<(), &'static str> { + match val { + Value::Null => out.push_str("null"), + Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }), + Value::Number(n) => out.push_str(&n.to_string()), + Value::String(s) => escape_canonical_string(s, out)?, + Value::Array(arr) => { + out.push('['); + for (i, item) in arr.iter().enumerate() { + if i > 0 { + out.push(','); + } + write_canonical(item, out)?; + } + out.push(']'); + } + Value::Object(map) => { + static KEY_ORDER: OnceLock>> = + OnceLock::new(); + let key_order = KEY_ORDER.get_or_init(|| { + serde_json::from_str(CANONICAL_KEY_ORDER_JSON).expect("generated key order") + }); + let known_type = map + .get("type") + .and_then(Value::as_str) + .filter(|kind| key_order.contains_key(*kind)); + let record_type = map + .get("t") + .and_then(Value::as_str) + .and_then(|kind| match kind { + "segment_start" => Some("snapshot_record"), + "delta" => Some("delta_record"), + _ => None, + }); + let kind = known_type.or(record_type).or_else(|| { + if map.contains_key("id") || map.contains_key("text") { + Some("block") + } else if map.contains_key("label") + || map.contains_key("level") + || map.contains_key("linkHost") + { + Some("block_attrs") + } else { + None + } + }); + let known_order: Vec<&str> = kind + .and_then(|kind| key_order.get(kind)) + .map(|keys| keys.iter().map(String::as_str).collect()) + .unwrap_or_default(); + + let mut ordered_keys: Vec<&str> = Vec::new(); + for &k in &known_order { + if map.contains_key(k) { + ordered_keys.push(k); + } + } + + let mut unknown_keys: Vec<&str> = map + .keys() + .map(|s| s.as_str()) + .filter(|k| !known_order.contains(k)) + .collect(); + unknown_keys.sort_by(|a, b| a.encode_utf16().cmp(b.encode_utf16())); + + ordered_keys.extend(unknown_keys); + + out.push('{'); + for (i, &k) in ordered_keys.iter().enumerate() { + if i > 0 { + out.push(','); + } + escape_canonical_string(k, out)?; + out.push(':'); + write_canonical(&map[k], out)?; + } + out.push('}'); + } + } + Ok(()) +} + +pub fn encode(val: &Value) -> Result, DecodeError> { + let mut out = String::new(); + canonical_stringify(val, &mut out).map_err(|e| DecodeError::new(e))?; + Ok(out.into_bytes()) +} + +// --- Decoder --- + +pub fn decode(bytes: &[u8], direction: Direction) -> DecodeOutcome { + if bytes.is_empty() { + return DecodeOutcome::Refuse(DecodeError::new("empty_payload")); + } + if bytes.len() > direction.socket_cap() { + return DecodeOutcome::Refuse(DecodeError::new("oversize")); + } + let text = match std::str::from_utf8(bytes) { + Ok(text) => text, + Err(_) => return DecodeOutcome::Refuse(DecodeError::new("bad_utf8")), + }; + if text.trim().is_empty() { + return DecodeOutcome::Refuse(DecodeError::new("empty_payload")); + } + // Parse the actual payload first: never repair number lexemes or scan through escaped strings. + let value: Value = match serde_json::from_str(text) { + Ok(value) => value, + Err(error) => { + let description = error.to_string(); + let code = if description.starts_with("lone leading surrogate") + || description.starts_with("unexpected end of hex escape") + { + "lone_surrogate" + } else if description.starts_with("number out of range") { + "bad_number" + } else { + "bad_json" + }; + return DecodeOutcome::Refuse(DecodeError::new(code)); + } + }; + if !depth_allowed(&value) { + return DecodeOutcome::Refuse(DecodeError::new("bad_json")); + } + let Some(object) = value.as_object() else { + return DecodeOutcome::Refuse(DecodeError::new("bad_json")); + }; + let Some(message_type) = object.get("type").and_then(Value::as_str) else { + return DecodeOutcome::Refuse(DecodeError::new("missing_field")); + }; + let expected_direction = match message_type { + "hello" | "batch" => Direction::ExtensionToHost, + "hello_ack" | "unsupported" | "state" | "boundary" | "accepted" | "bye" => { + Direction::HostToExtension + } + _ => return DecodeOutcome::Refuse(DecodeError::new("bad_type")), + }; + if direction != expected_direction { + return DecodeOutcome::Refuse(DecodeError::new("bad_direction")); + } + if message_type != "batch" && bytes.len() > CONTROL_MAX { + return DecodeOutcome::Refuse(DecodeError::new("oversize")); + } + let validator = match get_validator() { + Ok(v) => v, + Err(error) => return DecodeOutcome::Refuse(error), + }; + if !validator.is_valid(&value) { + return DecodeOutcome::Refuse(schema_error(&value, message_type)); + } + if message_type == "hello" { + let Some(protocol) = value.get("protocol").and_then(nonnegative_integer) else { + return DecodeOutcome::Refuse(DecodeError::new("bad_number")); + }; + if protocol != u64::from(WIRE_PROTOCOL) { + return DecodeOutcome::Unsupported { + protocol, + version: value["version"].as_str().unwrap().to_owned(), + behind: if protocol > u64::from(WIRE_PROTOCOL) { + "app" + } else { + "extension" + } + .to_owned(), + }; + } + } + if message_type == "batch" { + let records = value["records"].as_array().expect("validated records"); + let snapshot = records[0]["t"] == "segment_start"; + if snapshot && records.len() != 1 { + return DecodeOutcome::Refuse(DecodeError::new("bad_record")); + } + for (row, record) in records.iter().enumerate() { + let cause = match record.get("ctx").and_then(Value::as_str) { + None => Some("missing"), + Some("") => Some("empty"), + Some(_) => None, + }; + if let Some(cause) = cause { + return DecodeOutcome::Refuse(DecodeError::with_record_error( + "bad_record", + row, + "ctx", + cause, + )); + } + } + let context = records[0]["ctx"] + .as_str() + .expect("validated native context"); + for (row, record) in records.iter().enumerate() { + if record["t"] != if snapshot { "segment_start" } else { "delta" } { + return DecodeOutcome::Refuse(DecodeError::new("bad_record")); + } + if record.get("ctx").and_then(Value::as_str) != Some(context) { + return DecodeOutcome::Refuse(DecodeError::new("mixed_context")); + } + if record + .get("inst") + .is_some_and(|inst| inst != &value["inst"]) + { + return DecodeOutcome::Refuse(DecodeError::with_record_error( + "bad_record", + row, + "inst", + "mismatch", + )); + } + if record + .get("snapshot_reason") + .is_some_and(|reason| reason.as_str() != Some("delivery_recovery")) + { + return DecodeOutcome::Refuse(DecodeError::new("invalid_enum")); + } + } + if let Some(error) = block_id_error(records) { + return DecodeOutcome::Refuse(error); + } + } + DecodeOutcome::Accept(value) +} + +// --- Recipe Builder in Rust --- + +pub fn build_recipe(recipe_id: &str) -> Result, &'static str> { + let mut base_obj = match recipe_id { + "extension_to_host_batch_max" | "extension_to_host_batch_oversize" => { + serde_json::json!({ + "type": "batch", + "destination_generation": "g", + "inst": "i", + "batch_id": "0123456789abcdef0123456789abcdef", + "queued_at_ms": 0, + "records": [{ + "t": "segment_start", + "ts": 0, + "ctx": "c", + "blocks": [{"id": "b", "text": "x"}] + }], + "pad": "" + }) + } + "control_payload_max" | "control_payload_oversize" => { + serde_json::json!({ + "type": "hello", + "protocol": 1, + "version": "1.0.0", + "brand": "chrome", + "inst": "inst1", + "pad": "" + }) + } + "batch_delta_cap_3000" => { + let mut deltas = Vec::new(); + for i in 0..1500 { + deltas.push(serde_json::json!({"t": "delta", "ts": 0, "ctx": "c", "op": "add", "block": {"id": format!("a{}", i), "text": "t"}})); + deltas.push(serde_json::json!({"t": "delta", "ts": 0, "ctx": "c", "op": "remove", "block": {"id": format!("r{}", i)}})); + } + let obj = serde_json::json!({ + "type": "batch", + "destination_generation": "g", + "inst": "i", + "batch_id": "0123456789abcdef0123456789abcdef", + "queued_at_ms": 0, + "records": deltas + }); + return encode(&obj).map_err(|_| "encode error"); + } + "batch_delta_oversize_3001" => { + let mut deltas = Vec::new(); + for i in 0..1500 { + deltas.push(serde_json::json!({"t": "delta", "ts": 0, "ctx": "c", "op": "add", "block": {"id": format!("a{}", i), "text": "t"}})); + deltas.push(serde_json::json!({"t": "delta", "ts": 0, "ctx": "c", "op": "remove", "block": {"id": format!("r{}", i)}})); + } + deltas.push(serde_json::json!({"t": "delta", "ts": 0, "ctx": "c", "op": "add", "block": {"id": "extra", "text": "t"}})); + let obj = serde_json::json!({ + "type": "batch", + "destination_generation": "g", + "inst": "i", + "batch_id": "0123456789abcdef0123456789abcdef", + "queued_at_ms": 0, + "records": deltas + }); + return encode(&obj).map_err(|_| "encode error"); + } + _ => return Err("unknown recipe"), + }; + + let target_len = match recipe_id { + "extension_to_host_batch_max" => EXTENSION_TO_HOST_MAX, + "extension_to_host_batch_oversize" => EXTENSION_TO_HOST_MAX + 1, + "control_payload_max" => CONTROL_MAX, + "control_payload_oversize" => CONTROL_MAX + 1, + _ => return Err("unknown target length"), + }; + + let empty_bytes = encode(&base_obj).map_err(|_| "encode error")?; + let pad_len = target_len - empty_bytes.len(); + base_obj["pad"] = Value::String("a".repeat(pad_len)); + + let final_bytes = encode(&base_obj).map_err(|_| "encode error")?; + if final_bytes.len() != target_len { + return Err("recipe length mismatch"); + } + + Ok(final_bytes) +} + +pub fn build_reply(receipt: &Value) -> Result { + let result = receipt + .get("result") + .or_else(|| receipt.get("outcome")) + .and_then(Value::as_str) + .unwrap_or(if receipt.get("reason").is_some() { + "rejected" + } else { + "" + }); + let result = match result { + "accepted" if receipt.get("duplicate").and_then(Value::as_bool) == Some(true) => { + "duplicate" + } + "accepted" | "duplicate" => result, + "rejected" | "backpressure" | "loss" => "rejected", + _ => return Err("invalid_receipt"), + }; + let mut reply = serde_json::Map::new(); + reply.insert("type".to_owned(), Value::String("accepted".to_owned())); + reply.insert("result".to_owned(), Value::String(result.to_owned())); + for field in ["destination_generation", "inst", "batch_id"] { + reply.insert( + field.to_owned(), + receipt.get(field).cloned().ok_or("invalid_receipt")?, + ); + } + if result == "rejected" { + let reason = receipt + .get("reason") + .and_then(Value::as_str) + .ok_or("invalid_receipt")?; + let class = if RETRYABLE_REASONS.contains(&reason) { + "retryable" + } else if PERMANENT_REASONS.contains(&reason) { + "permanent" + } else { + return Err("invalid_receipt"); + }; + if receipt + .get("class") + .is_some_and(|value| value.as_str() != Some(class)) + { + return Err("invalid_receipt"); + } + if receipt.get("period_id").is_some() { + return Err("invalid_receipt"); + } + reply.insert("reason".to_owned(), Value::String(reason.to_owned())); + reply.insert("class".to_owned(), Value::String(class.to_owned())); + } else { + if receipt.get("reason").is_some() || receipt.get("class").is_some() { + return Err("invalid_receipt"); + } + reply.insert( + "period_id".to_owned(), + receipt.get("period_id").cloned().ok_or("invalid_receipt")?, + ); + } + let reply = Value::Object(reply); + let encoded = encode(&reply).map_err(|_| "invalid_receipt")?; + if !matches!( + decode(&encoded, Direction::HostToExtension), + DecodeOutcome::Accept(_) + ) { + return Err("invalid_receipt"); + } + Ok(reply) +} diff --git a/vendor/crates/native-browser-frame/src/constants.rs b/vendor/crates/native-browser-frame/src/constants.rs new file mode 100644 index 0000000..c249663 --- /dev/null +++ b/vendor/crates/native-browser-frame/src/constants.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +pub const BUNDLE_VERSION: &str = "1.1.0"; +pub const WIRE_PROTOCOL: u32 = 1; + +pub const EXTENSION_TO_HOST_MAX: usize = 33554432; +pub const HOST_TO_EXTENSION_MAX: usize = 65536; +pub const CONTROL_MAX: usize = 65536; +pub const DELTA_RECORDS_MAX: usize = 3000; +pub const BATCH_ID_HEX_LEN: usize = 32; +// Object and array containers count toward depth; the root container counts as one. +pub const JSON_MAX_DEPTH: usize = 127; + +pub const FILE_MAX: usize = 50331648; +pub const OUTBOX_BYTES_MAX: usize = 67108864; +pub const OUTBOX_AGE_MS_MAX: u64 = 600000; +pub const SPOOL_BYTES_MAX: usize = 536870912; +pub const SPOOL_AGE_MS_MAX: u64 = 604800000; +pub const FUTURE_SKEW_MS_MAX: u64 = 60000; +pub const ACCEPTED_RETENTION_MS_MIN: u64 = 1200000; +pub const HANDSHAKE_MS_BUDGET: u64 = 5000; +pub const STATE_RENEWAL_MS_INTERVAL: u64 = 5000; +pub const FRESHNESS_MS_MAX: u64 = 15000; +pub const PARTIAL_FRAME_MS_LIFETIME: u64 = 30000; +pub const TIMESTAMP_MAX: u64 = 9007199254740991; + +pub const VERSION_MAX: usize = 64; +pub const GENERATION_MAX: usize = 128; +pub const PERIOD_ID_MAX: usize = 128; +pub const FAILURE_CODE_MAX: usize = 64; + +pub const INST_STRING_MAX: usize = 128; +pub const ID_STRING_MAX: usize = 256; +pub const TITLE_STRING_MAX: usize = 8192; +pub const URL_STRING_MAX: usize = 32768; +pub const SITE_STRING_MAX: usize = 512; +pub const ADAPTER_STRING_MAX: usize = 64; +pub const CTX_STRING_MAX: usize = 256; +pub const TYPE_STRING_MAX: usize = 64; +pub const LINK_HOST_STRING_MAX: usize = 512; +pub const LEVEL_STRING_MAX: usize = 16; +pub const LABEL_STRING_MAX: usize = 300; +pub const TEXT_MAX: usize = 2001; +pub const BLOCK_DEPTH_MAX: u64 = 4096; +pub const BLOCKS_MAX: usize = 1500; + +pub const BRAND_ENUM: &[&str] = &["chrome", "edge", "firefox"]; +pub const CAPTURE_ENUM: &[&str] = &["unavailable", "not_paired", "permitted", "paused", "intake_off"]; +pub const DELIVERY_ENUM: &[&str] = &["unknown", "kept_locally", "delivered", "idle", "failed"]; +pub const FAILURE_ENUM: &[&str] = &["relay_unavailable", "journal_rejected", "local_io", "resource_exhausted", "queue_full", "age_policy", "unaccepted_lost"]; +pub const BYE_REASON_ENUM: &[&str] = &["shutdown", "replaced", "update"]; +pub const SNAPSHOT_REASON_ENUM: &[&str] = &["delivery_recovery"]; +pub const BEHIND_ENUM: &[&str] = &["app", "extension"]; + +pub const RESULT_ENUM: &[&str] = &["accepted", "duplicate", "rejected"]; +pub const RETRYABLE_REASONS: &[&str] = &["snapshot_required", "resource_exhausted", "queue_full", "age_policy"]; +pub const PERMANENT_REASONS: &[&str] = &["malformed", "oversize", "stale_generation", "expired_unaccepted", "unaccepted_lost"]; +pub const REGISTRATION_JSON: &str = r#"{"description":"Solstone browser host","type":"stdio","path_placeholder":"__PATH__","suffixes":{"chrome_linux":".config/google-chrome/NativeMessagingHosts/.json","edge_linux":".config/microsoft-edge/NativeMessagingHosts/.json","firefox_linux":".mozilla/native-messaging-hosts/.json","chrome_macos":"Library/Application Support/Google/Chrome/NativeMessagingHosts/.json","edge_macos":"Library/Application Support/Microsoft Edge/NativeMessagingHosts/.json","firefox_macos":"Library/Application Support/Mozilla/NativeMessagingHosts/.json","chrome_windows":"Software\\Google\\Chrome\\NativeMessagingHosts\\","edge_windows":"Software\\Microsoft\\Edge\\NativeMessagingHosts\\","firefox_windows":"Software\\Mozilla\\NativeMessagingHosts\\"},"argv":{"chrome_macos":{"arguments_after_executable":["origin"],"identity_argument":0,"identity_source":"allowed_origins"},"chrome_linux":{"arguments_after_executable":["origin"],"identity_argument":0,"identity_source":"allowed_origins"},"chrome_windows":{"arguments_after_executable":["origin","parent_window"],"identity_argument":0,"identity_source":"allowed_origins"},"edge_macos":{"arguments_after_executable":["origin"],"identity_argument":0,"identity_source":"allowed_origins"},"edge_linux":{"arguments_after_executable":["origin"],"identity_argument":0,"identity_source":"allowed_origins"},"edge_windows":{"arguments_after_executable":["origin","parent_window"],"identity_argument":0,"identity_source":"allowed_origins"},"firefox_macos":{"arguments_after_executable":["manifest_path","extension_id"],"identity_argument":1,"identity_source":"allowed_extensions"},"firefox_linux":{"arguments_after_executable":["manifest_path","extension_id"],"identity_argument":1,"identity_source":"allowed_extensions"},"firefox_windows":{"arguments_after_executable":["manifest_path","extension_id"],"identity_argument":1,"identity_source":"allowed_extensions"}},"windows":{"hive":"HKEY_CURRENT_USER","value_name":"","value_type":"REG_SZ","value":"absolute_manifest_path","registry_views":["32","64"],"path_contains_view":false}}"#; +pub const CANONICAL_KEY_ORDER_JSON: &str = r#"{"hello":["type","protocol","version","brand","inst"],"hello_ack":["type","capture","delivery","freshness_ms","destination_generation","period_id","failure","custody","version"],"unsupported":["type","protocol","behind"],"state":["type","capture","delivery","freshness_ms","destination_generation","period_id","failure","custody","version"],"batch":["type","destination_generation","inst","batch_id","queued_at_ms","records"],"boundary":["type","destination_generation","period_id"],"accepted":["type","result","destination_generation","inst","batch_id","period_id","reason","class"],"bye":["type","reason"],"snapshot_record":["t","ts","rel","site","url","title","adapter","ctx","inst","n","blocks","snapshot_reason"],"delta_record":["t","ts","rel","site","ctx","inst","op","block"],"block":["id","text","type","depth","attrs"],"block_attrs":["label","level","linkHost"]}"#; + +pub const REGISTRATION_DESCRIPTION: &str = "Solstone browser host"; +pub const REGISTRATION_TYPE: &str = "stdio"; +pub const REGISTRATION_PATH_PLACEHOLDER: &str = "__PATH__"; + +pub const PROD_HOST: &str = "app.solstone.browser"; +pub const PROD_CHROME_ID: &str = "eibbeeoifjoabddfmgeggnageolkcnim"; +pub const PROD_EDGE_ID: &str = "eibbeeoifjoabddfmgeggnageolkcnim"; +pub const PROD_FIREFOX_ID: &str = "browser@solstone.app"; + +pub const DEV_HOST: &str = "app.solstone.browser.dev"; +pub const DEV_CHROME_ID: &str = "fgfnkcefedeheoeamppkiiloncfekakf"; +pub const DEV_EDGE_ID: &str = "fgfnkcefedeheoeamppkiiloncfekakf"; +pub const DEV_FIREFOX_ID: &str = "browser.dev@solstone.app"; diff --git a/vendor/crates/native-browser-frame/src/frame.rs b/vendor/crates/native-browser-frame/src/frame.rs new file mode 100644 index 0000000..915a799 --- /dev/null +++ b/vendor/crates/native-browser-frame/src/frame.rs @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +use crate::constants::{EXTENSION_TO_HOST_MAX, HOST_TO_EXTENSION_MAX}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + ExtensionToHost, + HostToExtension, +} + +impl Direction { + pub fn socket_cap(self) -> usize { + match self { + Direction::ExtensionToHost => EXTENSION_TO_HOST_MAX, + Direction::HostToExtension => HOST_TO_EXTENSION_MAX, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Step { + Message(Vec), + EmptyRead, + CleanEof, + NeedMore, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Chunk<'a> { + Data(&'a [u8]), + Eof, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FrameError { + EmptyPayload, + OverCap, + TruncatedPrefix, + TruncatedBody, +} + +impl std::fmt::Display for FrameError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FrameError::EmptyPayload => write!(f, "empty_payload"), + FrameError::OverCap => write!(f, "over_cap"), + FrameError::TruncatedPrefix => write!(f, "truncated_prefix"), + FrameError::TruncatedBody => write!(f, "truncated_body"), + } + } +} + +impl std::error::Error for FrameError {} + +pub struct Assembler { + direction: Direction, + buf: Vec, + expected_len: Option, + failed: Option, +} + +impl Assembler { + pub fn new(direction: Direction) -> Self { + Self { + direction, + buf: Vec::new(), + expected_len: None, + failed: None, + } + } + + pub fn retained(&self) -> usize { + self.buf.len() + } + + pub fn reset(&mut self) { + self.buf.clear(); + self.expected_len = None; + self.failed = None; + } + + pub fn feed(&mut self, chunk: Chunk) -> Result, FrameError> { + if let Some(err) = self.failed { + return Err(err); + } + + match chunk { + Chunk::Eof => { + if self.buf.is_empty() && self.expected_len.is_none() { + Ok(vec![Step::CleanEof]) + } else if self.expected_len.is_none() { + Err(FrameError::TruncatedPrefix) + } else { + Err(FrameError::TruncatedBody) + } + } + Chunk::Data(data) => { + if data.is_empty() { + return Ok(vec![Step::EmptyRead]); + } + + let mut steps = Vec::new(); + let mut offset = 0; + + while offset < data.len() { + if let Some(err) = self.failed { + return Err(err); + } + + // Reading 4-byte prefix + if self.expected_len.is_none() { + let needed = 4 - self.buf.len(); + let available = data.len() - offset; + let take = available.min(needed); + self.buf.extend_from_slice(&data[offset..offset + take]); + offset += take; + + if self.buf.len() == 4 { + let prefix_bytes: [u8; 4] = self.buf[0..4].try_into().unwrap(); + let len = u32::from_ne_bytes(prefix_bytes) as usize; + if len == 0 { + self.failed = Some(FrameError::EmptyPayload); + return Err(FrameError::EmptyPayload); + } + if len > self.direction.socket_cap() { + self.failed = Some(FrameError::OverCap); + return Err(FrameError::OverCap); + } + self.expected_len = Some(len); + self.buf.clear(); + } + } + + // Reading payload body + if let Some(len) = self.expected_len { + let needed = len - self.buf.len(); + let available = data.len() - offset; + let take = available.min(needed); + self.buf.extend_from_slice(&data[offset..offset + take]); + offset += take; + + if self.buf.len() == len { + let msg = std::mem::take(&mut self.buf); + self.expected_len = None; + steps.push(Step::Message(msg)); + } + } + } + + if steps.is_empty() { + steps.push(Step::NeedMore); + } + + Ok(steps) + } + } + } +} + +pub struct OutFrame { + direction: Direction, + buffer: Vec, + cursor: usize, +} + +impl OutFrame { + pub fn from_payload(direction: Direction, payload: &[u8]) -> Result { + if payload.is_empty() { + return Err(FrameError::EmptyPayload); + } + if payload.len() > direction.socket_cap() { + return Err(FrameError::OverCap); + } + + let len_prefix = (payload.len() as u32).to_ne_bytes(); + let mut buffer = Vec::with_capacity(4 + payload.len()); + buffer.extend_from_slice(&len_prefix); + buffer.extend_from_slice(payload); + + Ok(Self { + direction, + buffer, + cursor: 0, + }) + } + + pub fn direction(&self) -> Direction { + self.direction + } + + pub fn pending(&self) -> &[u8] { + &self.buffer[self.cursor..] + } + + pub fn write_with(&mut self, mut write_fn: F) -> Result + where + F: FnMut(&[u8]) -> Result, + { + let pending = self.pending(); + if pending.is_empty() { + return Ok(0); + } + let written = write_fn(pending)?.min(pending.len()); + self.cursor += written; + Ok(written) + } +} diff --git a/vendor/crates/native-browser-frame/src/lib.rs b/vendor/crates/native-browser-frame/src/lib.rs new file mode 100644 index 0000000..8d5ac0e --- /dev/null +++ b/vendor/crates/native-browser-frame/src/lib.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +pub mod constants; +pub mod frame; +pub mod codec; +pub mod predicate; +pub mod register; + +pub use constants::*; +pub use frame::{Assembler, Chunk, Direction, FrameError, OutFrame, Step}; +pub use codec::{build_recipe, build_reply, canonical_stringify, decode, encode, DecodeError, DecodeOutcome}; +pub use predicate::*; +pub use register::{render_registration, RegisterError, RegistrationRender}; + diff --git a/vendor/crates/native-browser-frame/src/predicate.rs b/vendor/crates/native-browser-frame/src/predicate.rs new file mode 100644 index 0000000..cab589c --- /dev/null +++ b/vendor/crates/native-browser-frame/src/predicate.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +pub fn handshake_expired(started_ms: u64, now_ms: u64, budget_ms: u64) -> bool { + now_ms.saturating_sub(started_ms) >= budget_ms +} + +pub fn state_renewal_due(last_ms: u64, now_ms: u64, interval_ms: u64) -> bool { + now_ms.saturating_sub(last_ms) >= interval_ms +} + +pub fn freshness_value_allowed(freshness_ms: u64) -> bool { + freshness_ms <= crate::constants::FRESHNESS_MS_MAX +} + +pub fn freshness_authorizes_skim(issued_ms: u64, freshness_ms: u64, now_ms: u64) -> bool { + freshness_ms > 0 + && freshness_value_allowed(freshness_ms) + && now_ms >= issued_ms + && now_ms.saturating_sub(issued_ms) < freshness_ms +} + +pub fn freshness_authorizes_deletion(_issued_ms: u64, _freshness_ms: u64, _now_ms: u64) -> bool { + false +} + +pub fn partial_frame_expired(first_byte_ms: u64, now_ms: u64, lifetime_ms: u64) -> bool { + now_ms.saturating_sub(first_byte_ms) >= lifetime_ms +} + +pub fn future_beyond_tolerance(observed_ms: u64, now_ms: u64, tolerance_ms: u64) -> bool { + observed_ms.saturating_sub(now_ms) > tolerance_ms +} + +pub fn queued_past_outbox_age(queued_at_ms: u64, now_ms: u64, max_age_ms: u64) -> bool { + now_ms.saturating_sub(queued_at_ms) >= max_age_ms +} + +pub fn accepted_past_min_retention(accepted_at_ms: u64, now_ms: u64, retention_ms: u64) -> bool { + now_ms.saturating_sub(accepted_at_ms) >= retention_ms +} + +pub fn connection_token_matches(live: u64, presented: u64) -> bool { + live == presented +} + +pub fn may_renew_on_connection( + live: u64, + presented: u64, + last_ms: u64, + now_ms: u64, + interval_ms: u64, +) -> bool { + live == presented && now_ms >= last_ms && now_ms.saturating_sub(last_ms) < interval_ms +} + +pub fn capture_is_permitted(state: &serde_json::Value) -> bool { + if !matches!( + state.get("type").and_then(serde_json::Value::as_str), + Some("state" | "hello_ack") + ) || state.get("capture").and_then(serde_json::Value::as_str) != Some("permitted") + || state + .get("custody") + .and_then(|custody| custody.get("full")) + .and_then(serde_json::Value::as_bool) == Some(true) + || state + .get("freshness_ms") + .and_then(crate::codec::nonnegative_integer) + .is_none_or(|lease| lease == 0) + { + return false; + } + crate::codec::encode(state).is_ok_and(|bytes| { + matches!( + crate::codec::decode(&bytes, crate::frame::Direction::HostToExtension), + crate::codec::DecodeOutcome::Accept(_) + ) + }) +} diff --git a/vendor/crates/native-browser-frame/src/register.rs b/vendor/crates/native-browser-frame/src/register.rs new file mode 100644 index 0000000..3a82b21 --- /dev/null +++ b/vendor/crates/native-browser-frame/src/register.rs @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2026 sol pbc + +use crate::constants::*; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RegistrationRender { + pub channel: String, + pub browser: String, + pub os: String, + pub host: String, + pub filename: String, + pub path: String, + pub json: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RegisterError { + UnknownChannel, + UnknownBrowser, + UnknownOs, +} + +impl std::fmt::Display for RegisterError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RegisterError::UnknownChannel => write!(f, "unknown_channel"), + RegisterError::UnknownBrowser => write!(f, "unknown_browser"), + RegisterError::UnknownOs => write!(f, "unknown_os"), + } + } +} + +impl std::error::Error for RegisterError {} + +pub fn render_registration( + channel: &str, + browser: &str, + os: &str, + exec_path: Option<&str>, + config_root: Option<&str>, +) -> Result { + let (host, chrome_id, edge_id, firefox_id) = match channel { + "production" => (PROD_HOST, PROD_CHROME_ID, PROD_EDGE_ID, PROD_FIREFOX_ID), + "dev" => (DEV_HOST, DEV_CHROME_ID, DEV_EDGE_ID, DEV_FIREFOX_ID), + _ => return Err(RegisterError::UnknownChannel), + }; + + let id = match browser { + "chrome" => chrome_id, + "edge" => edge_id, + "firefox" => firefox_id, + _ => return Err(RegisterError::UnknownBrowser), + }; + + let suffix_template = match (browser, os) { + ("chrome", "linux") => ".config/google-chrome/NativeMessagingHosts/.json", + ("edge", "linux") => ".config/microsoft-edge/NativeMessagingHosts/.json", + ("firefox", "linux") => ".mozilla/native-messaging-hosts/.json", + ("chrome", "macos") => "Library/Application Support/Google/Chrome/NativeMessagingHosts/.json", + ("edge", "macos") => "Library/Application Support/Microsoft Edge/NativeMessagingHosts/.json", + ("firefox", "macos") => "Library/Application Support/Mozilla/NativeMessagingHosts/.json", + ("chrome", "windows") => "Software\\Google\\Chrome\\NativeMessagingHosts\\", + ("edge", "windows") => "Software\\Microsoft\\Edge\\NativeMessagingHosts\\", + ("firefox", "windows") => "Software\\Mozilla\\NativeMessagingHosts\\", + _ => return Err(RegisterError::UnknownOs), + }; + + let path_val = exec_path.unwrap_or(REGISTRATION_PATH_PLACEHOLDER); + let mut manifest = serde_json::Map::new(); + manifest.insert("name".to_string(), serde_json::Value::String(host.to_string())); + manifest.insert("description".to_string(), serde_json::Value::String(REGISTRATION_DESCRIPTION.to_string())); + manifest.insert("path".to_string(), serde_json::Value::String(path_val.to_string())); + manifest.insert("type".to_string(), serde_json::Value::String(REGISTRATION_TYPE.to_string())); + + if browser == "firefox" { + manifest.insert("allowed_extensions".to_string(), serde_json::json!([id])); + } else { + manifest.insert("allowed_origins".to_string(), serde_json::json!([format!("chrome-extension://{}/", id)])); + } + + let suffix = suffix_template.replace("", host); + let full_path = if let Some(root) = config_root { + let trimmed = root.trim_end_matches(['/', '\\']); + let sep = if os == "windows" { "\\" } else { "/" }; + format!("{}{}{}", trimmed, sep, suffix) + } else { + suffix + }; + + let filename = if os == "windows" { + host.to_string() + } else { + format!("{}.json", host) + }; + + let json_val = serde_json::Value::Object(manifest); + let mut json_str = serde_json::to_string_pretty(&json_val).unwrap(); + json_str.push('\n'); + + Ok(RegistrationRender { + channel: channel.to_string(), + browser: browser.to_string(), + os: os.to_string(), + host: host.to_string(), + filename, + path: full_path, + json: json_str, + }) +} -- 2.51.2