diff --git a/.cargo/nextest.toml b/.cargo/nextest.toml new file mode 100644 index 000000000..07216b1ff --- /dev/null +++ b/.cargo/nextest.toml @@ -0,0 +1,2 @@ +[profile.default] +retries = 3 diff --git a/.github/workflows/release-node-bindings.yml b/.github/workflows/release-node-bindings.yml index 57f92afc1..31e81cc15 100644 --- a/.github/workflows/release-node-bindings.yml +++ b/.github/workflows/release-node-bindings.yml @@ -5,7 +5,7 @@ on: jobs: build-linux: - runs-on: warp-ubuntu-latest-x64-16x + runs-on: warp-ubuntu-2204-x64-16x strategy: fail-fast: false matrix: diff --git a/.github/workflows/release-swift-bindings.yml b/.github/workflows/release-swift-bindings.yml index 5fbb052b9..b5b841651 100644 --- a/.github/workflows/release-swift-bindings.yml +++ b/.github/workflows/release-swift-bindings.yml @@ -31,6 +31,7 @@ jobs: - name: Build target env: CROSS_NO_WARNINGS: "0" + IPHONEOS_DEPLOYMENT_TARGET: 10 run: | cross build --release --target ${{ matrix.target }} --manifest-path bindings_ffi/Cargo.toml - name: Upload binary diff --git a/.github/workflows/release-xdbg.yml b/.github/workflows/release-xdbg.yml index 3e034d699..80fb6e23a 100644 --- a/.github/workflows/release-xdbg.yml +++ b/.github/workflows/release-xdbg.yml @@ -1,4 +1,4 @@ -name: Build and Push xdbgDocker +name: Build and Push xmtp-debug to ghcr on: push: branches: @@ -24,6 +24,10 @@ jobs: - name: Build xdbgDocker run: | nix build .#packages.${{ matrix.platform }}.xdbgDocker + - name: Build xdbg Binary + run: | + nix build .#packages.${{ matrix.platform }}.xdbg + nix path-info .#packages.${{ matrix.platform }}.xdbg | xargs -I {} cp {}/bin/xdbg ./xdbg-${{ matrix.platform }} - name: Log in to GitHub Container Registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - name: Tag Docker image @@ -35,3 +39,12 @@ jobs: run: | IMAGE_NAME=ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:xdbg-${{ matrix.platform }} docker push $IMAGE_NAME + - name: Upload xdbg binary to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./xdbg-${{ matrix.platform }} + asset_name: xdbg-${{ matrix.platform }} + asset_content_type: application/octet-stream diff --git a/.github/workflows/test-ffi-bindings.yml b/.github/workflows/test-ffi-bindings.yml index 43b4cc61e..d23ca46ef 100644 --- a/.github/workflows/test-ffi-bindings.yml +++ b/.github/workflows/test-ffi-bindings.yml @@ -50,4 +50,4 @@ jobs: - name: Run cargo nextest on FFI bindings run: | export CLASSPATH="${{ env.CLASSPATH }}" - cargo nextest run --manifest-path bindings_ffi/Cargo.toml --test-threads 2 + cargo nextest --config-file ".cargo/nextest.toml" run --manifest-path bindings_ffi/Cargo.toml --test-threads 2 diff --git a/.github/workflows/test-webassembly.yml b/.github/workflows/test-webassembly.yml index c77c0a7c1..24cabc60d 100644 --- a/.github/workflows/test-webassembly.yml +++ b/.github/workflows/test-webassembly.yml @@ -16,7 +16,7 @@ on: - "rust-toolchain" env: CARGO_TERM_COLOR: always - WASM_BINDGEN_TEST_TIMEOUT: 120 + WASM_BINDGEN_TEST_TIMEOUT: 180 WASM_BINDGEN_TEST_ONLY_WEB: 1 WASM_BINDGEN_SPLIT_LINKED_MODULES: 1 jobs: @@ -35,7 +35,7 @@ jobs: - name: Start Docker containers run: dev/up - name: Build WebAssembly Packages - run: cargo build --tests --release --target wasm32-unknown-unknown -p xmtp_id -p xmtp_mls -p xmtp_api_http -p xmtp_cryptography + run: cargo build --tests --release --target wasm32-unknown-unknown -p xmtp_id -p xmtp_mls -p xmtp_api_http -p xmtp_cryptography -p xmtp_common - name: test with chrome run: | cargo test --release --target wasm32-unknown-unknown -p xmtp_mls -p xmtp_id -p xmtp_api_http -p xmtp_cryptography -- \ diff --git a/.github/workflows/test-workspace.yml b/.github/workflows/test-workspace.yml index e565fe64c..a3fc82477 100644 --- a/.github/workflows/test-workspace.yml +++ b/.github/workflows/test-workspace.yml @@ -43,6 +43,6 @@ jobs: - name: Install nextest uses: taiki-e/install-action@nextest - name: build tests - run: cargo nextest run --no-run --workspace --tests --exclude xmtpv3 --exclude bindings_node --exclude bindings_wasm + run: cargo nextest --config-file ".cargo/nextest.toml" run --no-run --workspace --tests --exclude xmtpv3 --exclude bindings_node --exclude bindings_wasm - name: cargo test - run: cargo nextest run --workspace --test-threads 2 --exclude xmtpv3 --exclude bindings_node --exclude bindings_wasm + run: cargo nextest --config-file ".cargo/nextest.toml" run --workspace --test-threads 2 --exclude xmtpv3 --exclude bindings_node --exclude bindings_wasm diff --git a/Cargo.lock b/Cargo.lock index 71c4eae62..7cf24af0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arrayref" @@ -197,7 +197,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -257,7 +257,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -268,7 +268,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -296,7 +296,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -315,7 +315,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "itoa", @@ -326,7 +326,7 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tower 0.5.1", "tower-layer", "tower-service", @@ -341,13 +341,13 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", ] @@ -427,10 +427,31 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.90", +] + [[package]] name = "bindings_node" version = "0.1.0" dependencies = [ + "futures", "hex", "napi", "napi-build", @@ -440,6 +461,7 @@ dependencies = [ "tracing", "tracing-subscriber", "xmtp_api_grpc", + "xmtp_common", "xmtp_cryptography", "xmtp_id", "xmtp_mls", @@ -464,6 +486,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-bindgen-test", "xmtp_api_http", + "xmtp_common", "xmtp_cryptography", "xmtp_id", "xmtp_mls", @@ -557,9 +580,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -596,9 +619,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -639,15 +662,24 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "jobserver", "libc", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -680,9 +712,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -730,11 +762,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -742,9 +785,9 @@ dependencies = [ [[package]] name = "clap-verbosity-flag" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dcf89bb9d98abb02e9a4a8ef1cce429e547a803460a8245c399860985d5281" +checksum = "54381ae56ad222eea3f529c692879e9c65e07945ae48d3dc4d1cb18dbec8cf44" dependencies = [ "clap", "log", @@ -752,9 +795,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -771,14 +814,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "coins-bip32" @@ -900,9 +943,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -919,18 +962,18 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ "proc-macro2", "quote", @@ -962,6 +1005,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -970,9 +1023,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -1111,12 +1164,12 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1152,7 +1205,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1176,7 +1229,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1187,7 +1240,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1243,13 +1296,13 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "diesel" version = "2.2.4" -source = "git+https://github.com/diesel-rs/diesel?branch=master#5a2940eeaa935c0cc0a7fc57ffe012f3aaa80f43" +source = "git+https://github.com/diesel-rs/diesel?branch=master#b3cca0b44faf7679f307a19bcb08bf51f090c4a1" dependencies = [ "diesel_derives", "downcast-rs", @@ -1261,7 +1314,7 @@ dependencies = [ [[package]] name = "diesel-wasm-sqlite" version = "0.0.1" -source = "git+https://github.com/xmtp/diesel-wasm-sqlite?branch=main#df437741e19c4e1bad27af8d0680479a73472332" +source = "git+https://github.com/xmtp/sqlite-web-rs?branch=main#7f0f938aa4d49ed7bfe5624d086e7f6583805419" dependencies = [ "diesel", "diesel_derives", @@ -1269,7 +1322,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "talc", - "thiserror 1.0.69", + "thiserror 2.0.6", "tokio", "tracing", "wasm-bindgen", @@ -1279,19 +1332,19 @@ dependencies = [ [[package]] name = "diesel_derives" version = "2.2.0" -source = "git+https://github.com/diesel-rs/diesel?branch=master#5a2940eeaa935c0cc0a7fc57ffe012f3aaa80f43" +source = "git+https://github.com/diesel-rs/diesel?branch=master#b3cca0b44faf7679f307a19bcb08bf51f090c4a1" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "diesel_migrations" version = "2.2.0" -source = "git+https://github.com/diesel-rs/diesel?branch=master#5a2940eeaa935c0cc0a7fc57ffe012f3aaa80f43" +source = "git+https://github.com/diesel-rs/diesel?branch=master#b3cca0b44faf7679f307a19bcb08bf51f090c4a1" dependencies = [ "diesel", "migrations_internals", @@ -1301,9 +1354,9 @@ dependencies = [ [[package]] name = "diesel_table_macro_syntax" version = "0.2.0" -source = "git+https://github.com/diesel-rs/diesel?branch=master#5a2940eeaa935c0cc0a7fc57ffe012f3aaa80f43" +source = "git+https://github.com/diesel-rs/diesel?branch=master#b3cca0b44faf7679f307a19bcb08bf51f090c4a1" dependencies = [ - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1386,7 +1439,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1404,14 +1457,14 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dsl_auto_type" version = "0.1.0" -source = "git+https://github.com/diesel-rs/diesel?branch=master#5a2940eeaa935c0cc0a7fc57ffe012f3aaa80f43" +source = "git+https://github.com/diesel-rs/diesel?branch=master#b3cca0b44faf7679f307a19bcb08bf51f090c4a1" dependencies = [ "darling", "either", "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1576,12 +1629,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1720,7 +1773,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.87", + "syn 2.0.90", "toml 0.8.19", "walkdir", ] @@ -1738,7 +1791,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1764,7 +1817,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.87", + "syn 2.0.90", "tempfile", "thiserror 1.0.69", "tiny-keccak", @@ -1914,9 +1967,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdlimit" @@ -2130,7 +2183,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2310,7 +2363,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -2319,17 +2372,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.1.0", - "indexmap 2.6.0", + "http 1.2.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -2354,9 +2407,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hashers" @@ -2516,9 +2569,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -2543,7 +2596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http 1.2.0", ] [[package]] @@ -2554,7 +2607,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "pin-project-lite", ] @@ -2597,15 +2650,15 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", - "http 1.1.0", + "h2 0.4.7", + "http 1.2.0", "http-body 1.0.1", "httparse", "httpdate", @@ -2637,13 +2690,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http 1.1.0", - "hyper 1.5.0", + "http 1.2.0", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.17", + "rustls 0.23.19", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tower-service", ] @@ -2653,7 +2706,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "pin-project-lite", "tokio", @@ -2681,7 +2734,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -2698,9 +2751,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.5.1", "pin-project-lite", "socket2", "tokio", @@ -2846,7 +2899,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2905,13 +2958,13 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -2932,12 +2985,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] @@ -3035,9 +3088,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" @@ -3050,10 +3109,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -3146,9 +3206,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.164" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libgit2-sys" @@ -3164,12 +3224,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3278,9 +3338,9 @@ dependencies = [ [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -3341,7 +3401,7 @@ dependencies = [ [[package]] name = "migrations_internals" version = "2.2.0" -source = "git+https://github.com/diesel-rs/diesel?branch=master#5a2940eeaa935c0cc0a7fc57ffe012f3aaa80f43" +source = "git+https://github.com/diesel-rs/diesel?branch=master#b3cca0b44faf7679f307a19bcb08bf51f090c4a1" dependencies = [ "serde", "toml 0.8.19", @@ -3350,7 +3410,7 @@ dependencies = [ [[package]] name = "migrations_macros" version = "2.2.0" -source = "git+https://github.com/diesel-rs/diesel?branch=master#5a2940eeaa935c0cc0a7fc57ffe012f3aaa80f43" +source = "git+https://github.com/diesel-rs/diesel?branch=master#b3cca0b44faf7679f307a19bcb08bf51f090c4a1" dependencies = [ "migrations_internals", "proc-macro2", @@ -3375,13 +3435,13 @@ dependencies = [ [[package]] name = "mini-internal" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cd9f9bbedc1b92683a9847b8db12f3203cf32af6a11db085fa007708dc9555" +checksum = "07b7f1340a7f0d2f89755aac4096f1c55352bfcb95cb8f351d6fcefa18df474f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3402,9 +3462,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniserde" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b650e926368ad21aaabe6055341d1874df696178f47d70b6d9a691f616274e" +checksum = "625208b7bc90255fe6d99338041880bf48a3049166f46fbefbd4e9b0478c0b66" dependencies = [ "itoa", "mini-internal", @@ -3431,11 +3491,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -3453,13 +3512,14 @@ dependencies = [ "openmls", "openmls_rust_crypto", "rand", - "thiserror 2.0.3", + "thiserror 2.0.6", "tokio", "tonic", "tracing", "tracing-subscriber", "vergen", "warp", + "xmtp_common", "xmtp_cryptography", "xmtp_id", "xmtp_mls", @@ -3489,7 +3549,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3502,10 +3562,10 @@ dependencies = [ "bytes", "colored", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "log", "rand", @@ -3562,23 +3622,23 @@ checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" [[package]] name = "napi-derive" -version = "2.16.12" +version = "2.16.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17435f7a00bfdab20b0c27d9c56f58f6499e418252253081bfff448099da31d1" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" dependencies = [ "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "napi-derive-backend" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "967c485e00f0bf3b1bdbe510a38a4606919cf1d34d9a37ad41f25a81aa077abe" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" dependencies = [ "convert_case", "once_cell", @@ -3586,7 +3646,7 @@ dependencies = [ "quote", "regex", "semver", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3610,11 +3670,20 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -3704,7 +3773,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3870,7 +3939,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3947,31 +4016,44 @@ dependencies = [ "primeorder", ] +[[package]] +name = "paranoid-android" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "101795d63d371b43e38d6e7254677657be82f17022f7f7893c268f33ac0caadc" +dependencies = [ + "lazy_static", + "ndk-sys", + "sharded-slab", + "smallvec", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "parity-scale-codec" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 1.0.109", ] [[package]] @@ -4135,7 +4217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap 2.7.0", ] [[package]] @@ -4178,7 +4260,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4216,7 +4298,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4316,9 +4398,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "powerfmt" @@ -4374,7 +4456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4411,9 +4493,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -4436,9 +4518,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" dependencies = [ "bytes", "prost-derive", @@ -4446,13 +4528,12 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" +checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b" dependencies = [ - "bytes", "heck", - "itertools 0.10.5", + "itertools 0.13.0", "log", "multimap", "once_cell", @@ -4461,28 +4542,28 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.87", + "syn 2.0.90", "tempfile", ] [[package]] name = "prost-derive" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "prost-types" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" +checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc" dependencies = [ "prost", ] @@ -4709,11 +4790,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.6", - "http 1.1.0", + "h2 0.4.7", + "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-tls 0.6.0", "hyper-util", @@ -4729,7 +4810,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "system-configuration 0.6.1", "tokio", "tokio-native-tls", @@ -4831,6 +4912,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -4848,15 +4935,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4873,9 +4960,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.17" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", "once_cell", @@ -4888,15 +4975,14 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.0.1", ] [[package]] @@ -4976,9 +5062,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa7ffc1c0ef49b0452c6e2986abf2b07743320641ffd5fc63d552458e3b779b" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "cfg-if", "derive_more", @@ -4988,21 +5074,21 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46385cc24172cf615450267463f937c10072516359b3ff1cb24228a4a08bf951" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -5045,7 +5131,7 @@ checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5105,7 +5191,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -5170,7 +5269,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5332,9 +5431,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -5372,7 +5471,7 @@ checksum = "658f2ca5276b92c3dfd65fa88316b4e032ace68f88d7570b43967784c0bac5ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5463,7 +5562,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5505,9 +5604,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -5522,9 +5621,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -5537,7 +5636,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5547,7 +5646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys 0.5.0", ] @@ -5558,7 +5657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys 0.6.0", ] @@ -5647,11 +5746,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.6", ] [[package]] @@ -5662,28 +5761,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", -] - -[[package]] -name = "thread-id" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99043e46c5a15af379c06add30d9c93a6c0e8849de00d244c4a2c417da128d80" -dependencies = [ - "libc", - "windows-sys 0.59.0", + "syn 2.0.90", ] [[package]] @@ -5698,9 +5787,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -5721,9 +5810,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -5802,14 +5891,14 @@ checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -5832,7 +5921,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5857,20 +5946,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.17", - "rustls-pki-types", + "rustls 0.23.19", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -5909,9 +5997,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -5956,7 +6044,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -5974,11 +6062,11 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2 0.4.6", - "http 1.1.0", + "h2 0.4.7", + "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-timeout", "hyper-util", "percent-encoding", @@ -5988,13 +6076,13 @@ dependencies = [ "rustls-pemfile 2.2.0", "socket2", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tokio-stream", "tower 0.4.13", "tower-layer", "tower-service", "tracing", - "webpki-roots 0.26.6", + "webpki-roots 0.26.7", ] [[package]] @@ -6045,9 +6133,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -6069,20 +6157,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -6090,9 +6178,9 @@ dependencies = [ [[package]] name = "tracing-error" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", "tracing-subscriber", @@ -6131,11 +6219,26 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tracing-oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528bdd1f0e27b5dd9a4ededf154e824b0532731e4af73bb531de46276e0aab1e" +dependencies = [ + "bindgen", + "cc", + "cfg-if", + "once_cell", + "parking_lot 0.12.3", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -6145,9 +6248,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "chrono", "matchers", @@ -6199,7 +6302,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6238,7 +6341,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.1.0", + "http 1.2.0", "httparse", "log", "rand", @@ -6280,9 +6383,9 @@ checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" @@ -6364,7 +6467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6395,7 +6498,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.87", + "syn 2.0.90", "toml 0.5.11", "uniffi_meta", ] @@ -6462,9 +6565,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -6627,9 +6730,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -6638,36 +6741,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6675,30 +6778,29 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-bindgen-test" -version = "0.3.45" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +checksum = "c61d44563646eb934577f2772656c7ad5e9c90fac78aa8013d776fcdaf24625d" dependencies = [ - "console_error_panic_hook", "js-sys", "minicov", "scoped-tls", @@ -6709,13 +6811,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.45" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6731,26 +6833,11 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -6774,9 +6861,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -6812,7 +6899,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -7105,7 +7192,7 @@ dependencies = [ "redb", "speedy", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.6", "tokio", "tracing", "tracing-appender", @@ -7165,7 +7252,7 @@ dependencies = [ "reqwest 0.12.9", "serde", "serde_json", - "thiserror 2.0.3", + "thiserror 2.0.6", "tokio", "tracing", "wasm-bindgen-test", @@ -7187,7 +7274,7 @@ dependencies = [ "prost", "serde", "serde_json", - "thiserror 2.0.3", + "thiserror 2.0.6", "timeago", "tokio", "tracing", @@ -7195,12 +7282,49 @@ dependencies = [ "valuable", "valuable-serde", "xmtp_api_grpc", + "xmtp_common", + "xmtp_content_types", "xmtp_cryptography", "xmtp_id", "xmtp_mls", "xmtp_proto", ] +[[package]] +name = "xmtp_common" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "futures", + "getrandom", + "gloo-timers 0.3.0", + "js-sys", + "parking_lot 0.12.3", + "rand", + "thiserror 2.0.6", + "tokio", + "tracing", + "tracing-subscriber", + "tracing-wasm", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", + "web-time", + "xmtp_cryptography", +] + +[[package]] +name = "xmtp_content_types" +version = "0.1.0" +dependencies = [ + "prost", + "rand", + "thiserror 2.0.6", + "tonic", + "xmtp_common", + "xmtp_proto", +] + [[package]] name = "xmtp_cryptography" version = "0.1.0" @@ -7221,7 +7345,7 @@ dependencies = [ "serde", "sha2 0.10.8", "sha3", - "thiserror 2.0.3", + "thiserror 2.0.6", "tls_codec", "tracing", "wasm-bindgen-futures", @@ -7250,12 +7374,13 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "thiserror 2.0.3", + "thiserror 2.0.6", "tokio", "tracing", "url", "wasm-bindgen-test", - "wasm-timer", + "web-time", + "xmtp_common", "xmtp_cryptography", "xmtp_proto", "xmtp_v2", @@ -7272,6 +7397,7 @@ dependencies = [ "bincode", "chrono", "console_error_panic_hook", + "const_format", "criterion", "ctor", "diesel", @@ -7279,10 +7405,13 @@ dependencies = [ "diesel_migrations", "dyn-clone", "ethers", + "fdlimit", "futures", "getrandom", "gloo-timers 0.3.0", "hex", + "hkdf", + "hmac 0.12.1", "indicatif", "libsqlite3-sys", "mockall", @@ -7300,8 +7429,9 @@ dependencies = [ "reqwest 0.12.9", "serde", "serde_json", + "sha2 0.10.8", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.6", "tls_codec", "tokio", "tokio-stream", @@ -7313,10 +7443,11 @@ dependencies = [ "trait-variant", "wasm-bindgen-futures", "wasm-bindgen-test", - "wasm-timer", "web-sys", "xmtp_api_grpc", "xmtp_api_http", + "xmtp_common", + "xmtp_content_types", "xmtp_cryptography", "xmtp_id", "xmtp_proto", @@ -7337,7 +7468,9 @@ dependencies = [ "prost", "serde", "tonic", + "tracing", "wasm-bindgen-test", + "xmtp_common", ] [[package]] @@ -7374,15 +7507,18 @@ version = "0.1.0" dependencies = [ "ethers", "futures", - "log", + "paranoid-android", "parking_lot 0.12.3", "rand", - "thiserror 2.0.3", - "thread-id", + "thiserror 2.0.6", "tokio", + "tracing", + "tracing-oslog", + "tracing-subscriber", "uniffi", "uuid 1.11.0", "xmtp_api_grpc", + "xmtp_common", "xmtp_cryptography", "xmtp_id", "xmtp_mls", @@ -7430,9 +7566,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -7442,13 +7578,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "synstructure", ] @@ -7470,27 +7606,27 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "synstructure", ] @@ -7511,7 +7647,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7533,7 +7669,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 28432cadc..0dde776cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,9 @@ members = [ "bindings_node", "bindings_ffi", "xtask", - "xmtp_debug" + "xmtp_debug", + "xmtp_content_types", + "common" ] # Make the feature resolver explicit. @@ -22,15 +24,15 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.0" license = "MIT" +version = "0.1.0" [workspace.dependencies] anyhow = "1.0" async-stream = "0.3" async-trait = "0.1.77" +base64 = "0.22" chrono = "0.4.38" -wasm-timer = "0.2" ctor = "0.2" ed25519 = "2.2.3" ed25519-dalek = { version = "2.1.1", features = ["zeroize"] } @@ -39,6 +41,7 @@ futures = "0.3.30" futures-core = "0.3.30" getrandom = { version = "0.2", default-features = false } hex = "0.4.3" +hkdf = "0.12.3" openmls = { git = "https://github.com/xmtp/openmls", rev = "043b347cb18d528647df36f500725ab57c41c7db", default-features = false } openmls_basic_credential = { git = "https://github.com/xmtp/openmls", rev = "043b347cb18d528647df36f500725ab57c41c7db" } openmls_rust_crypto = { git = "https://github.com/xmtp/openmls", rev = "043b347cb18d528647df36f500725ab57c41c7db" } @@ -48,8 +51,6 @@ pbjson-types = "0.7.0" prost = "^0.13" prost-types = "^0.13" rand = "0.8.5" -uuid = "1.10" -base64 = "0.22" regex = "1.10.4" rustc-hex = "2.1.0" serde = { version = "1.0", default-features = false } @@ -59,6 +60,9 @@ sha3 = "0.10.8" thiserror = "2.0" tls_codec = "0.4.1" tokio = { version = "1.35.1", default-features = false } +uuid = "1.10" +wasm-timer = "0.2" +web-time = "1.1" # Changing this version and rustls may potentially break the android build. Use Caution. # Test with Android and Swift first. # Its probably preferable to one day use https://github.com/rustls/rustls-platform-verifier @@ -67,35 +71,41 @@ tokio = { version = "1.35.1", default-features = false } # - https://github.com/seanmonstar/reqwest/issues/2159 # - https://github.com/hyperium/tonic/pull/1974 # - https://github.com/rustls/rustls-platform-verifier/issues/58 -tonic = { version = "0.12", default-features = false } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false } +bincode = "1.3" +console_error_panic_hook = "0.1" +const_format = "0.2" diesel = { version = "2.2", default-features = false } diesel-wasm-sqlite = "0.0.1" diesel_migrations = { version = "2.2", default-features = false } -parking_lot = "0.12.3" -wasm-bindgen-futures = "0.4" -wasm-bindgen = "=0.2.95" -wasm-bindgen-test = "0.3.45" +dyn-clone = "1" +fdlimit = "0.3" gloo-timers = "0.3" -web-sys = "0.3" js-sys = "0.3" -openssl-sys = { version = "0.9", features = ["vendored"] } +libsqlite3-sys = { version = "0.29", features = [ + "bundled-sqlcipher-vendored-openssl", +] } openssl = { version = "0.10", features = ["vendored"] } -libsqlite3-sys = { version = "0.29", features = ["bundled-sqlcipher-vendored-openssl" ] } -dyn-clone = "1" +openssl-sys = { version = "0.9", features = ["vendored"] } +parking_lot = "0.12.3" +tonic = { version = "0.12", default-features = false } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false } trait-variant = "0.1.2" url = "2.5.0" +wasm-bindgen = "=0.2.99" +wasm-bindgen-futures = "0.4" +wasm-bindgen-test = "0.3.49" +web-sys = "0.3" zeroize = "1.8" -bincode = "1.3" -console_error_panic_hook = "0.1" # Internal Crate Dependencies -xmtp_cryptography = { path = "xmtp_cryptography" } xmtp_api_grpc = { path = "xmtp_api_grpc" } +xmtp_cryptography = { path = "xmtp_cryptography" } xmtp_id = { path = "xmtp_id" } xmtp_mls = { path = "xmtp_mls" } xmtp_proto = { path = "xmtp_proto" } +xmtp_content_types = { path = "xmtp_content_types" } +xmtp_common = { path = "common" } [profile.dev] # Disabling debug info speeds up builds a bunch, @@ -122,10 +132,10 @@ strip = "symbols" # NOTE: The release profile reduces bundle size from 230M to 41M - may have performance impliciations # https://stackoverflow.com/a/54842093 [profile.release.package.xmtpv3] +codegen-units = 1 # Reduce number of codegen units to increase optimizations inherits = "release-with-lto" -codegen-units = 1 # Reduce number of codegen units to increase optimizations -opt-level = 'z' # Optimize for size + loop vectorization -strip = true # Strip symbols from binary* +opt-level = 'z' # Optimize for size + loop vectorization +strip = true # Strip symbols from binary* [profile.release.package.bindings_wasm] inherits = "release-with-lto" @@ -136,7 +146,7 @@ opt-level = "s" # are made public for third-party dependencies: https://github.com/diesel-rs/diesel/pull/4236 # (cfg-specific patche support does not exist) [patch.crates-io] -diesel-wasm-sqlite = { git = "https://github.com/xmtp/diesel-wasm-sqlite", branch = "main" } diesel = { git = "https://github.com/diesel-rs/diesel", branch = "master" } +diesel-wasm-sqlite = { git = "https://github.com/xmtp/sqlite-web-rs", branch = "main" } diesel_derives = { git = "https://github.com/diesel-rs/diesel", branch = "master" } diesel_migrations = { git = "https://github.com/diesel-rs/diesel", branch = "master" } diff --git a/bindings_ffi/Cargo.toml b/bindings_ffi/Cargo.toml index 00ba44467..7e44b3498 100644 --- a/bindings_ffi/Cargo.toml +++ b/bindings_ffi/Cargo.toml @@ -9,10 +9,10 @@ crate-type = ["lib", "cdylib", "staticlib"] [dependencies] futures.workspace = true -log = { version = "0.4", features = ["std"] } +tracing.workspace = true +tracing-subscriber = { workspace = true, features = ["registry", "env-filter", "fmt", "json"] } parking_lot.workspace = true thiserror.workspace = true -thread-id = "5.0.0" tokio = { workspace = true, features = ["macros"] } uniffi = { version = "0.28.0", default-features = false, features = ["tokio"] } xmtp_api_grpc = { path = "../xmtp_api_grpc" } @@ -22,6 +22,13 @@ xmtp_mls = { path = "../xmtp_mls" } xmtp_proto = { path = "../xmtp_proto", features = ["proto_full"] } xmtp_user_preferences = { path = "../xmtp_user_preferences" } xmtp_v2 = { path = "../xmtp_v2" } +xmtp_common.workspace = true + +[target.'cfg(target_os = "android")'.dependencies] +paranoid-android = "0.2" + +[target.'cfg(target_os = "ios")'.dependencies] +tracing-oslog = "0.2" [build-dependencies] uniffi = { version = "0.28.0", features = ["build"] } diff --git a/bindings_ffi/src/lib.rs b/bindings_ffi/src/lib.rs index 3d16186be..7cd5a5b65 100755 --- a/bindings_ffi/src/lib.rs +++ b/bindings_ffi/src/lib.rs @@ -7,11 +7,17 @@ pub mod v2; pub use crate::inbox_owner::SigningError; use inbox_owner::FfiInboxOwner; -use logger::FfiLogger; pub use mls::*; use std::error::Error; -uniffi::include_scaffolding!("xmtpv3"); +extern crate tracing as log; + +pub use ffi::*; +#[allow(clippy::all)] +mod ffi { + use super::*; + uniffi::include_scaffolding!("xmtpv3"); +} #[derive(uniffi::Error, thiserror::Error, Debug)] #[uniffi(flat_error)] @@ -57,6 +63,8 @@ pub enum GenericError { pub enum FfiSubscribeError { #[error("Subscribe Error {0}")] Subscribe(#[from] xmtp_mls::subscriptions::SubscribeError), + #[error("Storage error: {0}")] + Storage(#[from] xmtp_mls::storage::StorageError), } impl From for GenericError { diff --git a/bindings_ffi/src/logger.rs b/bindings_ffi/src/logger.rs index 5e9d0c623..c4b00e349 100644 --- a/bindings_ffi/src/logger.rs +++ b/bindings_ffi/src/logger.rs @@ -1,43 +1,96 @@ -use log::{LevelFilter, Metadata, Record}; +use log::Subscriber; use std::sync::Once; +use tracing_subscriber::{ + layer::SubscriberExt, registry::LookupSpan, util::SubscriberInitExt, Layer, +}; -pub trait FfiLogger: Send + Sync { - fn log(&self, level: u32, level_label: String, message: String); +#[cfg(target_os = "android")] +pub use android::*; +#[cfg(target_os = "android")] +mod android { + use super::*; + pub fn native_layer() -> impl Layer + where + S: Subscriber + for<'a> LookupSpan<'a>, + { + paranoid_android::layer(env!("CARGO_PKG_NAME")).with_thread_names(true) + } } -struct RustLogger { - logger: parking_lot::Mutex>, +#[cfg(target_os = "ios")] +pub use ios::*; +#[cfg(target_os = "ios")] +mod ios { + use super::*; + // use tracing_subscriber::Layer; + pub fn native_layer() -> impl Layer + where + S: Subscriber + for<'a> LookupSpan<'a>, + { + use tracing_oslog::OsLogger; + let subsystem = format!("org.xmtp.{}", env!("CARGO_PKG_NAME")); + OsLogger::new(subsystem, "default") + } } -impl log::Log for RustLogger { - fn enabled(&self, _metadata: &Metadata) -> bool { - true - } +#[cfg(not(any(target_os = "ios", target_os = "android")))] +pub use other::*; +#[cfg(not(any(target_os = "ios", target_os = "android")))] +mod other { + use super::*; - fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - // TODO handle errors - self.logger.lock().log( - record.level() as u32, - record.level().to_string(), - format!("[libxmtp][t:{}] {}", thread_id::get(), record.args()), - ); - } - } + pub fn native_layer() -> impl Layer + where + S: Subscriber + for<'a> LookupSpan<'a>, + { + use tracing_subscriber::{ + fmt::{self, format}, + EnvFilter, Layer, + }; + let structured = std::env::var("STRUCTURED"); + let is_structured = matches!(structured, Ok(s) if s == "true" || s == "1"); - fn flush(&self) {} + let filter = || { + EnvFilter::builder() + .with_default_directive(tracing::metadata::LevelFilter::INFO.into()) + .from_env_lossy() + }; + + vec![ + // structured JSON logger + is_structured + .then(|| { + tracing_subscriber::fmt::layer() + .json() + .flatten_event(true) + .with_level(true) + .with_filter(filter()) + }) + .boxed(), + // default logger + (!is_structured) + .then(|| { + fmt::layer() + .compact() + .fmt_fields({ + format::debug_fn(move |writer, field, value| { + if field.name() == "message" { + write!(writer, "{:?}", value)?; + } + Ok(()) + }) + }) + .with_filter(filter()) + }) + .boxed(), + ] + } } static LOGGER_INIT: Once = Once::new(); -pub fn init_logger(logger: Box) { - // TODO handle errors +pub fn init_logger() { LOGGER_INIT.call_once(|| { - let logger = RustLogger { - logger: parking_lot::Mutex::new(logger), - }; - log::set_boxed_logger(Box::new(logger)) - .map(|()| log::set_max_level(LevelFilter::Info)) - .expect("Failed to initialize logger"); - log::info!("Logger initialized"); + let native_layer = native_layer(); + tracing_subscriber::registry().with(native_layer).init() }); } diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index 7ab18fe12..a9ae8232b 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -1,10 +1,10 @@ pub use crate::inbox_owner::SigningError; use crate::logger::init_logger; -use crate::logger::FfiLogger; use crate::{FfiSubscribeError, GenericError}; use std::{collections::HashMap, convert::TryInto, sync::Arc}; use tokio::sync::Mutex; use xmtp_api_grpc::grpc_api_helper::Client as TonicApiClient; +use xmtp_common::retry::Retry; use xmtp_id::associations::verify_signed_with_public_context; use xmtp_id::scw_verifier::RemoteSignatureVerifier; use xmtp_id::{ @@ -17,6 +17,7 @@ use xmtp_id::{ InboxId, }; use xmtp_mls::groups::scoped_client::LocalScopedGroupClient; +use xmtp_mls::groups::HmacKey; use xmtp_mls::storage::group::ConversationType; use xmtp_mls::storage::group_message::MsgQueryArgs; use xmtp_mls::storage::group_message::SortDirection; @@ -37,7 +38,6 @@ use xmtp_mls::{ GroupMetadataOptions, MlsGroup, PreconfiguredPolicies, UpdateAdminListType, }, identity::IdentityStrategy, - retry::Retry, storage::{ consent_record::{ConsentState, ConsentType, StoredConsentRecord}, group::GroupQueryArgs, @@ -71,7 +71,6 @@ pub type RustXmtpClient = MlsClient; #[allow(clippy::too_many_arguments)] #[uniffi::export(async_runtime = "tokio")] pub async fn create_client( - logger: Box, host: String, is_secure: bool, db: Option, @@ -82,7 +81,7 @@ pub async fn create_client( legacy_signed_private_key_proto: Option>, history_sync_url: Option, ) -> Result, GenericError> { - init_logger(logger); + init_logger(); log::info!( "Creating API client for host: {}, isSecure: {}", host, @@ -112,7 +111,7 @@ pub async fn create_client( None => EncryptedMessageStore::new_unencrypted(storage_option).await?, }; log::info!("Creating XMTP client"); - let identity_strategy = IdentityStrategy::CreateIfNotFound( + let identity_strategy = IdentityStrategy::new( inbox_id.clone(), account_address.clone(), nonce, @@ -142,7 +141,6 @@ pub async fn create_client( #[allow(unused)] #[uniffi::export(async_runtime = "tokio")] pub async fn get_inbox_id_for_address( - logger: Box, host: String, is_secure: bool, account_address: String, @@ -285,7 +283,7 @@ impl FfiXmtpClient { } pub fn installation_id(&self) -> Vec { - self.inner_client.installation_public_key() + self.inner_client.installation_public_key().to_vec() } pub fn release_db_connection(&self) -> Result<(), GenericError> { @@ -298,8 +296,8 @@ impl FfiXmtpClient { pub async fn find_inbox_id(&self, address: String) -> Result, GenericError> { let inner = self.inner_client.as_ref(); - - let result = inner.find_inbox_id_from_address(address).await?; + let conn = self.inner_client.store().conn()?; + let result = inner.find_inbox_id_from_address(&conn, address).await?; Ok(result) } @@ -330,7 +328,10 @@ impl FfiXmtpClient { ) -> Result, GenericError> { let state = self .inner_client - .inbox_addresses(refresh_from_network, inbox_ids) + .inbox_addresses( + refresh_from_network, + inbox_ids.iter().map(String::as_str).collect(), + ) .await?; Ok(state.into_iter().map(Into::into).collect()) } @@ -377,7 +378,7 @@ impl FfiXmtpClient { signature_bytes: Vec, ) -> Result<(), GenericError> { let inner = self.inner_client.as_ref(); - let public_key = inner.installation_public_key(); + let public_key = inner.installation_public_key().to_vec(); self.verify_signed_with_public_key(signature_text, signature_bytes, public_key) } @@ -440,26 +441,6 @@ impl FfiXmtpClient { .register_identity(signature_request.clone()) .await?; - self.maybe_start_sync_worker().await?; - - Ok(()) - } - - /// Starts the sync worker if the history sync url is present. - async fn maybe_start_sync_worker(&self) -> Result<(), GenericError> { - if self.inner_client.history_sync_url().is_none() { - return Ok(()); - } - - let provider = self - .inner_client - .mls_provider() - .map_err(GenericError::from_error)?; - self.inner_client - .start_sync_worker(&provider) - .await - .map_err(GenericError::from_error)?; - Ok(()) } @@ -472,15 +453,15 @@ impl FfiXmtpClient { Ok(()) } - /// Adds an identity - really a wallet address - to the existing client + /// Adds a wallet address to the existing client pub async fn add_wallet( &self, - existing_wallet_address: &str, new_wallet_address: &str, ) -> Result, GenericError> { let signature_request = self .inner_client - .associate_wallet(existing_wallet_address.into(), new_wallet_address.into())?; + .associate_wallet(new_wallet_address.into()) + .await?; let scw_verifier = self.inner_client.scw_verifier().clone(); let request = Arc::new(FfiSignatureRequest { inner: Arc::new(tokio::sync::Mutex::new(signature_request)), @@ -534,7 +515,7 @@ impl FfiXmtpClient { let other_installation_ids = inbox_state .installation_ids() .into_iter() - .filter(|id| id != &installation_id) + .filter(|id| id != installation_id) .collect(); let signature_request = self @@ -547,6 +528,33 @@ impl FfiXmtpClient { scw_verifier: self.inner_client.scw_verifier().clone().clone(), })) } + + pub fn get_hmac_keys(&self) -> Result, GenericError> { + let inner = self.inner_client.as_ref(); + let conversations = inner.find_groups(GroupQueryArgs::default())?; + + let mut keys = vec![]; + for conversation in conversations { + let mut k = conversation + .hmac_keys(-1..=1)? + .into_iter() + .map(Into::into) + .collect::>(); + + keys.append(&mut k); + } + + Ok(keys) + } +} + +impl From for FfiHmacKey { + fn from(value: HmacKey) -> Self { + Self { + epoch: value.epoch, + key: value.key.to_vec(), + } + } } #[derive(uniffi::Record)] @@ -557,6 +565,12 @@ pub struct FfiInboxState { pub account_addresses: Vec, } +#[derive(uniffi::Record)] +pub struct FfiHmacKey { + key: Vec, + epoch: i64, +} + #[derive(uniffi::Record)] pub struct FfiInstallation { pub id: Vec, @@ -901,32 +915,33 @@ impl FfiConversations { pub async fn sync(&self) -> Result<(), GenericError> { let inner = self.inner_client.as_ref(); - let conn = inner.store().conn()?; - inner.sync_welcomes(&conn).await?; + let provider = inner.mls_provider()?; + inner.sync_welcomes(&provider).await?; Ok(()) } pub fn get_sync_group(&self) -> Result { let inner = self.inner_client.as_ref(); - let sync_group = inner.get_sync_group()?; + let conn = inner.store().conn()?; + let sync_group = inner.get_sync_group(&conn)?; Ok(sync_group.into()) } - pub async fn sync_all_conversations(&self) -> Result { + pub async fn sync_all_conversations( + &self, + consent_state: Option, + ) -> Result { let inner = self.inner_client.as_ref(); - let groups = inner.find_groups(GroupQueryArgs::default().include_sync_groups())?; - - log::info!( - "groups for client inbox id {:?}: {:?}", - self.inner_client.inbox_id(), - groups.len() - ); - - let num_groups_synced: usize = inner.sync_all_groups(groups).await?; - // Uniffi does not work with usize, so we need to convert to u32 + let provider = inner.mls_provider()?; + let consent: Option = consent_state.map(|state| state.into()); + let num_groups_synced: usize = inner + .sync_all_welcomes_and_groups(&provider, consent) + .await?; + // Convert usize to u32 for compatibility with Uniffi let num_groups_synced: u32 = num_groups_synced .try_into() .map_err(|_| GenericError::FailedToConvertToU32)?; + Ok(num_groups_synced) } @@ -1021,41 +1036,33 @@ impl FfiConversations { &self, message_callback: Arc, ) -> FfiStreamCloser { - let handle = RustXmtpClient::stream_all_messages_with_callback( - self.inner_client.clone(), - Some(ConversationType::Group), - move |msg| match msg { - Ok(m) => message_callback.on_message(m.into()), - Err(e) => message_callback.on_error(e.into()), - }, - ); - - FfiStreamCloser::new(handle) + self.stream_messages(message_callback, Some(FfiConversationType::Group)) + .await } pub async fn stream_all_dm_messages( &self, message_callback: Arc, ) -> FfiStreamCloser { - let handle = RustXmtpClient::stream_all_messages_with_callback( - self.inner_client.clone(), - Some(ConversationType::Dm), - move |msg| match msg { - Ok(m) => message_callback.on_message(m.into()), - Err(e) => message_callback.on_error(e.into()), - }, - ); - - FfiStreamCloser::new(handle) + self.stream_messages(message_callback, Some(FfiConversationType::Dm)) + .await } pub async fn stream_all_messages( &self, message_callback: Arc, + ) -> FfiStreamCloser { + self.stream_messages(message_callback, None).await + } + + async fn stream_messages( + &self, + message_callback: Arc, + conversation_type: Option, ) -> FfiStreamCloser { let handle = RustXmtpClient::stream_all_messages_with_callback( self.inner_client.clone(), - None, + conversation_type.map(Into::into), move |msg| match msg { Ok(m) => message_callback.on_message(m.into()), Err(e) => message_callback.on_error(e.into()), @@ -1078,6 +1085,16 @@ impl FfiConversations { } } +impl From for ConversationType { + fn from(value: FfiConversationType) -> Self { + match value { + FfiConversationType::Dm => ConversationType::Dm, + FfiConversationType::Group => ConversationType::Group, + FfiConversationType::Sync => ConversationType::Sync, + } + } +} + #[derive(uniffi::Object)] pub struct FfiConversation { inner: MlsGroup, @@ -1285,9 +1302,10 @@ impl FfiConversation { &self, envelope_bytes: Vec, ) -> Result { + let provider = self.inner.mls_provider()?; let message = self .inner - .process_streamed_group_message(envelope_bytes) + .process_streamed_group_message(&provider, envelope_bytes) .await?; let ffi_message = message.into(); @@ -1802,16 +1820,14 @@ impl FfiGroupPermissions { mod tests { use super::{create_client, FfiConsentCallback, FfiMessage, FfiMessageCallback, FfiXmtpClient}; use crate::{ - get_inbox_id_for_address, inbox_owner::SigningError, logger::FfiLogger, FfiConsent, - FfiConsentEntityType, FfiConsentState, FfiConversation, FfiConversationCallback, - FfiConversationMessageKind, FfiCreateGroupOptions, FfiGroupPermissionsOptions, - FfiInboxOwner, FfiListConversationsOptions, FfiListMessagesOptions, FfiMetadataField, - FfiPermissionPolicy, FfiPermissionPolicySet, FfiPermissionUpdateType, FfiSubscribeError, + get_inbox_id_for_address, inbox_owner::SigningError, FfiConsent, FfiConsentEntityType, + FfiConsentState, FfiConversation, FfiConversationCallback, FfiConversationMessageKind, + FfiCreateGroupOptions, FfiGroupPermissionsOptions, FfiInboxOwner, + FfiListConversationsOptions, FfiListMessagesOptions, FfiMetadataField, FfiPermissionPolicy, + FfiPermissionPolicySet, FfiPermissionUpdateType, FfiSubscribeError, }; use ethers::utils::hex; - use rand::distributions::{Alphanumeric, DistString}; use std::{ - env, sync::{ atomic::{AtomicU32, Ordering}, Arc, Mutex, @@ -1819,6 +1835,8 @@ mod tests { time::{Duration, Instant}, }; use tokio::{sync::Notify, time::error::Elapsed}; + use xmtp_common::tmp_path; + use xmtp_common::{wait_for_eq, wait_for_ok}; use xmtp_cryptography::{signature::RecoverableSignature, utils::rng}; use xmtp_id::associations::{ generate_inbox_id, @@ -1863,14 +1881,6 @@ mod tests { } } - pub struct MockLogger {} - - impl FfiLogger for MockLogger { - fn log(&self, _level: u32, level_label: String, message: String) { - println!("[{}]{}", level_label, message) - } - } - #[derive(Default)] struct RustStreamCallback { num_messages: AtomicU32, @@ -1878,6 +1888,8 @@ mod tests { conversations: Mutex>>, consent_updates: Mutex>, notify: Notify, + inbox_id: Option, + installation_id: Option, } impl RustStreamCallback { @@ -1897,12 +1909,22 @@ mod tests { .await?; Ok(()) } + + pub fn from_client(client: &FfiXmtpClient) -> Self { + RustStreamCallback { + inbox_id: Some(client.inner_client.inbox_id().to_string()), + installation_id: Some(hex::encode(client.inner_client.installation_public_key())), + ..Default::default() + } + } } impl FfiMessageCallback for RustStreamCallback { fn on_message(&self, message: FfiMessage) { let mut messages = self.messages.lock().unwrap(); log::info!( + inbox_id = self.inbox_id, + installation_id = self.installation_id, "ON MESSAGE Received\n-------- \n{}\n----------", String::from_utf8_lossy(&message.content) ); @@ -1918,7 +1940,11 @@ mod tests { impl FfiConversationCallback for RustStreamCallback { fn on_conversation(&self, group: Arc) { - log::debug!("received conversation"); + log::debug!( + inbox_id = self.inbox_id, + installation_id = self.installation_id, + "received conversation" + ); let _ = self.num_messages.fetch_add(1, Ordering::SeqCst); let mut convos = self.conversations.lock().unwrap(); convos.push(group); @@ -1932,7 +1958,11 @@ mod tests { impl FfiConsentCallback for RustStreamCallback { fn on_consent_update(&self, mut consent: Vec) { - log::debug!("received consent update"); + log::debug!( + inbox_id = self.inbox_id, + installation_id = self.installation_id, + "received consent update" + ); let mut consent_updates = self.consent_updates.lock().unwrap(); consent_updates.append(&mut consent); self.notify.notify_one(); @@ -1943,15 +1973,6 @@ mod tests { } } - pub fn rand_string() -> String { - Alphanumeric.sample_string(&mut rand::thread_rng(), 24) - } - - pub fn tmp_path() -> String { - let db_name = rand_string(); - format!("{}/{}.db3", env::temp_dir().to_str().unwrap(), db_name) - } - fn static_enc_key() -> EncryptionKey { [2u8; 32] } @@ -1992,7 +2013,6 @@ mod tests { let inbox_id = generate_inbox_id(&ffi_inbox_owner.get_address(), &nonce).unwrap(); let client = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(tmp_path()), @@ -2035,7 +2055,6 @@ mod tests { let real_inbox_id = client.inbox_id(); let from_network = get_inbox_id_for_address( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, client.account_address.clone(), @@ -2056,7 +2075,6 @@ mod tests { let inbox_id = generate_inbox_id(&account_address, &nonce).unwrap(); let client = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(tmp_path()), @@ -2082,7 +2100,6 @@ mod tests { let path = tmp_path(); let client_a = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(path.clone()), @@ -2097,11 +2114,10 @@ mod tests { .unwrap(); register_client(&ffi_inbox_owner, &client_a).await; - let installation_pub_key = client_a.inner_client.installation_public_key(); + let installation_pub_key = client_a.inner_client.installation_public_key().to_vec(); drop(client_a); let client_b = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(path), @@ -2115,7 +2131,7 @@ mod tests { .await .unwrap(); - let other_installation_pub_key = client_b.inner_client.installation_public_key(); + let other_installation_pub_key = client_b.inner_client.installation_public_key().to_vec(); drop(client_b); assert!( @@ -2135,7 +2151,6 @@ mod tests { let key = static_enc_key().to_vec(); let client_a = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(path.clone()), @@ -2155,7 +2170,6 @@ mod tests { other_key[31] = 1; let result_errored = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(path), @@ -2172,27 +2186,28 @@ mod tests { assert!(result_errored, "did not error on wrong encryption key") } + trait SignWithWallet { + async fn add_wallet_signature(&self, wallet: &xmtp_cryptography::utils::LocalWallet); + } + use super::FfiSignatureRequest; - async fn sign_with_wallet( - wallet: &xmtp_cryptography::utils::LocalWallet, - signature_request: &FfiSignatureRequest, - ) { - let scw_verifier = signature_request.scw_verifier.clone(); - let signature_text = signature_request.inner.lock().await.signature_text(); - let wallet_signature: Vec = wallet.sign(&signature_text.clone()).unwrap().into(); + impl SignWithWallet for FfiSignatureRequest { + async fn add_wallet_signature(&self, wallet: &xmtp_cryptography::utils::LocalWallet) { + let signature_text = self.inner.lock().await.signature_text(); + let wallet_signature: Vec = wallet.sign(&signature_text.clone()).unwrap().into(); - signature_request - .inner - .lock() - .await - .add_signature( - UnverifiedSignature::RecoverableEcdsa(UnverifiedRecoverableEcdsaSignature::new( - wallet_signature, - )), - scw_verifier, - ) - .await - .unwrap(); + self.inner + .lock() + .await + .add_signature( + UnverifiedSignature::RecoverableEcdsa( + UnverifiedRecoverableEcdsaSignature::new(wallet_signature), + ), + &self.scw_verifier, + ) + .await + .unwrap(); + } } use xmtp_cryptography::utils::generate_local_wallet; @@ -2207,7 +2222,6 @@ mod tests { let path = tmp_path(); let key = static_enc_key().to_vec(); let client = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(path.clone()), @@ -2224,7 +2238,9 @@ mod tests { let signature_request = client.signature_request().unwrap().clone(); register_client(&ffi_inbox_owner, &client).await; - sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await; + signature_request + .add_wallet_signature(&ffi_inbox_owner.wallet) + .await; let conn = client.inner_client.store().conn().unwrap(); let state = client @@ -2236,18 +2252,16 @@ mod tests { assert_eq!(state.members().len(), 2); // Now, add the second wallet to the client - let wallet_to_add = generate_local_wallet(); let new_account_address = wallet_to_add.get_address(); println!("second address: {}", new_account_address); let signature_request = client - .add_wallet(&ffi_inbox_owner.get_address(), &new_account_address) + .add_wallet(&new_account_address) .await .expect("could not add wallet"); - sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await; - sign_with_wallet(&wallet_to_add, &signature_request).await; + signature_request.add_wallet_signature(&wallet_to_add).await; client .apply_signature_request(signature_request) @@ -2273,7 +2287,6 @@ mod tests { let path = tmp_path(); let key = static_enc_key().to_vec(); let client = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(path.clone()), @@ -2290,7 +2303,9 @@ mod tests { let signature_request = client.signature_request().unwrap().clone(); register_client(&ffi_inbox_owner, &client).await; - sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await; + signature_request + .add_wallet_signature(&ffi_inbox_owner.wallet) + .await; let conn = client.inner_client.store().conn().unwrap(); let state = client @@ -2308,12 +2323,11 @@ mod tests { println!("second address: {}", new_account_address); let signature_request = client - .add_wallet(&ffi_inbox_owner.get_address(), &new_account_address) + .add_wallet(&new_account_address) .await .expect("could not add wallet"); - sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await; - sign_with_wallet(&wallet_to_add, &signature_request).await; + signature_request.add_wallet_signature(&wallet_to_add).await; client .apply_signature_request(signature_request.clone()) @@ -2334,7 +2348,9 @@ mod tests { .await .expect("could not revoke wallet"); - sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await; + signature_request + .add_wallet_signature(&ffi_inbox_owner.wallet) + .await; client .apply_signature_request(signature_request) @@ -2358,7 +2374,6 @@ mod tests { let path = tmp_path(); let client = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(path.clone()), @@ -2386,7 +2401,6 @@ mod tests { let path = tmp_path(); let client_amal = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(path.clone()), @@ -2413,7 +2427,6 @@ mod tests { ); let client_bola = create_client( - Box::new(MockLogger {}), xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), false, Some(path.clone()), @@ -2491,7 +2504,6 @@ mod tests { // Looks like this test might be a separate issue #[tokio::test(flavor = "multi_thread", worker_threads = 5)] - #[ignore] async fn test_can_stream_group_messages_for_updates() { let alix = new_test_client().await; let bo = new_test_client().await; @@ -2533,6 +2545,17 @@ mod tests { .unwrap(); message_callbacks.wait_for_delivery(None).await.unwrap(); + alix_group.send(b"Hello there".to_vec()).await.unwrap(); + message_callbacks.wait_for_delivery(None).await.unwrap(); + + let dm = bo + .conversations() + .create_dm(alix.account_address.clone()) + .await + .unwrap(); + dm.send(b"Hello again".to_vec()).await.unwrap(); + message_callbacks.wait_for_delivery(None).await.unwrap(); + // Uncomment the following lines to add more group name updates bo_group .update_group_name("Old Name3".to_string()) @@ -2540,7 +2563,7 @@ mod tests { .unwrap(); message_callbacks.wait_for_delivery(None).await.unwrap(); - assert_eq!(message_callbacks.message_count(), 3); + assert_eq!(message_callbacks.message_count(), 6); stream_messages.end_and_wait().await.unwrap(); @@ -2562,7 +2585,10 @@ mod tests { .unwrap(); } - bo.conversations().sync().await.unwrap(); + bo.conversations() + .sync_all_conversations(None) + .await + .unwrap(); let alix_groups = alix .conversations() .list(FfiListConversationsOptions::default()) @@ -2586,7 +2612,10 @@ mod tests { assert_eq!(bo_messages1.len(), 0); assert_eq!(bo_messages5.len(), 0); - bo.conversations().sync_all_conversations().await.unwrap(); + bo.conversations() + .sync_all_conversations(None) + .await + .unwrap(); let bo_messages1 = bo_group1 .find_messages(FfiListMessagesOptions::default()) @@ -2614,8 +2643,12 @@ mod tests { .unwrap(); } bo.conversations().sync().await.unwrap(); - let num_groups_synced_1: u32 = bo.conversations().sync_all_conversations().await.unwrap(); - assert!(num_groups_synced_1 == 30); + let num_groups_synced_1: u32 = bo + .conversations() + .sync_all_conversations(None) + .await + .unwrap(); + assert_eq!(num_groups_synced_1, 30); // Remove bo from all groups and sync for group in alix @@ -2631,12 +2664,20 @@ mod tests { } // First sync after removal needs to process all groups and set them to inactive - let num_groups_synced_2: u32 = bo.conversations().sync_all_conversations().await.unwrap(); - assert!(num_groups_synced_2 == 30); + let num_groups_synced_2: u32 = bo + .conversations() + .sync_all_conversations(None) + .await + .unwrap(); + assert_eq!(num_groups_synced_2, 30); // Second sync after removal will not process inactive groups - let num_groups_synced_3: u32 = bo.conversations().sync_all_conversations().await.unwrap(); - assert!(num_groups_synced_3 == 0); + let num_groups_synced_3: u32 = bo + .conversations() + .sync_all_conversations(None) + .await + .unwrap(); + assert_eq!(num_groups_synced_3, 0); } #[tokio::test(flavor = "multi_thread", worker_threads = 5)] @@ -2789,7 +2830,7 @@ mod tests { let caro = new_test_client().await; // Alix begins a stream for all messages - let message_callbacks = Arc::new(RustStreamCallback::default()); + let message_callbacks = Arc::new(RustStreamCallback::from_client(&alix)); let stream_messages = alix .conversations() .stream_all_messages(message_callbacks.clone()) @@ -2836,12 +2877,12 @@ mod tests { let bo2 = new_test_client_with_wallet(bo_wallet).await; // Bo begins a stream for all messages - let bo_message_callbacks = Arc::new(RustStreamCallback::default()); - let bo_stream_messages = bo2 + let bo2_message_callbacks = Arc::new(RustStreamCallback::from_client(&bo2)); + let bo2_stream_messages = bo2 .conversations() - .stream_all_messages(bo_message_callbacks.clone()) + .stream_all_messages(bo2_message_callbacks.clone()) .await; - bo_stream_messages.wait_for_ready().await; + bo2_stream_messages.wait_for_ready().await; alix_group.update_installations().await.unwrap(); @@ -3205,7 +3246,7 @@ mod tests { let bo = new_test_client().await; let caro = new_test_client().await; - let caro_conn = caro.inner_client.store().conn().unwrap(); + let caro_provider = caro.inner_client.mls_provider().unwrap(); let alix_group = alix .conversations() @@ -3235,7 +3276,11 @@ mod tests { ) .await .unwrap(); - let _ = caro.inner_client.sync_welcomes(&caro_conn).await.unwrap(); + let _ = caro + .inner_client + .sync_welcomes(&caro_provider) + .await + .unwrap(); bo_group.send("second".as_bytes().to_vec()).await.unwrap(); stream_callback.wait_for_delivery(None).await.unwrap(); @@ -3254,7 +3299,7 @@ mod tests { let amal = new_test_client().await; let bola = new_test_client().await; - let bola_conn = bola.inner_client.store().conn().unwrap(); + let bola_provider = bola.inner_client.mls_provider().unwrap(); let amal_group: Arc = amal .conversations() @@ -3265,7 +3310,10 @@ mod tests { .await .unwrap(); - bola.inner_client.sync_welcomes(&bola_conn).await.unwrap(); + bola.inner_client + .sync_welcomes(&bola_provider) + .await + .unwrap(); let bola_group = bola.conversation(amal_group.id()).unwrap(); let stream_callback = Arc::new(RustStreamCallback::default()); @@ -3835,7 +3883,7 @@ mod tests { assert_eq!(client_2_state.installations.len(), 2); let signature_request = client_1.revoke_all_other_installations().await.unwrap(); - sign_with_wallet(&wallet, &signature_request).await; + signature_request.add_wallet_signature(&wallet).await; client_1 .apply_signature_request(signature_request) .await @@ -3875,9 +3923,15 @@ mod tests { .create_dm(bola.account_address.clone()) .await .unwrap(); - let alix_num_sync = alix_conversations.sync_all_conversations().await.unwrap(); + let alix_num_sync = alix_conversations + .sync_all_conversations(None) + .await + .unwrap(); bola_conversations.sync().await.unwrap(); - let bola_num_sync = bola_conversations.sync_all_conversations().await.unwrap(); + let bola_num_sync = bola_conversations + .sync_all_conversations(None) + .await + .unwrap(); assert_eq!(alix_num_sync, 1); assert_eq!(bola_num_sync, 1); @@ -4091,15 +4145,43 @@ mod tests { async fn test_stream_consent() { let wallet = generate_local_wallet(); let alix_a = new_test_client_with_wallet_and_history(wallet.clone()).await; + let alix_a_conn = alix_a.inner_client.store().conn().unwrap(); + // wait for alix_a's sync worker to create a sync group + let _ = wait_for_ok(|| async { alix_a.inner_client.get_sync_group(&alix_a_conn) }).await; + let alix_b = new_test_client_with_wallet_and_history(wallet).await; + wait_for_eq(|| async { alix_b.inner_client.identity().is_ready() }, true) + .await + .unwrap(); + let bo = new_test_client_with_history().await; - // have alix_a pull down the new sync group created by alix_b - assert!(alix_a.conversations().sync().await.is_ok()); + // wait for the first installation to get invited to the new sync group + wait_for_eq( + || async { + assert!(alix_a.conversations().sync().await.is_ok()); + alix_a + .inner_client + .store() + .conn() + .unwrap() + .all_sync_groups() + .unwrap() + .len() + }, + 2, + ) + .await + .unwrap(); // check that they have the same sync group - let sync_group_a = alix_a.conversations().get_sync_group().unwrap(); - let sync_group_b = alix_b.conversations().get_sync_group().unwrap(); + let sync_group_a = wait_for_ok(|| async { alix_a.conversations().get_sync_group() }) + .await + .unwrap(); + let sync_group_b = wait_for_ok(|| async { alix_b.conversations().get_sync_group() }) + .await + .unwrap(); + assert_eq!(sync_group_a.id(), sync_group_b.id()); // create a stream from both installations @@ -4134,7 +4216,7 @@ mod tests { // update the sync group's messages to pipe them into the events alix_b .conversations() - .sync_all_conversations() + .sync_all_conversations(None) .await .unwrap(); @@ -4371,4 +4453,230 @@ mod tests { "Hello in group" ); } + + #[tokio::test(flavor = "multi_thread", worker_threads = 5)] + async fn test_can_not_create_new_inbox_id_with_already_associated_wallet() { + // Step 1: Generate wallet A + let wallet_a = generate_local_wallet(); + + // Step 2: Use wallet A to create a new client with a new inbox id derived from wallet A + let wallet_a_inbox_id = generate_inbox_id(&wallet_a.get_address(), &1).unwrap(); + let client_a = create_client( + xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), + false, + Some(tmp_path()), + Some(xmtp_mls::storage::EncryptedMessageStore::generate_enc_key().into()), + &wallet_a_inbox_id, + wallet_a.get_address(), + 1, + None, + Some(HISTORY_SYNC_URL.to_string()), + ) + .await + .unwrap(); + let ffi_inbox_owner = LocalWalletInboxOwner::with_wallet(wallet_a.clone()); + register_client(&ffi_inbox_owner, &client_a).await; + + // Step 3: Generate wallet B + let wallet_b = generate_local_wallet(); + + // Step 4: Associate wallet B to inbox A + let add_wallet_signature_request = client_a + .add_wallet(&wallet_b.get_address()) + .await + .expect("could not add wallet"); + add_wallet_signature_request + .add_wallet_signature(&wallet_b) + .await; + client_a + .apply_signature_request(add_wallet_signature_request) + .await + .unwrap(); + + // Verify that we can now use wallet B to create a new client that has inbox_id == client_a.inbox_id + let nonce = 1; + let inbox_id = client_a.inbox_id(); + + let client_b = create_client( + xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), + false, + Some(tmp_path()), + Some(xmtp_mls::storage::EncryptedMessageStore::generate_enc_key().into()), + &inbox_id, + wallet_b.get_address(), + nonce, + None, + Some(HISTORY_SYNC_URL.to_string()), + ) + .await + .unwrap(); + let ffi_inbox_owner = LocalWalletInboxOwner::with_wallet(wallet_b.clone()); + register_client(&ffi_inbox_owner, &client_b).await; + + assert!(client_b.inbox_id() == client_a.inbox_id()); + + // Verify both clients can receive messages for inbox_id == client_a.inbox_id + let bo = new_test_client().await; + + // Alix creates DM with Bo + let bo_dm = bo + .conversations() + .create_dm(wallet_a.get_address().clone()) + .await + .unwrap(); + + bo_dm.send("Hello in DM".as_bytes().to_vec()).await.unwrap(); + + // Verify that client_a and client_b received the dm message to wallet a address + client_a + .conversations() + .sync_all_conversations(None) + .await + .unwrap(); + client_b + .conversations() + .sync_all_conversations(None) + .await + .unwrap(); + + let alix_dm_messages = client_a + .conversations() + .list(FfiListConversationsOptions::default()) + .await + .unwrap()[0] + .find_messages(FfiListMessagesOptions::default()) + .unwrap(); + let bo_dm_messages = client_b + .conversations() + .list(FfiListConversationsOptions::default()) + .await + .unwrap()[0] + .find_messages(FfiListMessagesOptions::default()) + .unwrap(); + assert_eq!(alix_dm_messages[0].content, "Hello in DM".as_bytes()); + assert_eq!(bo_dm_messages[0].content, "Hello in DM".as_bytes()); + + let client_b_inbox_id = generate_inbox_id(&wallet_b.get_address(), &nonce).unwrap(); + let client_b_new_result = create_client( + xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), + false, + Some(tmp_path()), + Some(xmtp_mls::storage::EncryptedMessageStore::generate_enc_key().into()), + &client_b_inbox_id, + wallet_b.get_address(), + nonce, + None, + Some(HISTORY_SYNC_URL.to_string()), + ) + .await; + + // Client creation for b now fails since wallet b is already associated with inbox a + match client_b_new_result { + Err(err) => { + println!("Error returned: {:?}", err); + assert_eq!( + err.to_string(), + "Client builder error: error creating new identity: Inbox ID mismatch" + .to_string() + ); + } + Ok(_) => panic!("Expected an error, but got Ok"), + } + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 5)] + async fn test_wallet_b_cannot_create_new_client_for_inbox_b_after_association() { + // Step 1: Wallet A creates a new client with inbox_id A + let wallet_a = generate_local_wallet(); + let wallet_a_inbox_id = generate_inbox_id(&wallet_a.get_address(), &1).unwrap(); + let client_a = create_client( + xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), + false, + Some(tmp_path()), + Some(xmtp_mls::storage::EncryptedMessageStore::generate_enc_key().into()), + &wallet_a_inbox_id, + wallet_a.get_address(), + 1, + None, + Some(HISTORY_SYNC_URL.to_string()), + ) + .await + .unwrap(); + let ffi_inbox_owner_a = LocalWalletInboxOwner::with_wallet(wallet_a.clone()); + register_client(&ffi_inbox_owner_a, &client_a).await; + + // Step 2: Wallet B creates a new client with inbox_id B + let wallet_b = generate_local_wallet(); + let wallet_b_inbox_id = generate_inbox_id(&wallet_b.get_address(), &1).unwrap(); + let client_b1 = create_client( + xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), + false, + Some(tmp_path()), + Some(xmtp_mls::storage::EncryptedMessageStore::generate_enc_key().into()), + &wallet_b_inbox_id, + wallet_b.get_address(), + 1, + None, + Some(HISTORY_SYNC_URL.to_string()), + ) + .await + .unwrap(); + let ffi_inbox_owner_b1 = LocalWalletInboxOwner::with_wallet(wallet_b.clone()); + register_client(&ffi_inbox_owner_b1, &client_b1).await; + + // Step 3: Wallet B creates a second client for inbox_id B + let _client_b2 = create_client( + xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), + false, + Some(tmp_path()), + Some(xmtp_mls::storage::EncryptedMessageStore::generate_enc_key().into()), + &wallet_b_inbox_id, + wallet_b.get_address(), + 1, + None, + Some(HISTORY_SYNC_URL.to_string()), + ) + .await + .unwrap(); + + // Step 4: Client A adds association to wallet B + let add_wallet_signature_request = client_a + .add_wallet(&wallet_b.get_address()) + .await + .expect("could not add wallet"); + add_wallet_signature_request + .add_wallet_signature(&wallet_b) + .await; + client_a + .apply_signature_request(add_wallet_signature_request) + .await + .unwrap(); + + // Step 5: Wallet B tries to create another new client for inbox_id B, but it fails + let client_b3 = create_client( + xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(), + false, + Some(tmp_path()), + Some(xmtp_mls::storage::EncryptedMessageStore::generate_enc_key().into()), + &wallet_b_inbox_id, + wallet_b.get_address(), + 1, + None, + Some(HISTORY_SYNC_URL.to_string()), + ) + .await; + + // Client creation for b now fails since wallet b is already associated with inbox a + match client_b3 { + Err(err) => { + println!("Error returned: {:?}", err); + assert_eq!( + err.to_string(), + "Client builder error: error creating new identity: Inbox ID mismatch" + .to_string() + ); + } + Ok(_) => panic!("Expected an error, but got Ok"), + } + } } diff --git a/bindings_ffi/src/xmtpv3.udl b/bindings_ffi/src/xmtpv3.udl index fa7fef7a1..635ba512e 100644 --- a/bindings_ffi/src/xmtpv3.udl +++ b/bindings_ffi/src/xmtpv3.udl @@ -12,7 +12,3 @@ callback interface FfiInboxOwner { [Throws=SigningError] bytes sign(string text); }; - -callback interface FfiLogger { - void log(u32 level, string level_label, string message); -}; \ No newline at end of file diff --git a/bindings_ffi/tests/test_android.kts b/bindings_ffi/tests/test_android.kts index 21871d2db..fd787db1d 100644 --- a/bindings_ffi/tests/test_android.kts +++ b/bindings_ffi/tests/test_android.kts @@ -17,20 +17,15 @@ class Web3jInboxOwner(private val credentials: Credentials) : FfiInboxOwner { } } -class MockLogger : FfiLogger { - override fun log(level: UInt, levelLabel: String, message: String) {} -} - val privateKey: ByteArray = SecureRandom().generateSeed(32) val credentials: Credentials = Credentials.create(ECKeyPair.create(privateKey)) val inboxOwner = Web3jInboxOwner(credentials) -var logger = MockLogger() // TODO Tests sometimes hang and never complete // runBlocking { // val apiUrl: String = System.getenv("XMTP_API_URL") ?: "http://localhost:5556" // try { -// val client = uniffi.xmtpv3.createClient(logger, inboxOwner, apiUrl, false) +// val client = uniffi.xmtpv3.createClient(inboxOwner, apiUrl, false) // assert(client.walletAddress() != null) { // "Should be able to get wallet address" // } @@ -43,7 +38,7 @@ var logger = MockLogger() // runBlocking { // try { -// val client = uniffi.xmtpv3.createClient(logger, inboxOwner, "http://malformed:5556", false); +// val client = uniffi.xmtpv3.createClient(inboxOwner, "http://malformed:5556", false); // assert(false) { // "Should throw error with malformed network address" // } diff --git a/bindings_node/CHANGELOG.md b/bindings_node/CHANGELOG.md index 4c2dbe669..3a46a8658 100644 --- a/bindings_node/CHANGELOG.md +++ b/bindings_node/CHANGELOG.md @@ -1,5 +1,35 @@ # @xmtp/node-bindings +## 0.0.28 + +- Removed `is_installation_authorized` and `is_address_authorized` from `Client` +- Lowercased `address` passed to `is_address_authorized` + +## 0.0.27 + +- Switched to Ubuntu 22.04 for builds + +## 0.0.25 + +- Fixed streaming by adding `napi4` feature to napi-rs + +## 0.0.24 + +- Fixed using `Vec` instead rust `Uint8Array` type in `is_installation_authorized` + +## 0.0.23 + +- Added `is_installation_authorized` to `Client` +- Added `is_address_authorized` to `Client` + +## 0.0.22 + +- Moved `verify_signed_with_public_key` out of `Client` + +## 0.0.21 + +- Added `installation_id_bytes` to `Client` + ## 0.0.20 - Fixed argument types for new signing methods @@ -9,7 +39,8 @@ - Renamed `Level` to `LogLevel` - Filtered out group membership messages from DM groups - Fixed `syncAllConversations` export -- Added `sign_with_installation_key`, `verify_signed_with_installation_key`, and `verify_signed_with_public_key` to `Client` +- Added `sign_with_installation_key`, `verify_signed_with_installation_key`, and + `verify_signed_with_public_key` to `Client` ## 0.0.18 @@ -27,9 +58,12 @@ - Added sort direction to `NapiListMessagesOptions` - Added `dm_peer_inbox_id` method to `NapiGroup` -- Added `allowed_states` and `conversation_type` to `NapiListConversationsOptions` -- Added `create_dm`, `list_groups`, and `list_dms` methods to `NapiConversations` -- Added `stream_groups`, `stream_dms`, `stream_all_group_messages`, and `stream_all_dm_messages` streaming methods to `NapiConversations` +- Added `allowed_states` and `conversation_type` to + `NapiListConversationsOptions` +- Added `create_dm`, `list_groups`, and `list_dms` methods to + `NapiConversations` +- Added `stream_groups`, `stream_dms`, `stream_all_group_messages`, and + `stream_all_dm_messages` streaming methods to `NapiConversations` ## 0.0.15 diff --git a/bindings_node/Cargo.toml b/bindings_node/Cargo.toml index e3beae204..81729585c 100644 --- a/bindings_node/Cargo.toml +++ b/bindings_node/Cargo.toml @@ -8,21 +8,29 @@ crate-type = ["cdylib"] [dependencies] # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix +futures.workspace = true hex.workspace = true napi = { version = "2.12.2", default-features = false, features = [ + "napi4", "napi6", "async", ] } napi-derive = "2.12.2" prost.workspace = true tokio = { workspace = true, features = ["sync"] } -tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "json", "chrono"] } +tracing-subscriber = { workspace = true, features = [ + "env-filter", + "fmt", + "json", + "chrono", +] } tracing.workspace = true xmtp_api_grpc = { path = "../xmtp_api_grpc" } xmtp_cryptography = { path = "../xmtp_cryptography" } xmtp_id = { path = "../xmtp_id" } xmtp_mls = { path = "../xmtp_mls" } xmtp_proto = { path = "../xmtp_proto", features = ["proto_full"] } +xmtp_common.workspace = true [build-dependencies] napi-build = "2.0.1" diff --git a/bindings_node/README.md b/bindings_node/README.md index 6ffd0180a..750a5e7bf 100644 --- a/bindings_node/README.md +++ b/bindings_node/README.md @@ -13,3 +13,7 @@ ## Testing Before running the test suite, a local XMTP node must be running. This can be achieved by running `./dev/up` at the root of this repository. Docker is required. + +# Publishing + +To release a new version of the bindings, update the version in `package.json` with the appropriate semver value and add an entry to the CHANGELOG.md file. Once merged, manually trigger the `Release Node Bindings` workflow to build and publish the bindings. diff --git a/bindings_node/package.json b/bindings_node/package.json index 9939f3844..a8a0907b0 100644 --- a/bindings_node/package.json +++ b/bindings_node/package.json @@ -1,6 +1,6 @@ { "name": "@xmtp/node-bindings", - "version": "0.0.20", + "version": "0.0.28", "repository": { "type": "git", "url": "git+https://git@github.com/xmtp/libxmtp.git", @@ -38,14 +38,14 @@ "devDependencies": { "@ianvs/prettier-plugin-sort-imports": "^4.4.0", "@napi-rs/cli": "^3.0.0-alpha.64", - "@types/node": "^22.9.0", + "@types/node": "^22.10.1", "@types/uuid": "^10.0.0", - "prettier": "^3.3.3", + "prettier": "^3.4.1", "prettier-plugin-packagejson": "^2.5.3", - "typescript": "^5.6.3", + "typescript": "^5.7.2", "uuid": "^11.0.3", "viem": "^2.21.47", - "vite": "^5.4.11", + "vite": "^6.0.1", "vite-tsconfig-paths": "^5.1.2", "vitest": "^2.1.5" }, diff --git a/bindings_node/src/client.rs b/bindings_node/src/client.rs index d2158b57c..cbe595bd9 100644 --- a/bindings_node/src/client.rs +++ b/bindings_node/src/client.rs @@ -13,7 +13,6 @@ use tracing_subscriber::{fmt, prelude::*}; pub use xmtp_api_grpc::grpc_api_helper::Client as TonicApiClient; use xmtp_cryptography::signature::ed25519_public_key_to_address; use xmtp_id::associations::builder::SignatureRequest; -use xmtp_id::associations::verify_signed_with_public_context; use xmtp_mls::builder::ClientBuilder; use xmtp_mls::groups::scoped_client::LocalScopedGroupClient; use xmtp_mls::identity::IdentityStrategy; @@ -123,7 +122,7 @@ fn init_logging(options: LogOptions) -> Result<()> { pub async fn create_client( host: String, is_secure: bool, - db_path: String, + db_path: Option, inbox_id: String, account_address: String, encryption_key: Option, @@ -131,11 +130,14 @@ pub async fn create_client( log_options: Option, ) -> Result { init_logging(log_options.unwrap_or_default())?; - let api_client = TonicApiClient::create(host.clone(), is_secure) + let api_client = TonicApiClient::create(&host, is_secure) .await .map_err(|_| Error::from_reason("Error creating Tonic API client"))?; - let storage_option = StorageOption::Persistent(db_path); + let storage_option = match db_path { + Some(path) => StorageOption::Persistent(path), + None => StorageOption::Ephemeral, + }; let store = match encryption_key { Some(key) => { @@ -152,7 +154,7 @@ pub async fn create_client( .map_err(|_| Error::from_reason("Error creating unencrypted message store"))?, }; - let identity_strategy = IdentityStrategy::CreateIfNotFound( + let identity_strategy = IdentityStrategy::new( inbox_id.clone(), account_address.clone().to_lowercase(), // this is a temporary solution @@ -201,6 +203,11 @@ impl Client { ed25519_public_key_to_address(self.inner_client.installation_public_key().as_slice()) } + #[napi] + pub fn installation_id_bytes(&self) -> Uint8Array { + self.inner_client.installation_public_key().into() + } + #[napi] pub async fn can_message(&self, account_addresses: Vec) -> Result> { let results: HashMap = self @@ -268,9 +275,14 @@ impl Client { #[napi] pub async fn find_inbox_id_by_address(&self, address: String) -> Result> { + let conn = self + .inner_client() + .store() + .conn() + .map_err(ErrorWrapper::from)?; let inbox_id = self .inner_client - .find_inbox_id_from_address(address) + .find_inbox_id_from_address(&conn, address) .await .map_err(ErrorWrapper::from)?; @@ -285,53 +297,12 @@ impl Client { ) -> Result> { let state = self .inner_client - .inbox_addresses(refresh_from_network, inbox_ids) + .inbox_addresses( + refresh_from_network, + inbox_ids.iter().map(String::as_str).collect(), + ) .await .map_err(ErrorWrapper::from)?; Ok(state.into_iter().map(Into::into).collect()) } - - #[napi] - pub fn sign_with_installation_key(&self, signature_text: String) -> Result { - let result = self - .inner_client - .context() - .sign_with_public_context(signature_text) - .map_err(ErrorWrapper::from)?; - - Ok(result.into()) - } - - #[napi] - pub fn verify_signed_with_installation_key( - &self, - signature_text: String, - signature_bytes: Uint8Array, - ) -> Result<()> { - let public_key = self.inner_client().installation_public_key(); - self.verify_signed_with_public_key(signature_text, signature_bytes, public_key.into()) - } - - #[napi] - pub fn verify_signed_with_public_key( - &self, - signature_text: String, - signature_bytes: Uint8Array, - public_key: Uint8Array, - ) -> Result<()> { - let signature_bytes = signature_bytes.deref().to_vec(); - let signature_bytes: [u8; 64] = signature_bytes - .try_into() - .map_err(|_| Error::from_reason("signature_bytes is not 64 bytes long."))?; - - let public_key = public_key.deref().to_vec(); - let public_key: [u8; 32] = public_key - .try_into() - .map_err(|_| Error::from_reason("public_key is not 32 bytes long."))?; - - Ok( - verify_signed_with_public_context(signature_text, &signature_bytes, &public_key) - .map_err(ErrorWrapper::from)?, - ) - } } diff --git a/bindings_node/src/conversation.rs b/bindings_node/src/conversation.rs index aeeffa6b1..897662463 100644 --- a/bindings_node/src/conversation.rs +++ b/bindings_node/src/conversation.rs @@ -197,8 +197,9 @@ impl Conversation { self.created_at_ns, ); let envelope_bytes: Vec = envelope_bytes.deref().to_vec(); + let provider = group.mls_provider().map_err(ErrorWrapper::from)?; let message = group - .process_streamed_group_message(envelope_bytes) + .process_streamed_group_message(&provider, envelope_bytes) .await .map_err(ErrorWrapper::from)?; diff --git a/bindings_node/src/conversations.rs b/bindings_node/src/conversations.rs index 24b618a40..f957a9807 100644 --- a/bindings_node/src/conversations.rs +++ b/bindings_node/src/conversations.rs @@ -235,14 +235,13 @@ impl Conversations { #[napi] pub async fn sync(&self) -> Result<()> { - let conn = self + let provider = self .inner_client - .store() - .conn() + .mls_provider() .map_err(ErrorWrapper::from)?; self .inner_client - .sync_welcomes(&conn) + .sync_welcomes(&provider) .await .map_err(ErrorWrapper::from)?; Ok(()) @@ -250,15 +249,17 @@ impl Conversations { #[napi] pub async fn sync_all_conversations(&self) -> Result { - let groups = self + let provider = self .inner_client - .find_groups(GroupQueryArgs::default()) + .mls_provider() .map_err(ErrorWrapper::from)?; + let num_groups_synced = self .inner_client - .sync_all_groups(groups) + .sync_all_welcomes_and_groups(&provider, None) .await .map_err(ErrorWrapper::from)?; + Ok(num_groups_synced) } @@ -342,12 +343,22 @@ impl Conversations { callback: JsFunction, conversation_type: Option, ) -> Result { + tracing::trace!( + inbox_id = self.inner_client.inbox_id(), + conversation_type = ?conversation_type, + ); let tsfn: ThreadsafeFunction = callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value]))?; + let inbox_id = self.inner_client.inbox_id().to_string(); let stream_closer = RustXmtpClient::stream_all_messages_with_callback( self.inner_client.clone(), conversation_type.map(Into::into), move |message| { + tracing::trace!( + inbox_id, + conversation_type = ?conversation_type, + "[received] calling tsfn callback" + ); tsfn.call( message .map(Into::into) diff --git a/bindings_node/src/inbox_id.rs b/bindings_node/src/inbox_id.rs index ebe93c6d7..5b91cb9ea 100644 --- a/bindings_node/src/inbox_id.rs +++ b/bindings_node/src/inbox_id.rs @@ -1,10 +1,13 @@ use crate::ErrorWrapper; use napi::bindgen_prelude::Result; +use napi::bindgen_prelude::Uint8Array; use napi_derive::napi; +use std::sync::Arc; use xmtp_api_grpc::grpc_api_helper::Client as TonicApiClient; +use xmtp_common::retry::Retry; use xmtp_id::associations::generate_inbox_id as xmtp_id_generate_inbox_id; +use xmtp_id::associations::MemberIdentifier; use xmtp_mls::api::ApiClientWrapper; -use xmtp_mls::retry::Retry; #[napi] pub async fn get_inbox_id_for_address( @@ -14,7 +17,7 @@ pub async fn get_inbox_id_for_address( ) -> Result> { let account_address = account_address.to_lowercase(); let api_client = ApiClientWrapper::new( - TonicApiClient::create(host.clone(), is_secure) + TonicApiClient::create(host, is_secure) .await .map_err(ErrorWrapper::from)? .into(), @@ -37,3 +40,53 @@ pub fn generate_inbox_id(account_address: String) -> Result { let result = xmtp_id_generate_inbox_id(&account_address, &1).map_err(ErrorWrapper::from)?; Ok(result) } + +#[napi] +pub async fn is_installation_authorized( + host: String, + inbox_id: String, + installation_id: Uint8Array, +) -> Result { + is_member_of_association_state( + &host, + &inbox_id, + &MemberIdentifier::Installation(installation_id.to_vec()), + ) + .await +} + +#[napi] +pub async fn is_address_authorized( + host: String, + inbox_id: String, + address: String, +) -> Result { + is_member_of_association_state( + &host, + &inbox_id, + &MemberIdentifier::Address(address.to_lowercase()), + ) + .await +} + +async fn is_member_of_association_state( + host: &str, + inbox_id: &str, + identifier: &MemberIdentifier, +) -> Result { + let api_client = TonicApiClient::create(host, true) + .await + .map_err(ErrorWrapper::from)?; + let api_client = ApiClientWrapper::new(Arc::new(api_client), Retry::default()); + + let is_member = xmtp_mls::identity_updates::is_member_of_association_state( + &api_client, + inbox_id, + identifier, + None, + ) + .await + .map_err(ErrorWrapper::from)?; + + Ok(is_member) +} diff --git a/bindings_node/src/signatures.rs b/bindings_node/src/signatures.rs index cfc67a81f..246ae5325 100644 --- a/bindings_node/src/signatures.rs +++ b/bindings_node/src/signatures.rs @@ -5,9 +5,31 @@ use napi_derive::napi; use std::ops::Deref; use xmtp_id::associations::{ unverified::{NewUnverifiedSmartContractWalletSignature, UnverifiedSignature}, - AccountId, + verify_signed_with_public_context, AccountId, }; +#[napi] +pub fn verify_signed_with_public_key( + signature_text: String, + signature_bytes: Uint8Array, + public_key: Uint8Array, +) -> Result<()> { + let signature_bytes = signature_bytes.deref().to_vec(); + let signature_bytes: [u8; 64] = signature_bytes + .try_into() + .map_err(|_| Error::from_reason("signature_bytes is not 64 bytes long."))?; + + let public_key = public_key.deref().to_vec(); + let public_key: [u8; 32] = public_key + .try_into() + .map_err(|_| Error::from_reason("public_key is not 32 bytes long."))?; + + Ok( + verify_signed_with_public_context(signature_text, &signature_bytes, &public_key) + .map_err(ErrorWrapper::from)?, + ) +} + #[napi] #[derive(Eq, Hash, PartialEq)] pub enum SignatureRequestType { @@ -35,17 +57,11 @@ impl Client { } #[napi] - pub async fn add_wallet_signature_text( - &self, - existing_wallet_address: String, - new_wallet_address: String, - ) -> Result { + pub async fn add_wallet_signature_text(&self, new_wallet_address: String) -> Result { let signature_request = self .inner_client() - .associate_wallet( - existing_wallet_address.to_lowercase(), - new_wallet_address.to_lowercase(), - ) + .associate_wallet(new_wallet_address.to_lowercase()) + .await .map_err(ErrorWrapper::from)?; let signature_text = signature_request.signature_text(); let mut signature_requests = self.signature_requests().lock().await; @@ -81,7 +97,7 @@ impl Client { let other_installation_ids = inbox_state .installation_ids() .into_iter() - .filter(|id| id != &installation_id) + .filter(|id| id != installation_id) .collect(); let signature_request = self .inner_client() @@ -173,4 +189,25 @@ impl Client { Ok(()) } + + #[napi] + pub fn sign_with_installation_key(&self, signature_text: String) -> Result { + let result = self + .inner_client() + .context() + .sign_with_public_context(signature_text) + .map_err(ErrorWrapper::from)?; + + Ok(result.into()) + } + + #[napi] + pub fn verify_signed_with_installation_key( + &self, + signature_text: String, + signature_bytes: Uint8Array, + ) -> Result<()> { + let public_key = self.inner_client().installation_public_key(); + verify_signed_with_public_key(signature_text, signature_bytes, public_key.into()) + } } diff --git a/bindings_node/src/streams.rs b/bindings_node/src/streams.rs index 1094bf54d..782f85edc 100644 --- a/bindings_node/src/streams.rs +++ b/bindings_node/src/streams.rs @@ -63,6 +63,14 @@ impl StreamCloser { } } + #[napi] + pub async fn wait_for_ready(&self) -> Result<(), Error> { + let mut stream_handle = self.handle.lock().await; + futures::future::OptionFuture::from((*stream_handle).as_mut().map(|s| s.wait_for_ready())) + .await; + Ok(()) + } + /// Checks if this stream is closed #[napi] pub fn is_closed(&self) -> bool { diff --git a/bindings_node/test/Client.test.ts b/bindings_node/test/Client.test.ts index 063b0f603..ee10c30bc 100644 --- a/bindings_node/test/Client.test.ts +++ b/bindings_node/test/Client.test.ts @@ -1,8 +1,19 @@ import { v4 } from 'uuid' import { toBytes } from 'viem' import { describe, expect, it } from 'vitest' -import { createClient, createRegisteredClient, createUser } from '@test/helpers' -import { ConsentEntityType, ConsentState, SignatureRequestType } from '../dist' +import { + createClient, + createRegisteredClient, + createUser, + encodeTextMessage, + sleep, +} from '@test/helpers' +import { + ConsentEntityType, + ConsentState, + SignatureRequestType, + verifySignedWithPublicKey, +} from '../dist' describe('Client', () => { it('should not be registered at first', async () => { @@ -64,23 +75,14 @@ describe('Client', () => { const user2 = createUser() const client = await createRegisteredClient(user) const signatureText = await client.addWalletSignatureText( - user.account.address, user2.account.address ) expect(signatureText).toBeDefined() - // sign message - const signature = await user.wallet.signMessage({ - message: signatureText, - }) const signature2 = await user2.wallet.signMessage({ message: signatureText, }) - await client.addSignature( - SignatureRequestType.AddWallet, - toBytes(signature) - ) await client.addSignature( SignatureRequestType.AddWallet, toBytes(signature2) @@ -101,23 +103,15 @@ describe('Client', () => { const user2 = createUser() const client = await createRegisteredClient(user) const signatureText = await client.addWalletSignatureText( - user.account.address, user2.account.address ) expect(signatureText).toBeDefined() // sign message - const signature = await user.wallet.signMessage({ - message: signatureText, - }) const signature2 = await user2.wallet.signMessage({ message: signatureText, }) - await client.addSignature( - SignatureRequestType.AddWallet, - toBytes(signature) - ) await client.addSignature( SignatureRequestType.AddWallet, toBytes(signature2) @@ -241,5 +235,56 @@ describe('Client', () => { user2.account.address.toLowerCase(), ]) }) - it('should create client with structured logging', async () => {}) + + it('should sign and verify with installation key', async () => { + const user = createUser() + const client = await createRegisteredClient(user) + const text = 'gm!' + const signature = client.signWithInstallationKey(text) + expect(signature).toBeDefined() + expect(() => + client.verifySignedWithInstallationKey(text, signature) + ).not.toThrow() + expect(() => + client.verifySignedWithInstallationKey(text, new Uint8Array()) + ).toThrow() + expect(() => + verifySignedWithPublicKey(text, signature, client.installationIdBytes()) + ).not.toThrow() + expect(() => + verifySignedWithPublicKey(text, signature, new Uint8Array()) + ).toThrow() + }) +}) + +describe('Streams', () => { + it('should stream all messages', async () => { + const user = createUser() + const client1 = await createRegisteredClient(user) + + const user2 = createUser() + const client2 = await createRegisteredClient(user2) + + const group = await client1 + .conversations() + .createGroup([user2.account.address]) + + await client2.conversations().sync() + const group2 = client2.conversations().findGroupById(group.id()) + + let messages = new Array() + client2.conversations().syncAllConversations() + let stream = client2.conversations().streamAllMessages((msg) => { + console.log('Message', msg) + messages.push(msg) + }) + await stream.waitForReady() + group.send(encodeTextMessage('Test1')) + group.send(encodeTextMessage('Test2')) + group.send(encodeTextMessage('Test3')) + group.send(encodeTextMessage('Test4')) + await sleep(1000) + await stream.endAndWait() + expect(messages.length).toBe(4) + }) }) diff --git a/bindings_node/test/helpers.ts b/bindings_node/test/helpers.ts index 1d3260925..989063d38 100644 --- a/bindings_node/test/helpers.ts +++ b/bindings_node/test/helpers.ts @@ -8,6 +8,7 @@ import { createClient as create, generateInboxId, getInboxIdForAddress, + LogLevel, SignatureRequestType, } from '../dist/index' @@ -44,7 +45,7 @@ export const createClient = async (user: User) => { user.account.address, undefined, undefined, - { level: 'info' } + { level: LogLevel.info } ) } @@ -80,3 +81,9 @@ export const encodeTextMessage = (text: string) => { content: new TextEncoder().encode(text), } } + +export function sleep(ms: number) { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }) +} diff --git a/bindings_node/test/inboxId.test.ts b/bindings_node/test/inboxId.test.ts index bb771853f..d4e4e2139 100644 --- a/bindings_node/test/inboxId.test.ts +++ b/bindings_node/test/inboxId.test.ts @@ -1,6 +1,11 @@ import { describe, expect, it } from 'vitest' import { createRegisteredClient, createUser, TEST_API_URL } from '@test/helpers' -import { generateInboxId, getInboxIdForAddress } from '../dist/index' +import { + generateInboxId, + getInboxIdForAddress, + isAddressAuthorized, + isInstallationAuthorized, +} from '../dist/index' describe('generateInboxId', () => { it('should generate an inbox id', () => { @@ -32,3 +37,29 @@ describe('getInboxIdForAddress', () => { expect(inboxId).toBe(client.inboxId()) }) }) + +describe('isInstallationAuthorized', () => { + it('should return true if installation is authorized', async () => { + const user = createUser() + const client = await createRegisteredClient(user) + const isAuthorized = await isInstallationAuthorized( + TEST_API_URL, + client.inboxId(), + client.installationIdBytes() + ) + expect(isAuthorized).toBe(true) + }) +}) + +describe('isAddressAuthorized', () => { + it('should return true if address is authorized', async () => { + const user = createUser() + const client = await createRegisteredClient(user) + const isAuthorized = await isAddressAuthorized( + TEST_API_URL, + client.inboxId(), + user.account.address + ) + expect(isAuthorized).toBe(true) + }) +}) diff --git a/bindings_node/yarn.lock b/bindings_node/yarn.lock index 94a66d978..5f2bdde7b 100644 --- a/bindings_node/yarn.lock +++ b/bindings_node/yarn.lock @@ -132,9 +132,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/aix-ppc64@npm:0.21.5" +"@esbuild/aix-ppc64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/aix-ppc64@npm:0.24.0" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -146,9 +146,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm64@npm:0.21.5" +"@esbuild/android-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/android-arm64@npm:0.24.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -160,9 +160,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm@npm:0.21.5" +"@esbuild/android-arm@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/android-arm@npm:0.24.0" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -174,9 +174,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-x64@npm:0.21.5" +"@esbuild/android-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/android-x64@npm:0.24.0" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -188,9 +188,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-arm64@npm:0.21.5" +"@esbuild/darwin-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/darwin-arm64@npm:0.24.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -202,9 +202,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-x64@npm:0.21.5" +"@esbuild/darwin-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/darwin-x64@npm:0.24.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -216,9 +216,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-arm64@npm:0.21.5" +"@esbuild/freebsd-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/freebsd-arm64@npm:0.24.0" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -230,9 +230,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-x64@npm:0.21.5" +"@esbuild/freebsd-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/freebsd-x64@npm:0.24.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -244,9 +244,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm64@npm:0.21.5" +"@esbuild/linux-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-arm64@npm:0.24.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -258,9 +258,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm@npm:0.21.5" +"@esbuild/linux-arm@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-arm@npm:0.24.0" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -272,9 +272,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ia32@npm:0.21.5" +"@esbuild/linux-ia32@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-ia32@npm:0.24.0" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -286,9 +286,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-loong64@npm:0.21.5" +"@esbuild/linux-loong64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-loong64@npm:0.24.0" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -300,9 +300,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-mips64el@npm:0.21.5" +"@esbuild/linux-mips64el@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-mips64el@npm:0.24.0" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -314,9 +314,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ppc64@npm:0.21.5" +"@esbuild/linux-ppc64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-ppc64@npm:0.24.0" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -328,9 +328,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-riscv64@npm:0.21.5" +"@esbuild/linux-riscv64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-riscv64@npm:0.24.0" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -342,9 +342,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-s390x@npm:0.21.5" +"@esbuild/linux-s390x@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-s390x@npm:0.24.0" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -356,9 +356,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-x64@npm:0.21.5" +"@esbuild/linux-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-x64@npm:0.24.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard @@ -370,13 +370,20 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/netbsd-x64@npm:0.21.5" +"@esbuild/netbsd-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/netbsd-x64@npm:0.24.0" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/openbsd-arm64@npm:0.24.0" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/openbsd-x64@npm:0.20.2" @@ -384,9 +391,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/openbsd-x64@npm:0.21.5" +"@esbuild/openbsd-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/openbsd-x64@npm:0.24.0" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard @@ -398,9 +405,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/sunos-x64@npm:0.21.5" +"@esbuild/sunos-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/sunos-x64@npm:0.24.0" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -412,9 +419,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-arm64@npm:0.21.5" +"@esbuild/win32-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/win32-arm64@npm:0.24.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -426,9 +433,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-ia32@npm:0.21.5" +"@esbuild/win32-ia32@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/win32-ia32@npm:0.24.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -440,9 +447,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-x64@npm:0.21.5" +"@esbuild/win32-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/win32-x64@npm:0.24.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1450,9 +1457,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.24.4" +"@rollup/rollup-android-arm-eabi@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.28.0" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -1464,9 +1471,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-android-arm64@npm:4.24.4" +"@rollup/rollup-android-arm64@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-android-arm64@npm:4.28.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -1478,9 +1485,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-darwin-arm64@npm:4.24.4" +"@rollup/rollup-darwin-arm64@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.28.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -1492,23 +1499,23 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-darwin-x64@npm:4.24.4" +"@rollup/rollup-darwin-x64@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.28.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.24.4" +"@rollup/rollup-freebsd-arm64@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.28.0" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-freebsd-x64@npm:4.24.4" +"@rollup/rollup-freebsd-x64@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-freebsd-x64@npm:4.28.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -1520,9 +1527,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.24.4" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.28.0" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard @@ -1534,9 +1541,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.24.4" +"@rollup/rollup-linux-arm-musleabihf@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.28.0" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard @@ -1548,9 +1555,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.24.4" +"@rollup/rollup-linux-arm64-gnu@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.28.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard @@ -1562,9 +1569,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.24.4" +"@rollup/rollup-linux-arm64-musl@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.28.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard @@ -1576,9 +1583,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.4" +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.28.0" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard @@ -1590,9 +1597,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.24.4" +"@rollup/rollup-linux-riscv64-gnu@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.28.0" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard @@ -1604,9 +1611,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.24.4" +"@rollup/rollup-linux-s390x-gnu@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.28.0" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard @@ -1618,9 +1625,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.24.4" +"@rollup/rollup-linux-x64-gnu@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.28.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard @@ -1632,9 +1639,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.24.4" +"@rollup/rollup-linux-x64-musl@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.28.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard @@ -1646,9 +1653,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.24.4" +"@rollup/rollup-win32-arm64-msvc@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.28.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -1660,9 +1667,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.24.4" +"@rollup/rollup-win32-ia32-msvc@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.28.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -1674,9 +1681,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.24.4": - version: 4.24.4 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.24.4" +"@rollup/rollup-win32-x64-msvc@npm:4.28.0": + version: 4.28.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.28.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1732,12 +1739,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^22.9.0": - version: 22.9.0 - resolution: "@types/node@npm:22.9.0" +"@types/node@npm:^22.10.1": + version: 22.10.1 + resolution: "@types/node@npm:22.10.1" dependencies: - undici-types: "npm:~6.19.8" - checksum: 10/a7df3426891868b0f5fb03e46aeddd8446178233521c624a44531c92a040cf08a82d8235f7e1e02af731fd16984665d4d71f3418caf9c2788313b10f040d615d + undici-types: "npm:~6.20.0" + checksum: 10/c802a526da2f3fa3ccefd00a71244e7cb825329951719e79e8fec62b1dbc2855388c830489770611584665ce10be23c05ed585982038b24924e1ba2c2cce03fd languageName: node linkType: hard @@ -1835,14 +1842,14 @@ __metadata: dependencies: "@ianvs/prettier-plugin-sort-imports": "npm:^4.4.0" "@napi-rs/cli": "npm:^3.0.0-alpha.64" - "@types/node": "npm:^22.9.0" + "@types/node": "npm:^22.10.1" "@types/uuid": "npm:^10.0.0" - prettier: "npm:^3.3.3" + prettier: "npm:^3.4.1" prettier-plugin-packagejson: "npm:^2.5.3" - typescript: "npm:^5.6.3" + typescript: "npm:^5.7.2" uuid: "npm:^11.0.3" viem: "npm:^2.21.47" - vite: "npm:^5.4.11" + vite: "npm:^6.0.1" vite-tsconfig-paths: "npm:^5.1.2" vitest: "npm:^2.1.5" languageName: unknown @@ -2291,33 +2298,34 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.21.3": - version: 0.21.5 - resolution: "esbuild@npm:0.21.5" - dependencies: - "@esbuild/aix-ppc64": "npm:0.21.5" - "@esbuild/android-arm": "npm:0.21.5" - "@esbuild/android-arm64": "npm:0.21.5" - "@esbuild/android-x64": "npm:0.21.5" - "@esbuild/darwin-arm64": "npm:0.21.5" - "@esbuild/darwin-x64": "npm:0.21.5" - "@esbuild/freebsd-arm64": "npm:0.21.5" - "@esbuild/freebsd-x64": "npm:0.21.5" - "@esbuild/linux-arm": "npm:0.21.5" - "@esbuild/linux-arm64": "npm:0.21.5" - "@esbuild/linux-ia32": "npm:0.21.5" - "@esbuild/linux-loong64": "npm:0.21.5" - "@esbuild/linux-mips64el": "npm:0.21.5" - "@esbuild/linux-ppc64": "npm:0.21.5" - "@esbuild/linux-riscv64": "npm:0.21.5" - "@esbuild/linux-s390x": "npm:0.21.5" - "@esbuild/linux-x64": "npm:0.21.5" - "@esbuild/netbsd-x64": "npm:0.21.5" - "@esbuild/openbsd-x64": "npm:0.21.5" - "@esbuild/sunos-x64": "npm:0.21.5" - "@esbuild/win32-arm64": "npm:0.21.5" - "@esbuild/win32-ia32": "npm:0.21.5" - "@esbuild/win32-x64": "npm:0.21.5" +"esbuild@npm:^0.24.0": + version: 0.24.0 + resolution: "esbuild@npm:0.24.0" + dependencies: + "@esbuild/aix-ppc64": "npm:0.24.0" + "@esbuild/android-arm": "npm:0.24.0" + "@esbuild/android-arm64": "npm:0.24.0" + "@esbuild/android-x64": "npm:0.24.0" + "@esbuild/darwin-arm64": "npm:0.24.0" + "@esbuild/darwin-x64": "npm:0.24.0" + "@esbuild/freebsd-arm64": "npm:0.24.0" + "@esbuild/freebsd-x64": "npm:0.24.0" + "@esbuild/linux-arm": "npm:0.24.0" + "@esbuild/linux-arm64": "npm:0.24.0" + "@esbuild/linux-ia32": "npm:0.24.0" + "@esbuild/linux-loong64": "npm:0.24.0" + "@esbuild/linux-mips64el": "npm:0.24.0" + "@esbuild/linux-ppc64": "npm:0.24.0" + "@esbuild/linux-riscv64": "npm:0.24.0" + "@esbuild/linux-s390x": "npm:0.24.0" + "@esbuild/linux-x64": "npm:0.24.0" + "@esbuild/netbsd-x64": "npm:0.24.0" + "@esbuild/openbsd-arm64": "npm:0.24.0" + "@esbuild/openbsd-x64": "npm:0.24.0" + "@esbuild/sunos-x64": "npm:0.24.0" + "@esbuild/win32-arm64": "npm:0.24.0" + "@esbuild/win32-ia32": "npm:0.24.0" + "@esbuild/win32-x64": "npm:0.24.0" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -2355,6 +2363,8 @@ __metadata: optional: true "@esbuild/netbsd-x64": optional: true + "@esbuild/openbsd-arm64": + optional: true "@esbuild/openbsd-x64": optional: true "@esbuild/sunos-x64": @@ -2367,7 +2377,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/d2ff2ca84d30cce8e871517374d6c2290835380dc7cd413b2d49189ed170d45e407be14de2cb4794cf76f75cf89955c4714726ebd3de7444b3046f5cab23ab6b + checksum: 10/500f83a1216d6548053007b85c070d8293395db344605b17418c6cf1217e5e8d338fa77fc8af27c23faa121c5528e5b0004d46d3a0cdeb87d48f1b5fa0164bc5 languageName: node linkType: hard @@ -3070,7 +3080,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.1.0": +"picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 @@ -3095,14 +3105,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.43": - version: 8.4.47 - resolution: "postcss@npm:8.4.47" +"postcss@npm:^8.4.49": + version: 8.4.49 + resolution: "postcss@npm:8.4.49" dependencies: nanoid: "npm:^3.3.7" - picocolors: "npm:^1.1.0" + picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10/f2b50ba9b6fcb795232b6bb20de7cdc538c0025989a8ed9c4438d1960196ba3b7eaff41fdb1a5c701b3504651ea87aeb685577707f0ae4d6ce6f3eae5df79a81 + checksum: 10/28fe1005b1339870e0a5006375ba5ac1213fd69800f79e7db09c398e074421ba6e162898e94f64942fed554037fd292db3811d87835d25ab5ef7f3c9daacb6ca languageName: node linkType: hard @@ -3121,12 +3131,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.3.3": - version: 3.3.3 - resolution: "prettier@npm:3.3.3" +"prettier@npm:^3.4.1": + version: 3.4.1 + resolution: "prettier@npm:3.4.1" bin: prettier: bin/prettier.cjs - checksum: 10/5beac1f30b5b40162532b8e2f7c3a4eb650910a2695e9c8512a62ffdc09dae93190c29db9107fa7f26d1b6c71aad3628ecb9b5de1ecb0911191099be109434d7 + checksum: 10/1ee4d1b1a9b6761cbb847cd81b9d87e51a0f4b2a4d5fe5755627c24828afe057a7ee9b764c3ee777d84abd46218d173d8a204ee9cb3acdd321ff9a6b25f99c1c languageName: node linkType: hard @@ -3238,28 +3248,28 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.20.0": - version: 4.24.4 - resolution: "rollup@npm:4.24.4" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.24.4" - "@rollup/rollup-android-arm64": "npm:4.24.4" - "@rollup/rollup-darwin-arm64": "npm:4.24.4" - "@rollup/rollup-darwin-x64": "npm:4.24.4" - "@rollup/rollup-freebsd-arm64": "npm:4.24.4" - "@rollup/rollup-freebsd-x64": "npm:4.24.4" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.24.4" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.24.4" - "@rollup/rollup-linux-arm64-gnu": "npm:4.24.4" - "@rollup/rollup-linux-arm64-musl": "npm:4.24.4" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.24.4" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.24.4" - "@rollup/rollup-linux-s390x-gnu": "npm:4.24.4" - "@rollup/rollup-linux-x64-gnu": "npm:4.24.4" - "@rollup/rollup-linux-x64-musl": "npm:4.24.4" - "@rollup/rollup-win32-arm64-msvc": "npm:4.24.4" - "@rollup/rollup-win32-ia32-msvc": "npm:4.24.4" - "@rollup/rollup-win32-x64-msvc": "npm:4.24.4" +"rollup@npm:^4.23.0": + version: 4.28.0 + resolution: "rollup@npm:4.28.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.28.0" + "@rollup/rollup-android-arm64": "npm:4.28.0" + "@rollup/rollup-darwin-arm64": "npm:4.28.0" + "@rollup/rollup-darwin-x64": "npm:4.28.0" + "@rollup/rollup-freebsd-arm64": "npm:4.28.0" + "@rollup/rollup-freebsd-x64": "npm:4.28.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.28.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.28.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.28.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.28.0" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.28.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.28.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.28.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.28.0" + "@rollup/rollup-linux-x64-musl": "npm:4.28.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.28.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.28.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.28.0" "@types/estree": "npm:1.0.6" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -3303,7 +3313,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10/a8ffde17d7cd5d9eaaf91bb025de83329b034771254d34b977df3f294e0992f6d89a444a0c8e9d73c8721d60cedf5be32fa8bd6f157874700bb8043c61ca660a + checksum: 10/e604ff8d866818fff5b15864eab09011b497117774de413a566c17bda5fc6878ea101b6124421e29d7c3478280b8e6a864b2b1f1c7c4422e7fe31cb6846a09fc languageName: node linkType: hard @@ -3641,30 +3651,30 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.6.3": - version: 5.6.3 - resolution: "typescript@npm:5.6.3" +"typescript@npm:^5.7.2": + version: 5.7.2 + resolution: "typescript@npm:5.7.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/c328e418e124b500908781d9f7b9b93cf08b66bf5936d94332b463822eea2f4e62973bfb3b8a745fdc038785cb66cf59d1092bac3ec2ac6a3e5854687f7833f1 + checksum: 10/4caa3904df69db9d4a8bedc31bafc1e19ffb7b24fbde2997a1633ae1398d0de5bdbf8daf602ccf3b23faddf1aeeb9b795223a2ed9c9a4fdcaf07bfde114a401a languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.6.3#optional!builtin": - version: 5.6.3 - resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=8c6c40" +"typescript@patch:typescript@npm%3A^5.7.2#optional!builtin": + version: 5.7.2 + resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin::version=5.7.2&hash=cef18b" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/00504c01ee42d470c23495426af07512e25e6546bce7e24572e72a9ca2e6b2e9bea63de4286c3cfea644874da1467dcfca23f4f98f7caf20f8b03c0213bb6837 + checksum: 10/ff27fc124bceb8969be722baa38af945b2505767cf794de3e2715e58f61b43780284060287d651fcbbdfb6f917f4653b20f4751991f17e0706db389b9bb3f75d languageName: node linkType: hard -"undici-types@npm:~6.19.8": - version: 6.19.8 - resolution: "undici-types@npm:6.19.8" - checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 +"undici-types@npm:~6.20.0": + version: 6.20.0 + resolution: "undici-types@npm:6.20.0" + checksum: 10/583ac7bbf4ff69931d3985f4762cde2690bb607844c16a5e2fbb92ed312fe4fa1b365e953032d469fa28ba8b224e88a595f0b10a449332f83fa77c695e567dbe languageName: node linkType: hard @@ -3795,29 +3805,34 @@ __metadata: languageName: node linkType: hard -"vite@npm:^5.4.11": - version: 5.4.11 - resolution: "vite@npm:5.4.11" +"vite@npm:^6.0.1": + version: 6.0.1 + resolution: "vite@npm:6.0.1" dependencies: - esbuild: "npm:^0.21.3" + esbuild: "npm:^0.24.0" fsevents: "npm:~2.3.3" - postcss: "npm:^8.4.43" - rollup: "npm:^4.20.0" + postcss: "npm:^8.4.49" + rollup: "npm:^4.23.0" peerDependencies: - "@types/node": ^18.0.0 || >=20.0.0 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" less: "*" lightningcss: ^1.21.0 sass: "*" sass-embedded: "*" stylus: "*" sugarss: "*" - terser: ^5.4.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 dependenciesMeta: fsevents: optional: true peerDependenciesMeta: "@types/node": optional: true + jiti: + optional: true less: optional: true lightningcss: @@ -3832,9 +3847,13 @@ __metadata: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true bin: vite: bin/vite.js - checksum: 10/719c4dea896e9547958643354003c8c9ea98e5367196d98f5f46cffb3ec963fead3ea5853f5af941c79bbfb73583dec19bbb0d28d2f644b95d7f59c55e22919d + checksum: 10/a42bf6e92425920335175b59d0e4d98cae8f2bfa473c1e32bd62e1feb4b808341992f92632d1883103351f8e9ec4ae53179266b0a815731e9c109a2e3768cde5 languageName: node linkType: hard diff --git a/bindings_wasm/CHANGELOG.md b/bindings_wasm/CHANGELOG.md index ae7da1394..7755ac0c9 100644 --- a/bindings_wasm/CHANGELOG.md +++ b/bindings_wasm/CHANGELOG.md @@ -1,5 +1,14 @@ # @xmtp/wasm-bindings +## 0.0.7 + +- Moved `verify_signed_with_public_key` out of `Client` + +## 0.0.6 + +- Added `installation_id_bytes` to `Client` +- Added `sign_with_installation_key`, `verify_signed_with_installation_key`, and `verify_signed_with_public_key` to `Client` + ## 0.0.5 - Filtered out group membership messages from DM groups diff --git a/bindings_wasm/Cargo.toml b/bindings_wasm/Cargo.toml index 65f79084b..0457f7a96 100644 --- a/bindings_wasm/Cargo.toml +++ b/bindings_wasm/Cargo.toml @@ -7,24 +7,24 @@ version.workspace = true crate-type = ["cdylib", "rlib"] [dependencies] +console_error_panic_hook.workspace = true hex.workspace = true js-sys.workspace = true prost.workspace = true serde-wasm-bindgen = "0.6.5" serde.workspace = true tokio.workspace = true +tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } +tracing-web = "0.1" +tracing.workspace = true wasm-bindgen-futures.workspace = true wasm-bindgen.workspace = true xmtp_api_http = { path = "../xmtp_api_http" } +xmtp_common.workspace = true xmtp_cryptography = { path = "../xmtp_cryptography" } xmtp_id = { path = "../xmtp_id" } xmtp_mls = { path = "../xmtp_mls", features = ["test-utils", "http-api"] } xmtp_proto = { path = "../xmtp_proto", features = ["proto_full"] } -tracing-web = "0.1" -tracing.workspace = true -tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } -console_error_panic_hook.workspace = true [dev-dependencies] wasm-bindgen-test.workspace = true -xmtp_mls = { path = "../xmtp_mls", features = ["test-utils", "http-api"] } diff --git a/bindings_wasm/package.json b/bindings_wasm/package.json index e799e8cf2..014358de6 100644 --- a/bindings_wasm/package.json +++ b/bindings_wasm/package.json @@ -1,6 +1,6 @@ { "name": "@xmtp/wasm-bindings", - "version": "0.0.5", + "version": "0.0.7", "type": "module", "license": "MIT", "description": "WASM bindings for the libXMTP rust library", diff --git a/bindings_wasm/src/client.rs b/bindings_wasm/src/client.rs index 7b3711d79..f00088619 100644 --- a/bindings_wasm/src/client.rs +++ b/bindings_wasm/src/client.rs @@ -12,7 +12,6 @@ use xmtp_api_http::XmtpHttpApiClient; use xmtp_cryptography::signature::ed25519_public_key_to_address; use xmtp_id::associations::builder::SignatureRequest; use xmtp_mls::builder::ClientBuilder; -use xmtp_mls::groups::scoped_client::ScopedGroupClient; use xmtp_mls::identity::IdentityStrategy; use xmtp_mls::storage::{EncryptedMessageStore, EncryptionKey, StorageOption}; use xmtp_mls::Client as MlsClient; @@ -124,7 +123,7 @@ pub async fn create_client( host: String, inbox_id: String, account_address: String, - db_path: String, + db_path: Option, encryption_key: Option, history_sync_url: Option, log_options: Option, @@ -133,7 +132,10 @@ pub async fn create_client( xmtp_mls::storage::init_sqlite().await; let api_client = XmtpHttpApiClient::new(host.clone()).unwrap(); - let storage_option = StorageOption::Persistent(db_path); + let storage_option = match db_path { + Some(path) => StorageOption::Persistent(path), + None => StorageOption::Ephemeral, + }; let store = match encryption_key { Some(key) => { @@ -150,7 +152,7 @@ pub async fn create_client( .map_err(|_| JsError::new("Error creating unencrypted message store"))?, }; - let identity_strategy = IdentityStrategy::CreateIfNotFound( + let identity_strategy = IdentityStrategy::new( inbox_id.clone(), account_address.clone().to_lowercase(), // this is a temporary solution @@ -203,6 +205,11 @@ impl Client { ed25519_public_key_to_address(self.inner_client.installation_public_key().as_slice()) } + #[wasm_bindgen(getter, js_name = installationIdBytes)] + pub fn installation_id_bytes(&self) -> Uint8Array { + Uint8Array::from(self.inner_client.installation_public_key().as_slice()) + } + #[wasm_bindgen(js_name = canMessage)] pub async fn can_message(&self, account_addresses: Vec) -> Result { let results: HashMap = self @@ -265,9 +272,14 @@ impl Client { #[wasm_bindgen(js_name = findInboxIdByAddress)] pub async fn find_inbox_id_by_address(&self, address: String) -> Result, JsError> { + let conn = self + .inner_client + .store() + .conn() + .map_err(|e| JsError::new(format!("{}", e).as_str()))?; let inbox_id = self .inner_client - .find_inbox_id_from_address(address) + .find_inbox_id_from_address(&conn, address) .await .map_err(|e| JsError::new(format!("{}", e).as_str()))?; diff --git a/bindings_wasm/src/conversations.rs b/bindings_wasm/src/conversations.rs index 07931f5b4..50f0790a2 100644 --- a/bindings_wasm/src/conversations.rs +++ b/bindings_wasm/src/conversations.rs @@ -269,14 +269,13 @@ impl Conversations { #[wasm_bindgen] pub async fn sync(&self) -> Result<(), JsError> { - let conn = self + let provider = self .inner_client - .store() - .conn() + .mls_provider() .map_err(|e| JsError::new(format!("{}", e).as_str()))?; self .inner_client - .sync_welcomes(&conn) + .sync_welcomes(&provider) .await .map_err(|e| JsError::new(format!("{}", e).as_str()))?; @@ -285,11 +284,17 @@ impl Conversations { #[wasm_bindgen(js_name = syncAllConversations)] pub async fn sync_all_conversations(&self) -> Result { - let groups = self + let provider = self .inner_client - .find_groups(GroupQueryArgs::default()) + .mls_provider() .map_err(|e| JsError::new(format!("{}", e).as_str()))?; - let num_groups_synced = self.inner_client.sync_all_groups(groups).await?; + + let num_groups_synced = self + .inner_client + .sync_all_welcomes_and_groups(&provider, None) + .await + .map_err(|e| JsError::new(format!("{}", e).as_str()))?; + Ok(num_groups_synced) } diff --git a/bindings_wasm/src/inbox_id.rs b/bindings_wasm/src/inbox_id.rs index 1aec357c4..d0b046f3d 100644 --- a/bindings_wasm/src/inbox_id.rs +++ b/bindings_wasm/src/inbox_id.rs @@ -1,8 +1,8 @@ use wasm_bindgen::prelude::{wasm_bindgen, JsError}; use xmtp_api_http::XmtpHttpApiClient; +use xmtp_common::retry::Retry; use xmtp_id::associations::generate_inbox_id as xmtp_id_generate_inbox_id; use xmtp_mls::api::ApiClientWrapper; -use xmtp_mls::retry::Retry; #[wasm_bindgen(js_name = getInboxIdForAddress)] pub async fn get_inbox_id_for_address( diff --git a/bindings_wasm/src/signatures.rs b/bindings_wasm/src/signatures.rs index 7c9f27ac7..9a660b947 100644 --- a/bindings_wasm/src/signatures.rs +++ b/bindings_wasm/src/signatures.rs @@ -1,5 +1,6 @@ use js_sys::Uint8Array; use wasm_bindgen::prelude::{wasm_bindgen, JsError}; +use xmtp_id::associations::verify_signed_with_public_context; use xmtp_id::associations::{ unverified::{NewUnverifiedSmartContractWalletSignature, UnverifiedSignature}, AccountId, @@ -7,6 +8,26 @@ use xmtp_id::associations::{ use crate::client::Client; +#[wasm_bindgen(js_name = verifySignedWithPublicKey)] +pub fn verify_signed_with_public_key( + signature_text: String, + signature_bytes: Uint8Array, + public_key: Uint8Array, +) -> Result<(), JsError> { + let signature_bytes = signature_bytes.to_vec(); + let signature_bytes: [u8; 64] = signature_bytes + .try_into() + .map_err(|_| JsError::new("signature_bytes is not 64 bytes long."))?; + + let public_key = public_key.to_vec(); + let public_key: [u8; 32] = public_key + .try_into() + .map_err(|_| JsError::new("public_key is not 32 bytes long."))?; + + verify_signed_with_public_context(signature_text, &signature_bytes, &public_key) + .map_err(|e| JsError::new(format!("{}", e).as_str())) +} + #[wasm_bindgen] #[derive(Clone, Eq, Hash, PartialEq)] pub enum SignatureRequestType { @@ -36,15 +57,12 @@ impl Client { #[wasm_bindgen(js_name = addWalletSignatureText)] pub async fn add_wallet_signature_text( &self, - existing_wallet_address: String, new_wallet_address: String, ) -> Result { let signature_request = self .inner_client() - .associate_wallet( - existing_wallet_address.to_lowercase(), - new_wallet_address.to_lowercase(), - ) + .associate_wallet(new_wallet_address.to_lowercase()) + .await .map_err(|e| JsError::new(format!("{}", e).as_str()))?; let signature_text = signature_request.signature_text(); let mut signature_requests = self.signature_requests().lock().await; @@ -83,7 +101,7 @@ impl Client { let other_installation_ids = inbox_state .installation_ids() .into_iter() - .filter(|id| id != &installation_id) + .filter(|id| id != installation_id) .collect(); let signature_request = self .inner_client() @@ -175,4 +193,29 @@ impl Client { Ok(()) } + + #[wasm_bindgen(js_name = signWithInstallationKey)] + pub fn sign_with_installation_key(&self, signature_text: String) -> Result { + let result = self + .inner_client() + .context() + .sign_with_public_context(signature_text) + .map_err(|e| JsError::new(format!("{}", e).as_str()))?; + + Ok(Uint8Array::from(result.as_slice())) + } + + #[wasm_bindgen(js_name = verifySignedWithInstallationKey)] + pub fn verify_signed_with_installation_key( + &self, + signature_text: String, + signature_bytes: Uint8Array, + ) -> Result<(), JsError> { + let public_key = self.inner_client().installation_public_key(); + verify_signed_with_public_key( + signature_text, + signature_bytes, + Uint8Array::from(public_key.as_slice()), + ) + } } diff --git a/bindings_wasm/tests/web.rs b/bindings_wasm/tests/web.rs index 32f6d02d6..afc323617 100644 --- a/bindings_wasm/tests/web.rs +++ b/bindings_wasm/tests/web.rs @@ -26,7 +26,7 @@ pub async fn test_create_client() { host.clone(), inbox_id.unwrap(), account_address.clone(), - "test".to_string(), + None, None, None, Some(LogOptions { diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 000000000..76b836916 --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "xmtp_common" +edition = "2021" +version.workspace = true +license.workspace = true + +[dependencies] +web-time.workspace = true +tracing.workspace = true +tokio = { workspace = true, features = ["time"] } +rand = "0.8" +futures.workspace = true +xmtp_cryptography.workspace = true + +parking_lot = { workspace = true, optional = true } +tracing-subscriber = { workspace = true, features = ["fmt", "env-filter", "ansi", "json"], optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true, features = ["js"] } +gloo-timers = { workspace = true, features = ["futures"] } +tracing-wasm = { version = "0.2", optional = true } +console_error_panic_hook = { version = "0.1", optional = true } +js-sys.workspace = true +web-sys = { workspace = true, features = ["Window"] } +wasm-bindgen-futures.workspace = true + +[dev-dependencies] +thiserror.workspace = true + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +tokio = { workspace = true, features = ["time", "macros", "rt", "sync"]} +wasm-bindgen-test.workspace = true + +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +tokio = { workspace = true, features = ["time", "macros", "rt-multi-thread", "sync"]} + +[features] +test-utils = ["dep:parking_lot", "dep:tracing-subscriber", "dep:tracing-wasm", "dep:console_error_panic_hook"] diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 000000000..a198962ad --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,47 @@ +//! Common types shared among all XMTP Crates + +mod macros; + +#[cfg(feature = "test-utils")] +mod test; +#[cfg(feature = "test-utils")] +pub use test::*; + +pub mod retry; +pub use retry::*; + +/// Global Marker trait for WebAssembly +#[cfg(target_arch = "wasm32")] +pub trait Wasm {} +#[cfg(target_arch = "wasm32")] +impl Wasm for T {} + +pub mod time; + +use rand::{ + distributions::{Alphanumeric, DistString}, + RngCore, +}; +use xmtp_cryptography::utils as crypto_utils; + +pub fn rand_string() -> String { + Alphanumeric.sample_string(&mut crypto_utils::rng(), N) +} + +pub fn rand_array() -> [u8; N] { + let mut buffer = [0u8; N]; + crypto_utils::rng().fill_bytes(&mut buffer); + buffer +} + +/// Yield back control to the async runtime +#[cfg(not(target_arch = "wasm32"))] +pub async fn yield_() { + tokio::task::yield_now().await +} + +/// Yield back control to the async runtime +#[cfg(target_arch = "wasm32")] +pub async fn yield_() { + time::sleep(crate::time::Duration::from_millis(1)).await; +} diff --git a/common/src/macros.rs b/common/src/macros.rs new file mode 100644 index 000000000..9ebf42790 --- /dev/null +++ b/common/src/macros.rs @@ -0,0 +1,24 @@ +/// Turn the `Result` into an `Option`, logging the error with `tracing::error` and +/// returning `None` if the value matches on Result::Err(). +/// Optionally pass a message as the second argument. +#[macro_export] +macro_rules! optify { + ( $e: expr ) => { + match $e { + Ok(v) => Some(v), + Err(e) => { + tracing::error!("{}", e); + None + } + } + }; + ( $e: expr, $msg: tt ) => { + match $e { + Ok(v) => Some(v), + Err(e) => { + tracing::error!("{}: {:?}", $msg, e); + None + } + } + }; +} diff --git a/xmtp_mls/src/retry.rs b/common/src/retry.rs similarity index 94% rename from xmtp_mls/src/retry.rs rename to common/src/retry.rs index 6e5aca993..3088d1dc6 100644 --- a/xmtp_mls/src/retry.rs +++ b/common/src/retry.rs @@ -18,9 +18,11 @@ use rand::Rng; +pub struct NotSpecialized; + /// Specifies which errors are retryable. /// All Errors are not retryable by-default. -pub trait RetryableError: std::error::Error { +pub trait RetryableError: std::error::Error { fn is_retryable(&self) -> bool; } @@ -76,7 +78,7 @@ pub struct RetryBuilder { /// /// # Example /// ``` -/// use xmtp_mls::retry::RetryBuilder; +/// use xmtp_common::retry::RetryBuilder; /// /// RetryBuilder::default() /// .retries(5) @@ -121,7 +123,7 @@ impl Retry { /// Retry but for an async context /// ``` -/// use xmtp_mls::{retry_async, retry::{RetryableError, Retry}}; +/// use xmtp_common::{retry_async, retry::{RetryableError, Retry}}; /// use thiserror::Error; /// use tokio::sync::mpsc; /// @@ -183,7 +185,7 @@ macro_rules! retry_async { e.to_string() ); attempts += 1; - $crate::sleep($retry.duration(attempts)).await; + $crate::time::sleep($retry.duration(attempts)).await; } else { tracing::info!("error is not retryable. {:?}:{}", e, e); break Err(e); @@ -207,13 +209,6 @@ macro_rules! retryable { }}; } -// network errors should generally be retryable, unless there's a bug in our code -impl RetryableError for xmtp_proto::Error { - fn is_retryable(&self) -> bool { - true - } -} - #[cfg(test)] pub(crate) mod tests { #[cfg(target_arch = "wasm32")] @@ -313,7 +308,7 @@ pub(crate) mod tests { return Ok(()); } // do some work - crate::sleep(core::time::Duration::from_nanos(100)).await; + crate::time::sleep(core::time::Duration::from_nanos(100)).await; Err(SomeError::ARetryableError) } @@ -339,7 +334,7 @@ pub(crate) mod tests { } *data += 1; // do some work - crate::sleep(core::time::Duration::from_nanos(100)).await; + crate::time::sleep(core::time::Duration::from_nanos(100)).await; Err(SomeError::ARetryableError) } diff --git a/common/src/test.rs b/common/src/test.rs new file mode 100644 index 000000000..c11c692cb --- /dev/null +++ b/common/src/test.rs @@ -0,0 +1,183 @@ +//! Common Test Utilites +use rand::{ + distributions::{Alphanumeric, DistString}, + seq::IteratorRandom, + Rng, +}; +use std::{future::Future, sync::OnceLock}; +use xmtp_cryptography::utils as crypto_utils; + +#[cfg(not(target_arch = "wasm32"))] +pub mod traced_test; +#[cfg(not(target_arch = "wasm32"))] +pub use traced_test::TestWriter; + +use crate::time::Expired; + +mod macros; + +static INIT: OnceLock<()> = OnceLock::new(); + +/// A simple test logger that defaults to the INFO level +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] +pub fn logger() { + use tracing_subscriber::{ + fmt::{self, format}, + layer::SubscriberExt, + util::SubscriberInitExt, + EnvFilter, Layer, + }; + + INIT.get_or_init(|| { + let structured = std::env::var("STRUCTURED"); + let is_structured = matches!(structured, Ok(s) if s == "true" || s == "1"); + + let filter = || { + EnvFilter::builder() + .with_default_directive(tracing::metadata::LevelFilter::INFO.into()) + .from_env_lossy() + }; + + tracing_subscriber::registry() + // structured JSON logger only if STRUCTURED=true + .with(is_structured.then(|| { + tracing_subscriber::fmt::layer() + .json() + .flatten_event(true) + .with_level(true) + .with_filter(filter()) + })) + // default logger + .with((!is_structured).then(|| { + fmt::layer() + .compact() + .fmt_fields({ + format::debug_fn(move |writer, field, value| { + if field.name() == "message" { + write!(writer, "{:?}", value)?; + } + Ok(()) + }) + }) + .with_filter(filter()) + })) + .init(); + }); +} + +/// A simple test logger that defaults to the INFO level +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +pub fn logger() { + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + use tracing_subscriber::EnvFilter; + + INIT.get_or_init(|| { + let filter = EnvFilter::builder() + .with_default_directive(tracing::metadata::LevelFilter::DEBUG.into()) + .from_env_lossy(); + + tracing_subscriber::registry() + .with(tracing_wasm::WASMLayer::default()) + .with(filter) + .init(); + + console_error_panic_hook::set_once(); + }); +} + +pub fn rand_hexstring() -> String { + let mut rng = crypto_utils::rng(); + let hex_chars = "0123456789abcdef"; + let v: String = (0..40) + .map(|_| hex_chars.chars().choose(&mut rng).unwrap()) + .collect(); + + format!("0x{}", v) +} + +pub fn rand_account_address() -> String { + Alphanumeric.sample_string(&mut crypto_utils::rng(), 42) +} + +pub fn rand_vec() -> Vec { + crate::rand_array::().to_vec() +} + +pub fn rand_u64() -> u64 { + crypto_utils::rng().gen() +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn tmp_path() -> String { + let db_name = crate::rand_string::<24>(); + format!("{}/{}.db3", std::env::temp_dir().to_str().unwrap(), db_name) +} + +#[cfg(target_arch = "wasm32")] +pub fn tmp_path() -> String { + let db_name = crate::rand_string::<24>(); + format!("{}/{}.db3", "test_db", db_name) +} + +pub fn rand_time() -> i64 { + let mut rng = rand::thread_rng(); + rng.gen_range(0..1_000_000_000) +} + +pub async fn wait_for_some(f: F) -> Option +where + F: Fn() -> Fut, + Fut: Future>, +{ + crate::time::timeout(crate::time::Duration::from_secs(20), async { + loop { + if let Some(r) = f().await { + return r; + } else { + crate::yield_().await; + } + } + }) + .await + .ok() +} + +pub async fn wait_for_ok(f: F) -> Result +where + F: Fn() -> Fut, + Fut: Future>, +{ + crate::time::timeout(crate::time::Duration::from_secs(20), async { + loop { + if let Ok(r) = f().await { + return r; + } else { + crate::yield_().await; + } + } + }) + .await +} + +pub async fn wait_for_eq(f: F, expected: T) -> Result<(), Expired> +where + F: Fn() -> Fut, + Fut: Future, + T: std::fmt::Debug + PartialEq, +{ + let result = crate::time::timeout(crate::time::Duration::from_secs(20), async { + loop { + let result = f().await; + if expected == result { + return result; + } else { + crate::yield_().await; + } + } + }) + .await?; + + assert_eq!(expected, result); + Ok(()) +} diff --git a/common/src/test/macros.rs b/common/src/test/macros.rs new file mode 100644 index 000000000..e07087d78 --- /dev/null +++ b/common/src/test/macros.rs @@ -0,0 +1,49 @@ +/// wrapper over assert!(matches!()) for Errors +/// assert_err!(fun(), StorageError::Explosion) +/// +/// or the message variant, +/// assert_err!(fun(), StorageError::Explosion, "the storage did not explode"); +#[macro_export] +macro_rules! assert_err { + ( $x:expr , $y:pat $(,)? ) => { + assert!(matches!($x, Err($y))) + }; + + ( $x:expr, $y:pat $(,)?, $($msg:tt)+) => {{ + assert!(matches!($x, Err($y)), $($msg)+) + }} + } + +/// wrapper over assert! macros for Ok's +/// +/// Make sure something is Ok(_) without caring about return value. +/// assert_ok!(fun()); +/// +/// Against an expected value, e.g Ok(true) +/// assert_ok!(fun(), true); +/// +/// or the message variant, +/// assert_ok!(fun(), Ok(_), "the storage is not ok"); +#[macro_export] +macro_rules! assert_ok { + + ( $e:expr ) => { + assert_ok!($e,) + }; + + ( $e:expr, ) => {{ + use std::result::Result::*; + match $e { + Ok(v) => v, + Err(e) => panic!("assertion failed: Err({:?})", e), + } + }}; + + ( $x:expr , $y:expr $(,)? ) => { + assert_eq!($x, Ok($y.into())); + }; + + ( $x:expr, $y:expr $(,)?, $($msg:tt)+) => {{ + assert_eq!($x, Ok($y.into()), $($msg)+); + }} + } diff --git a/xmtp_mls/src/utils/test/traced_test.rs b/common/src/test/traced_test.rs similarity index 72% rename from xmtp_mls/src/utils/test/traced_test.rs rename to common/src/test/traced_test.rs index fea217199..eb208d220 100644 --- a/xmtp_mls/src/utils/test/traced_test.rs +++ b/common/src/test/traced_test.rs @@ -1,3 +1,4 @@ +/// Tests that can assert on tracing logs in a tokio threaded context use std::{io, sync::Arc}; use parking_lot::Mutex; @@ -58,8 +59,9 @@ impl fmt::MakeWriter<'_> for TestWriter { self.clone() } } - +/* /// Only works with current-thread +#[inline] pub fn traced_test(f: impl Fn() -> Fut) where Fut: futures::Future, @@ -88,6 +90,39 @@ where buf.clear(); }); } +*/ + +#[macro_export] +macro_rules! traced_test { + ( $f:expr ) => {{ + use tracing_subscriber::fmt; + use $crate::traced_test::TestWriter; + + $crate::traced_test::LOG_BUFFER.with(|buf| { + let rt = tokio::runtime::Builder::new_current_thread() + .thread_name("tracing-test") + .enable_time() + .enable_io() + .build() + .unwrap(); + buf.clear(); + + let subscriber = fmt::Subscriber::builder() + .with_env_filter(format!("{}=debug", env!("CARGO_PKG_NAME"))) + .with_writer(buf.clone()) + .with_level(true) + .with_ansi(false) + .finish(); + + let dispatch = tracing::Dispatch::new(subscriber); + tracing::dispatcher::with_default(&dispatch, || { + rt.block_on($f); + }); + + buf.clear(); + }); + }}; +} /// macro that can assert logs in tests. /// Note: tests that use this must be used in `traced_test` function @@ -95,7 +130,7 @@ where #[macro_export] macro_rules! assert_logged { ( $search:expr , $occurrences:expr ) => { - $crate::utils::test::traced_test::LOG_BUFFER.with(|buf| { + $crate::traced_test::LOG_BUFFER.with(|buf| { let lines = { buf.flush(); buf.as_string() diff --git a/common/src/time.rs b/common/src/time.rs new file mode 100644 index 000000000..8b411a826 --- /dev/null +++ b/common/src/time.rs @@ -0,0 +1,99 @@ +//! Time primitives for native and WebAssembly + +use std::fmt; + +#[derive(Debug)] +pub struct Expired; + +impl std::error::Error for Expired { + fn description(&self) -> &str { + "Timer duration expired" + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for Expired { + fn from(_: tokio::time::error::Elapsed) -> Expired { + Expired + } +} + +impl fmt::Display for Expired { + fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { + write!(f, "timer duration expired") + } +} + +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] +pub use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +pub use web_time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +fn duration_since_epoch() -> Duration { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") +} + +pub fn now_ns() -> i64 { + duration_since_epoch().as_nanos() as i64 +} + +pub fn now_secs() -> i64 { + duration_since_epoch().as_secs() as i64 +} + +#[cfg(target_arch = "wasm32")] +pub async fn timeout(duration: Duration, future: F) -> Result +where + F: std::future::IntoFuture, +{ + use futures::future::Either::*; + let timeout = gloo_timers::future::TimeoutFuture::new(duration.as_millis() as u32); + let future = future.into_future(); + futures::pin_mut!(future); + match futures::future::select(timeout, future).await { + Left(_) => Err(Expired), + Right((value, _)) => Ok(value), + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub async fn timeout(duration: Duration, future: F) -> Result +where + F: std::future::IntoFuture, +{ + tokio::time::timeout(duration, future) + .await + .map_err(Into::into) +} + +// WASM Shims +#[cfg(target_arch = "wasm32")] +#[doc(hidden)] +pub async fn sleep(duration: Duration) { + use js_sys::wasm_bindgen::JsCast; + use js_sys::wasm_bindgen::UnwrapThrowExt; + use web_sys::WorkerGlobalScope; + + let mut cb = |resolve: js_sys::Function, _reject: js_sys::Function| { + let worker = js_sys::global() + .dyn_into::() + .expect("xmtp_mls should always act in worker in browser"); + + worker + .set_timeout_with_callback_and_timeout_and_arguments_0( + &resolve, + duration.as_millis() as i32, + ) + .expect("Failed to call set_timeout"); + }; + let p = js_sys::Promise::new(&mut cb); + wasm_bindgen_futures::JsFuture::from(p).await.unwrap_throw(); +} + +#[cfg(not(target_arch = "wasm32"))] +#[doc(hidden)] +pub async fn sleep(duration: Duration) { + tokio::time::sleep(duration).await +} diff --git a/dev/bench b/dev/bench index c4ec1e38e..b7b8f89c7 100755 --- a/dev/bench +++ b/dev/bench @@ -2,7 +2,9 @@ set -eou pipefail if [[ -z "${1-}" ]]; then - cargo bench --no-fail-fast --features bench + cargo bench --no-fail-fast --features bench -p xmtp_mls else - cargo bench --no-fail-fast --features bench -- "$1" + cargo bench --no-fail-fast --features bench -p xmtp_mls -- "$1" fi + +echo "Open benchmarks at target/criterion/report.html" diff --git a/dev/flamegraph b/dev/flamegraph index 9dbedbc26..872257321 100755 --- a/dev/flamegraph +++ b/dev/flamegraph @@ -7,9 +7,9 @@ if [[ "${OSTYPE}" == "darwin"* ]]; then fi if [[ -z "${1-}" ]]; then - XMTP_FLAMEGRAPH=trace cargo bench --no-fail-fast --features bench + XMTP_FLAMEGRAPH=trace cargo bench --no-fail-fast --features bench -p xmtp_mls else - XMTP_FLAMEGRAPH=trace cargo bench --no-fail-fast --features bench -- "$1" + XMTP_FLAMEGRAPH=trace cargo bench --no-fail-fast --features bench -p xmtp_mls -- "$1" fi -inferno-flamegraph > tracing-flamegraph.svg +cat xmtp_mls/tracing.foldeed | inferno-flamegraph > tracing-flamegraph.svg diff --git a/dev/release-kotlin b/dev/release-kotlin index 5020f8296..3580e9d32 100755 --- a/dev/release-kotlin +++ b/dev/release-kotlin @@ -42,6 +42,7 @@ nix develop .#android --command cargo ndk -o bindings_ffi/jniLibs/ --manifest-pa -t armv7-linux-androideabi \ -- build --release + for arch in arm64-v8a armeabi-v7a x86 x86_64; do mv "./bindings_ffi/jniLibs/$arch/$LIBRARY_NAME.so" "./bindings_ffi/jniLibs/$arch/$TARGET_NAME.so" done diff --git a/dev/wasm b/dev/wasm deleted file mode 100755 index 717bdf8b4..000000000 --- a/dev/wasm +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -eou pipefail - -if [[ "${OSTYPE}" == "darwin"* ]]; then - if ! wasm-pack --version &>/dev/null; then cargo install wasm-pack; fi -fi - -pushd bindings_wasm/ > /dev/null - -wasm-pack build --target nodejs --out-dir pkg # pkg is the default - -popd > /dev/null diff --git a/examples/cli/Cargo.toml b/examples/cli/Cargo.toml index 9d837d9a1..cff9a9200 100644 --- a/examples/cli/Cargo.toml +++ b/examples/cli/Cargo.toml @@ -39,6 +39,8 @@ valuable = { version = "0.1", features = ["derive"] } valuable-serde = "0.1" xmtp_api_grpc = { path = "../../xmtp_api_grpc" } xmtp_cryptography = { path = "../../xmtp_cryptography" } +xmtp_content_types = { path = "../../xmtp_content_types" } xmtp_id = { path = "../../xmtp_id" } xmtp_mls = { path = "../../xmtp_mls" } xmtp_proto = { path = "../../xmtp_proto", features = ["proto_full"] } +xmtp_common.workspace = true diff --git a/examples/cli/cli-client.rs b/examples/cli/cli-client.rs index 0448e03b4..a30060380 100755 --- a/examples/cli/cli-client.rs +++ b/examples/cli/cli-client.rs @@ -32,6 +32,8 @@ use tracing_subscriber::{ use valuable::Valuable; use xmtp_api_grpc::grpc_api_helper::Client as ClientV3; use xmtp_api_grpc::replication_client::ClientV4; +use xmtp_common::time::now_ns; +use xmtp_content_types::{text::TextCodec, ContentCodec}; use xmtp_cryptography::{ signature::{RecoverableSignature, SignatureError}, utils::rng, @@ -47,14 +49,12 @@ use xmtp_mls::XmtpApi; use xmtp_mls::{ builder::ClientBuilderError, client::ClientError, - codecs::{text::TextCodec, ContentCodec}, groups::{device_sync::MessageHistoryUrls, GroupMetadataOptions}, identity::IdentityStrategy, storage::{ group_message::StoredGroupMessage, EncryptedMessageStore, EncryptionKey, StorageError, StorageOption, }, - utils::time::now_ns, InboxOwner, }; use xmtp_proto::xmtp::mls::message_contents::DeviceSyncKind; @@ -235,21 +235,29 @@ async fn main() -> color_eyre::eyre::Result<()> { info!("Starting CLI Client...."); let grpc: Box = match (cli.testnet, &cli.env) { - (true, Env::Local) => { - Box::new(ClientV4::create("http://localhost:5050".into(), false).await?) - } - (true, Env::Dev) => { - Box::new(ClientV4::create("https://grpc.testnet.xmtp.network:443".into(), true).await?) - } - (false, Env::Local) => { - Box::new(ClientV3::create("http://localhost:5556".into(), false).await?) - } + (true, Env::Local) => Box::new( + ClientV4::create( + "http://localhost:5050".into(), + "http://localhost:5050".into(), + false, + ) + .await?, + ), + (true, Env::Dev) => Box::new( + ClientV4::create( + "https://grpc.testnet.xmtp.network:443".into(), + "https://payer.testnet.xmtp.network:443".into(), + true, + ) + .await?, + ), + (false, Env::Local) => Box::new(ClientV3::create("http://localhost:5556", false).await?), (false, Env::Dev) => { - Box::new(ClientV3::create("https://grpc.dev.xmtp.network:443".into(), true).await?) + Box::new(ClientV3::create("https://grpc.dev.xmtp.network:443", true).await?) + } + (false, Env::Production) => { + Box::new(ClientV3::create("https://grpc.production.xmtp.network:443", true).await?) } - (false, Env::Production) => Box::new( - ClientV3::create("https://grpc.production.xmtp.network:443".into(), true).await?, - ), (true, Env::Production) => todo!("not supported"), }; @@ -282,9 +290,9 @@ async fn main() -> color_eyre::eyre::Result<()> { } Commands::ListGroups {} => { info!("List Groups"); - let conn = client.store().conn()?; + let provider = client.mls_provider()?; client - .sync_welcomes(&conn) + .sync_welcomes(&provider) .await .expect("failed to sync welcomes"); @@ -432,10 +440,9 @@ async fn main() -> color_eyre::eyre::Result<()> { ); } Commands::RequestHistorySync {} => { - let conn = client.store().conn().unwrap(); let provider = client.mls_provider().unwrap(); - client.sync_welcomes(&conn).await.unwrap(); - client.start_sync_worker(&provider).await.unwrap(); + client.sync_welcomes(&provider).await.unwrap(); + client.start_sync_worker(); client .send_sync_request(&provider, DeviceSyncKind::MessageHistory) .await @@ -443,9 +450,9 @@ async fn main() -> color_eyre::eyre::Result<()> { info!("Sent history sync request in sync group.") } Commands::ListHistorySyncMessages {} => { - let conn = client.store().conn()?; - client.sync_welcomes(&conn).await?; - let group = client.get_sync_group()?; + let provider = client.mls_provider()?; + client.sync_welcomes(&provider).await?; + let group = client.get_sync_group(provider.conn_ref())?; let group_id_str = hex::encode(group.group_id.clone()); group.sync().await?; let messages = group @@ -535,7 +542,7 @@ where let inbox_id = generate_inbox_id(&w.get_address(), &nonce)?; let client = create_client( cli, - IdentityStrategy::CreateIfNotFound(inbox_id, w.get_address(), nonce, None), + IdentityStrategy::new(inbox_id, w.get_address(), nonce, None), client, ) .await?; @@ -566,8 +573,8 @@ where } async fn get_group(client: &Client, group_id: Vec) -> Result { - let conn = client.store().conn().unwrap(); - client.sync_welcomes(&conn).await?; + let provider = client.mls_provider().unwrap(); + client.sync_welcomes(&provider).await?; let group = client.group(group_id)?; group .sync() diff --git a/examples/cli/debug.rs b/examples/cli/debug.rs index 80311e7ff..c177911dc 100644 --- a/examples/cli/debug.rs +++ b/examples/cli/debug.rs @@ -71,7 +71,7 @@ pub async fn debug_welcome_messages( ) -> Result<(), String> { let api_client = client.api(); let envelopes = api_client - .query_welcome_messages(installation_id, None) + .query_welcome_messages(&installation_id, None) .await .unwrap(); for envelope in envelopes { diff --git a/examples/cli/serializable.rs b/examples/cli/serializable.rs index cbc3190a6..c6ee793ce 100644 --- a/examples/cli/serializable.rs +++ b/examples/cli/serializable.rs @@ -1,12 +1,8 @@ use prost::Message; use serde::Serialize; use valuable::Valuable; -use xmtp_mls::{ - codecs::{text::TextCodec, ContentCodec}, - groups::MlsGroup, - storage::group_message::StoredGroupMessage, - XmtpApi, -}; +use xmtp_content_types::{text::TextCodec, ContentCodec}; +use xmtp_mls::{groups::MlsGroup, storage::group_message::StoredGroupMessage, XmtpApi}; use xmtp_proto::xmtp::mls::message_contents::EncodedContent; #[derive(Serialize, Debug, Valuable)] diff --git a/flake.nix b/flake.nix index d3d0a2c36..b5b97d1e2 100644 --- a/flake.nix +++ b/flake.nix @@ -25,10 +25,22 @@ perSystem = { pkgs, lib, inputs', system, ... }: let fenixPkgs = inputs'.fenix.packages; - rust-toolchain = fenixPkgs.fromToolchainFile { - file = ./rust-toolchain; - sha256 = "sha256-s1RPtyvDGJaX/BisLT+ifVfuhDT1nZkZ1NcK8sbwELM="; - }; + androidTargets = [ + "aarch64-linux-android" + "armv7-linux-androideabi" + "x86_64-linux-android" + "i686-linux-android" + ]; + + # Pinned Rust Version + rust-toolchain = with fenix.packages.${system}; combine [ + stable.cargo + stable.rustc + (pkgs.lib.forEach + androidTargets + (target: targets."${target}".stable.rust-std)) + targets.x86_64-unknown-linux-musl.stable.rust-std + ]; pkgConfig = { inherit system; @@ -56,6 +68,8 @@ (commonCargoSources ./xmtp_proto) (commonCargoSources ./xmtp_v2) (commonCargoSources ./xmtp_user_preferences) + (commonCargoSources ./common) + (commonCargoSources ./xmtp_content_types) ./xmtp_id/src/scw_verifier/chain_urls_default.json ./xmtp_id/artifact ./xmtp_mls/migrations diff --git a/mls_validation_service/Cargo.toml b/mls_validation_service/Cargo.toml index fd6e37edd..e5aa6dbb7 100644 --- a/mls_validation_service/Cargo.toml +++ b/mls_validation_service/Cargo.toml @@ -36,6 +36,7 @@ ethers.workspace = true rand = { workspace = true } xmtp_id = { workspace = true, features = ["test-utils"] } xmtp_mls = { workspace = true, features = ["test-utils"] } +xmtp_common = { workspace = true, features = ["test-utils"]} [features] test-utils = ["xmtp_id/test-utils"] diff --git a/mls_validation_service/src/handlers.rs b/mls_validation_service/src/handlers.rs index ef96904dd..abf159685 100644 --- a/mls_validation_service/src/handlers.rs +++ b/mls_validation_service/src/handlers.rs @@ -328,11 +328,12 @@ mod tests { prelude::{tls_codec::Serialize, Credential as OpenMlsCredential, CredentialWithKey}, }; use openmls_rust_crypto::OpenMlsRustCrypto; + use xmtp_common::{rand_string, rand_u64}; use xmtp_cryptography::XmtpInstallationCredential; use xmtp_id::{ associations::{ generate_inbox_id, - test_utils::{rand_string, rand_u64, MockSmartContractSignatureVerifier}, + test_utils::MockSmartContractSignatureVerifier, unverified::{UnverifiedAction, UnverifiedIdentityUpdate}, }, is_smart_contract, @@ -399,7 +400,7 @@ mod tests { #[tokio::test] #[should_panic] async fn test_get_association_state() { - let account_address = rand_string(); + let account_address = rand_string::<24>(); let nonce = rand_u64(); let inbox_id = generate_inbox_id(&account_address, &nonce).unwrap(); let update = UnverifiedIdentityUpdate::new_test( diff --git a/rust-toolchain b/rust-toolchain index 6228acd2f..69af1791b 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -3,14 +3,8 @@ channel = "stable" components = [ "rustc", "cargo", "clippy", "rustfmt", "rust-analyzer" ] targets = [ "wasm32-unknown-unknown", - "x86_64-unknown-linux-gnu", - "aarch64-linux-android", - "armv7-linux-androideabi", - "x86_64-linux-android", - "i686-linux-android", "x86_64-apple-ios", "x86_64-apple-darwin", - "aarch64-apple-ios", - "x86_64-unknown-linux-musl", + "aarch64-apple-ios" ] profile = "default" diff --git a/xmtp_api_grpc/Cargo.toml b/xmtp_api_grpc/Cargo.toml index 7c4ebee2c..b69a0d0c4 100644 --- a/xmtp_api_grpc/Cargo.toml +++ b/xmtp_api_grpc/Cargo.toml @@ -17,13 +17,17 @@ xmtp_proto = { path = "../xmtp_proto", features = ["proto_full"] } xmtp_v2 = { path = "../xmtp_v2" } zeroize.workspace = true +# Anything but iOS and Android will use either webpki or native. +# If native certs are not found, it will fallback to webpki [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tonic = { workspace = true, features = [ "default", "tls", "tls-native-roots", + "tls-webpki-roots" ] } +# Force Android + iOS to use webki [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] tonic = { workspace = true, features = [ "default", diff --git a/xmtp_api_grpc/src/grpc_api_helper.rs b/xmtp_api_grpc/src/grpc_api_helper.rs index 150e0b57c..fa7683de7 100644 --- a/xmtp_api_grpc/src/grpc_api_helper.rs +++ b/xmtp_api_grpc/src/grpc_api_helper.rs @@ -74,7 +74,7 @@ pub struct Client { } impl Client { - pub async fn create(host: String, is_secure: bool) -> Result { + pub async fn create(host: impl ToString, is_secure: bool) -> Result { let host = host.to_string(); let app_version = MetadataValue::try_from(&String::from("0.0.0")) .map_err(|e| Error::new(ErrorKind::MetadataError).with(e))?; diff --git a/xmtp_api_grpc/src/lib.rs b/xmtp_api_grpc/src/lib.rs index d6bfe96e1..91bfced7f 100644 --- a/xmtp_api_grpc/src/lib.rs +++ b/xmtp_api_grpc/src/lib.rs @@ -16,13 +16,13 @@ mod utils { #[async_trait::async_trait] impl XmtpTestClient for crate::Client { async fn create_local() -> Self { - crate::Client::create("http://localhost:5556".into(), false) + crate::Client::create("http://localhost:5556", false) .await .unwrap() } async fn create_dev() -> Self { - crate::Client::create("https://grpc.dev.xmtp.network:443".into(), false) + crate::Client::create("https://grpc.dev.xmtp.network:443", true) .await .unwrap() } diff --git a/xmtp_api_grpc/src/replication_client.rs b/xmtp_api_grpc/src/replication_client.rs index d7ae88e2c..ee9a33141 100644 --- a/xmtp_api_grpc/src/replication_client.rs +++ b/xmtp_api_grpc/src/replication_client.rs @@ -72,16 +72,28 @@ pub struct ClientV4 { } impl ClientV4 { - pub async fn create(host: String, is_secure: bool) -> Result { - let host = host.to_string(); + pub async fn create( + grpc_url: String, + payer_url: String, + is_secure: bool, + ) -> Result { let app_version = MetadataValue::try_from(&String::from("0.0.0")) .map_err(|e| Error::new(ErrorKind::MetadataError).with(e))?; let libxmtp_version = MetadataValue::try_from(&String::from("0.0.0")) .map_err(|e| Error::new(ErrorKind::MetadataError).with(e))?; - let channel = match is_secure { - true => create_tls_channel(host).await?, - false => Channel::from_shared(host) + let grpc_channel = match is_secure { + true => create_tls_channel(grpc_url).await?, + false => Channel::from_shared(grpc_url) + .map_err(|e| Error::new(ErrorKind::SetupCreateChannelError).with(e))? + .connect() + .await + .map_err(|e| Error::new(ErrorKind::SetupConnectionError).with(e))?, + }; + + let payer_channel = match is_secure { + true => create_tls_channel(payer_url).await?, + false => Channel::from_shared(payer_url) .map_err(|e| Error::new(ErrorKind::SetupCreateChannelError).with(e))? .connect() .await @@ -89,8 +101,8 @@ impl ClientV4 { }; // GroupMessageInputTODO(mkysel) for now we assume both payer and replication are on the same host - let client = ReplicationApiClient::new(channel.clone()); - let payer_client = PayerApiClient::new(channel.clone()); + let client = ReplicationApiClient::new(grpc_channel.clone()); + let payer_client = PayerApiClient::new(payer_channel.clone()); Ok(Self { client, @@ -362,14 +374,8 @@ impl XmtpIdentityClient for ClientV4 { &self, request: PublishIdentityUpdateRequest, ) -> Result { - let client = &mut self.payer_client.clone(); - let res = client - .publish_client_envelopes(PublishClientEnvelopesRequest::try_from(request)?) - .await; - match res { - Ok(_) => Ok(PublishIdentityUpdateResponse {}), - Err(e) => Err(Error::new(ErrorKind::MlsError).with(e)), - } + self.publish_envelopes_to_payer(vec![request]).await?; + Ok(PublishIdentityUpdateResponse {}) } #[tracing::instrument(level = "trace", skip_all)] @@ -482,20 +488,30 @@ impl ClientV4 { } #[tracing::instrument(level = "trace", skip_all)] - async fn publish_envelopes_to_payer< - T: TryInto, - >( + async fn publish_envelopes_to_payer( &self, - items: impl IntoIterator, - ) -> Result<(), Error> { + messages: impl IntoIterator, + ) -> Result<(), Error> + where + T: TryInto, + >::Error: std::error::Error + Send + Sync + 'static, + { let client = &mut self.payer_client.clone(); - for item in items { - let request = item.try_into()?; - let res = client.publish_client_envelopes(request).await; - if let Err(e) = res { - return Err(Error::new(ErrorKind::MlsError).with(e)); - } - } + + let envelopes: Vec = messages + .into_iter() + .map(|message| { + message + .try_into() + .map_err(|e| Error::new(ErrorKind::MlsError).with(e)) + }) + .collect::>()?; + + client + .publish_client_envelopes(PublishClientEnvelopesRequest { envelopes }) + .await + .map_err(|e| Error::new(ErrorKind::MlsError).with(e))?; + Ok(()) } } diff --git a/xmtp_api_http/src/util.rs b/xmtp_api_http/src/util.rs index 55e4ff3fa..8a839fc56 100644 --- a/xmtp_api_http/src/util.rs +++ b/xmtp_api_http/src/util.rs @@ -76,7 +76,6 @@ pub fn create_grpc_stream_inner< http_client: reqwest::Client, ) -> impl Stream> { async_stream::stream! { - tracing::info!("Spawning grpc http stream"); let request = http_client .post(endpoint) .json(&request) diff --git a/xmtp_content_types/Cargo.toml b/xmtp_content_types/Cargo.toml new file mode 100644 index 000000000..2b7c506d1 --- /dev/null +++ b/xmtp_content_types/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "xmtp_content_types" +version.workspace = true +license.workspace = true + +[dependencies] +thiserror = { workspace = true } +prost = { workspace = true, features = ["prost-derive"] } +rand = { workspace = true } + +# XMTP/Local +xmtp_proto = { workspace = true, features = ["convert"] } +xmtp_common = { workspace = true } + +[dev-dependencies] +xmtp_common = { workspace = true, features = ['test-utils'] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tonic = { version = "0.12", features = ["transport"] } diff --git a/xmtp_mls/src/codecs/group_updated.rs b/xmtp_content_types/src/group_updated.rs similarity index 94% rename from xmtp_mls/src/codecs/group_updated.rs rename to xmtp_content_types/src/group_updated.rs index 09ebea595..2ab08917a 100644 --- a/xmtp_mls/src/codecs/group_updated.rs +++ b/xmtp_content_types/src/group_updated.rs @@ -50,20 +50,18 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use xmtp_proto::xmtp::mls::message_contents::{group_updated::Inbox, GroupUpdated}; - - use crate::utils::test::rand_string; - use super::*; + use xmtp_common::rand_string; + use xmtp_proto::xmtp::mls::message_contents::{group_updated::Inbox, GroupUpdated}; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn test_encode_decode() { let new_member = Inbox { - inbox_id: rand_string(), + inbox_id: rand_string::<24>(), }; let data = GroupUpdated { - initiated_by_inbox_id: rand_string(), + initiated_by_inbox_id: rand_string::<24>(), added_inboxes: vec![new_member.clone()], removed_inboxes: vec![], metadata_field_changes: vec![], diff --git a/xmtp_mls/src/codecs/mod.rs b/xmtp_content_types/src/lib.rs similarity index 86% rename from xmtp_mls/src/codecs/mod.rs rename to xmtp_content_types/src/lib.rs index dc44e7cf4..04a7a3fb9 100644 --- a/xmtp_mls/src/codecs/mod.rs +++ b/xmtp_content_types/src/lib.rs @@ -3,9 +3,14 @@ pub mod membership_change; pub mod text; use thiserror::Error; - use xmtp_proto::xmtp::mls::message_contents::{ContentTypeId, EncodedContent}; +pub enum ContentType { + GroupMembershipChange, + GroupUpdated, + Text, +} + #[derive(Debug, Error)] pub enum CodecError { #[error("encode error {0}")] diff --git a/xmtp_mls/src/codecs/membership_change.rs b/xmtp_content_types/src/membership_change.rs similarity index 94% rename from xmtp_mls/src/codecs/membership_change.rs rename to xmtp_content_types/src/membership_change.rs index e64f50df6..14401bbb8 100644 --- a/xmtp_mls/src/codecs/membership_change.rs +++ b/xmtp_content_types/src/membership_change.rs @@ -52,18 +52,16 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use xmtp_proto::xmtp::mls::message_contents::MembershipChange; - - use crate::utils::test::{rand_string, rand_vec}; - use super::*; + use xmtp_common::{rand_string, rand_vec}; + use xmtp_proto::xmtp::mls::message_contents::MembershipChange; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn test_encode_decode() { let new_member = MembershipChange { - installation_ids: vec![rand_vec()], - account_address: rand_string(), + installation_ids: vec![rand_vec::<24>()], + account_address: rand_string::<24>(), initiated_by_account_address: "".to_string(), }; let data = GroupMembershipChanges { diff --git a/xmtp_mls/src/codecs/text.rs b/xmtp_content_types/src/text.rs similarity index 97% rename from xmtp_mls/src/codecs/text.rs rename to xmtp_content_types/src/text.rs index d016f1513..124fb7831 100644 --- a/xmtp_mls/src/codecs/text.rs +++ b/xmtp_content_types/src/text.rs @@ -58,7 +58,7 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::codecs::{text::TextCodec, ContentCodec}; + use crate::{text::TextCodec, ContentCodec}; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] diff --git a/xmtp_cryptography/src/basic_credential.rs b/xmtp_cryptography/src/basic_credential.rs index 3f76cb33b..5e7e02337 100644 --- a/xmtp_cryptography/src/basic_credential.rs +++ b/xmtp_cryptography/src/basic_credential.rs @@ -159,7 +159,7 @@ impl Signer for XmtpInstallationCredential { } // The signer here must maintain compatability with `SignatureKeyPair` -impl<'a> Signer for &'a XmtpInstallationCredential { +impl Signer for &XmtpInstallationCredential { fn sign(&self, payload: &[u8]) -> Result, signatures::SignerError> { SignatureKeyPair::from(*self).sign(payload) } diff --git a/xmtp_debug/Cargo.toml b/xmtp_debug/Cargo.toml index d1ca81f23..f1ec81d46 100644 --- a/xmtp_debug/Cargo.toml +++ b/xmtp_debug/Cargo.toml @@ -23,7 +23,7 @@ owo-colors = "4.1" url.workspace = true redb = "2.2" directories = "5.0" -const_format = "0.2" +const_format.workspace = true speedy = "0.8" hex.workspace = true prost.workspace = true @@ -40,5 +40,5 @@ rand.workspace = true fs_extra = "1.3" ethers.workspace = true miniserde = "0.1" -fdlimit = "0.3" +fdlimit.workspace = true lipsum = "0.9" diff --git a/xmtp_debug/src/app.rs b/xmtp_debug/src/app.rs index 880349463..79f6c6473 100644 --- a/xmtp_debug/src/app.rs +++ b/xmtp_debug/src/app.rs @@ -14,6 +14,8 @@ mod inspect; mod modify; /// Query for data on the network mod query; +/// Send functionality +mod send; /// Local storage mod store; /// Types shared between App Functions @@ -96,6 +98,7 @@ impl App { if let Some(cmd) = cmd { match cmd { Generate(g) => generate::Generate::new(g, backend, db).run().await, + Send(s) => send::Send::new(s, backend, db).run().await, Inspect(i) => inspect::Inspect::new(i, backend, db).run().await, Query(_q) => todo!(), Info(i) => info::Info::new(i, backend, db).run().await, diff --git a/xmtp_debug/src/app/clients.rs b/xmtp_debug/src/app/clients.rs index 6ed848eeb..6824ca39e 100644 --- a/xmtp_debug/src/app/clients.rs +++ b/xmtp_debug/src/app/clients.rs @@ -1,4 +1,5 @@ //! Different ways to create a [`crate::DbgClient`] + use super::*; use crate::app::types::*; @@ -72,7 +73,7 @@ async fn new_client_inner( dir.join(db_name) }; - let client = crate::DbgClient::builder(IdentityStrategy::CreateIfNotFound( + let client = crate::DbgClient::builder(IdentityStrategy::new( inbox_id, wallet.get_address(), nonce, diff --git a/xmtp_debug/src/app/generate/groups.rs b/xmtp_debug/src/app/generate/groups.rs index fb828c93f..e8f33b550 100644 --- a/xmtp_debug/src/app/generate/groups.rs +++ b/xmtp_debug/src/app/generate/groups.rs @@ -34,6 +34,7 @@ impl GenerateGroups { } pub async fn create_groups(&self, n: usize, invitees: usize) -> Result> { + // TODO: Check if identities still exist let mut groups: Vec = Vec::with_capacity(n); let style = ProgressStyle::with_template( "{bar} {pos}/{len} elapsed {elapsed} remaining {eta_precise}", diff --git a/xmtp_debug/src/app/generate/identity.rs b/xmtp_debug/src/app/generate/identity.rs index 01df562d6..838d129c3 100644 --- a/xmtp_debug/src/app/generate/identity.rs +++ b/xmtp_debug/src/app/generate/identity.rs @@ -47,7 +47,7 @@ impl GenerateIdentity { let first = identities.next().ok_or(eyre::eyre!("Does not exist"))??; let state = client - .get_latest_association_state(&connection, hex::encode(first.inbox_id)) + .get_latest_association_state(&connection, &hex::encode(first.inbox_id)) .await?; info!("Found generated identities, checking for registration on backend...",); // we assume that if the first identity is registered, they all are @@ -114,7 +114,7 @@ impl GenerateIdentity { let future = |inbox_id: [u8; 32]| async move { let id = hex::encode(inbox_id); trace!(inbox_id = id, "getting association state"); - let state = tmp.get_latest_association_state(&conn, id).await?; + let state = tmp.get_latest_association_state(&conn, &id).await?; bar_ref.inc(1); Ok(state) }; diff --git a/xmtp_debug/src/app/generate/messages.rs b/xmtp_debug/src/app/generate/messages.rs index aeb118ff7..fc6006d85 100644 --- a/xmtp_debug/src/app/generate/messages.rs +++ b/xmtp_debug/src/app/generate/messages.rs @@ -8,7 +8,6 @@ use crate::{ use color_eyre::eyre::{self, eyre, Result}; use rand::{rngs::SmallRng, seq::SliceRandom, Rng, SeedableRng}; use std::sync::Arc; -use xmtp_mls::XmtpOpenMlsProvider; mod content_type; @@ -118,10 +117,9 @@ impl GenerateMessages { hex::encode(inbox_id) ))?; let client = app::client_from_identity(&identity, &network).await?; - let conn = client.store().conn()?; - client.sync_welcomes(&conn).await?; + let provider = client.mls_provider()?; + client.sync_welcomes(&provider).await?; let group = client.group(group.id.into())?; - let provider: XmtpOpenMlsProvider = conn.into(); group.maybe_update_installations(&provider, None).await?; group.sync_with_conn(&provider).await?; let words = rng.gen_range(0..*max_message_size); diff --git a/xmtp_debug/src/app/inspect.rs b/xmtp_debug/src/app/inspect.rs index bf500632e..b09f68ba0 100644 --- a/xmtp_debug/src/app/inspect.rs +++ b/xmtp_debug/src/app/inspect.rs @@ -38,7 +38,7 @@ impl Inspect { match kind { Associations => { let state = client - .get_latest_association_state(&conn, hex::encode(*inbox_id)) + .get_latest_association_state(&conn, &hex::encode(*inbox_id)) .await?; info!( inbox_id = state.inbox_id(), diff --git a/xmtp_debug/src/app/send.rs b/xmtp_debug/src/app/send.rs new file mode 100644 index 000000000..35a95cbf7 --- /dev/null +++ b/xmtp_debug/src/app/send.rs @@ -0,0 +1,58 @@ +use std::sync::Arc; + +use crate::args; +use color_eyre::eyre::{eyre, Result}; +use rand::prelude::*; + +use super::store::{Database, GroupStore, IdentityStore}; + +#[derive(Debug)] +pub struct Send { + db: Arc, + opts: args::Send, + network: args::BackendOpts, +} + +impl Send { + pub fn new(opts: args::Send, network: args::BackendOpts, db: Arc) -> Self { + Self { opts, network, db } + } + + pub async fn run(self) -> Result<()> { + use args::ActionKind::*; + let args::Send { ref action, .. } = self.opts; + + match action { + Message => self.message().await, + } + } + + async fn message(&self) -> Result<()> { + let Self { ref network, .. } = self; + let args::Send { + ref data, group_id, .. + } = &self.opts; + + let group_store: GroupStore = self.db.clone().into(); + let identity_store: IdentityStore = self.db.clone().into(); + let key = (u64::from(network), **group_id); + let group = group_store + .get(key.into())? + .ok_or(eyre!("No group with id {}", group_id))?; + let member = group + .members + .choose(&mut rand::thread_rng()) + .ok_or(eyre!("Empty group, no members to send message!"))?; + let key = (u64::from(network), *member); + let identity = identity_store + .get(key.into())? + .ok_or(eyre!("No Identity with inbox_id [{}]", hex::encode(member)))?; + + let client = crate::app::client_from_identity(&identity, network).await?; + let provider = client.mls_provider()?; + client.sync_welcomes(&provider).await?; + let xmtp_group = client.group(group.id.to_vec())?; + xmtp_group.send_message(data.as_bytes()).await?; + Ok(()) + } +} diff --git a/xmtp_debug/src/app/store.rs b/xmtp_debug/src/app/store.rs index 8fd0c85df..6d5fc4608 100644 --- a/xmtp_debug/src/app/store.rs +++ b/xmtp_debug/src/app/store.rs @@ -78,11 +78,14 @@ impl Writable for NetworkKey { } impl redb::Value for NetworkKey { - type SelfType<'a> = NetworkKey + type SelfType<'a> + = NetworkKey where Self: 'a; - type AsBytes<'a> = Vec // TODO: It _has_ be possible to make this a const [u8; N] somehow. + type AsBytes<'a> + = Vec + // TODO: It _has_ be possible to make this a const [u8; N] somehow. // We're not allowed to use `size_of::>()` yet, even though size_of and N are // both constant where @@ -197,7 +200,7 @@ pub struct KeyValueStore<'db, Storage> { store: Storage, } -impl<'db, Storage> KeyValueStore<'db, Storage> { +impl KeyValueStore<'_, Storage> { fn apply_write(&self, op: impl FnOnce(&WriteTransaction) -> Result<()>) -> Result<()> { use DatabaseOrTransaction::*; match self.db { @@ -376,11 +379,13 @@ pub struct Metadata { } impl redb::Value for Metadata { - type SelfType<'a> = Metadata + type SelfType<'a> + = Metadata where Self: 'a; - type AsBytes<'a> = [u8; size_of::()] + type AsBytes<'a> + = [u8; size_of::()] where Self: 'a; diff --git a/xmtp_debug/src/app/store/groups.rs b/xmtp_debug/src/app/store/groups.rs index f9d6d5140..a10c69d98 100644 --- a/xmtp_debug/src/app/store/groups.rs +++ b/xmtp_debug/src/app/store/groups.rs @@ -23,7 +23,7 @@ impl super::DeriveKey for Group { } } -impl<'a> super::DeriveKey for &'a Group { +impl super::DeriveKey for &Group { fn key(&self, network: u64) -> GroupKey { GroupKey { network, diff --git a/xmtp_debug/src/app/store/identity.rs b/xmtp_debug/src/app/store/identity.rs index 674aed515..6a5a468bb 100644 --- a/xmtp_debug/src/app/store/identity.rs +++ b/xmtp_debug/src/app/store/identity.rs @@ -33,7 +33,7 @@ impl super::DeriveKey for Identity { } } -impl<'a> super::DeriveKey for &'a Identity { +impl super::DeriveKey for &Identity { fn key(&self, network: u64) -> IdentityKey { IdentityKey { network, diff --git a/xmtp_debug/src/app/store/metadata.rs b/xmtp_debug/src/app/store/metadata.rs index bf44f1909..7bae6db65 100644 --- a/xmtp_debug/src/app/store/metadata.rs +++ b/xmtp_debug/src/app/store/metadata.rs @@ -96,7 +96,7 @@ impl super::DeriveKey for Metadata { } } -impl<'a> super::DeriveKey for &'a Metadata { +impl super::DeriveKey for &Metadata { fn key(&self, network: u64) -> MetaKey { MetaKey { network, diff --git a/xmtp_debug/src/app/types.rs b/xmtp_debug/src/app/types.rs index 28d39e2e0..073f26dce 100644 --- a/xmtp_debug/src/app/types.rs +++ b/xmtp_debug/src/app/types.rs @@ -135,11 +135,13 @@ struct ForeignIdentity { } impl redb::Value for Identity { - type SelfType<'a> = Identity + type SelfType<'a> + = Identity where Self: 'a; - type AsBytes<'a> = [u8; size_of::()] + type AsBytes<'a> + = [u8; size_of::()] where Self: 'a; @@ -184,11 +186,13 @@ pub struct Group { } impl redb::Value for Group { - type SelfType<'a> = Group + type SelfType<'a> + = Group where Self: 'a; - type AsBytes<'a> = Vec + type AsBytes<'a> + = Vec where Self: 'a; diff --git a/xmtp_debug/src/args.rs b/xmtp_debug/src/args.rs index 17c03ea32..d4ca414ce 100644 --- a/xmtp_debug/src/args.rs +++ b/xmtp_debug/src/args.rs @@ -27,11 +27,25 @@ pub enum Commands { Generate(Generate), Modify(Modify), Inspect(Inspect), + Send(Send), Query(Query), Info(InfoOpts), Export(ExportOpts), } +/// Send Data on the network +#[derive(Args, Debug)] +pub struct Send { + pub action: ActionKind, + pub data: String, + pub group_id: GroupId, +} + +#[derive(ValueEnum, Debug, Clone)] +pub enum ActionKind { + Message, +} + /// Generate Groups/Messages/Users #[derive(Args, Debug)] pub struct Generate { diff --git a/xmtp_id/Cargo.toml b/xmtp_id/Cargo.toml index 3488b23d3..2b6d44c91 100644 --- a/xmtp_id/Cargo.toml +++ b/xmtp_id/Cargo.toml @@ -27,8 +27,9 @@ tokio = { workspace = true, features = ["macros"] } tracing.workspace = true xmtp_cryptography.workspace = true xmtp_proto = { workspace = true, features = ["proto_full"] } -wasm-timer.workspace = true +web-time.workspace = true ethers = { workspace = true, features = ["rustls"] } +xmtp_common.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] openmls = { workspace = true, features = ["js"] } @@ -40,10 +41,11 @@ openmls.workspace = true [dev-dependencies] xmtp_v2 = { path = "../xmtp_v2" } ed25519-dalek = { workspace = true, features = ["digest", "rand_core"] } +xmtp_common = { workspace = true, features = ["test-utils"] } +wasm-bindgen-test.workspace = true [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test.workspace = true gloo-timers.workspace = true [features] -test-utils = [] +test-utils = ["xmtp_common/test-utils"] diff --git a/xmtp_id/src/associations/association_log.rs b/xmtp_id/src/associations/association_log.rs index 6588b3551..2160314a9 100644 --- a/xmtp_id/src/associations/association_log.rs +++ b/xmtp_id/src/associations/association_log.rs @@ -160,7 +160,7 @@ impl IdentityAction for AddAssociation { let existing_entity_id = match existing_member { // If there is an existing member of the XID, use that member's ID - Some(member) => member.identifier, + Some(member) => member.identifier.clone(), None => { // Get the recovery address from the state as a MemberIdentifier let recovery_identifier: MemberIdentifier = @@ -228,7 +228,7 @@ impl IdentityAction for RevokeAssociation { // Ensure that the new signature is on the same chain as the signature to create the account let existing_member = existing_state.get(&self.recovery_address_signature.signer); if let Some(member) = existing_member { - verify_chain_id_matches(&member, &self.recovery_address_signature)?; + verify_chain_id_matches(member, &self.recovery_address_signature)?; } if is_legacy_signature(&self.recovery_address_signature) { @@ -289,7 +289,7 @@ impl IdentityAction for ChangeRecoveryAddress { let existing_member = existing_state.get(&self.recovery_address_signature.signer); if let Some(member) = existing_member { - verify_chain_id_matches(&member, &self.recovery_address_signature)?; + verify_chain_id_matches(member, &self.recovery_address_signature)?; } if is_legacy_signature(&self.recovery_address_signature) { @@ -459,7 +459,7 @@ fn verify_chain_id_matches( member: &Member, signature: &VerifiedSignature, ) -> Result<(), AssociationError> { - if member.added_on_chain_id.ne(&signature.chain_id) { + if member.added_on_chain_id != signature.chain_id { return Err(AssociationError::ChainIdMismatch( member.added_on_chain_id.unwrap_or(0), signature.chain_id.unwrap_or(0), diff --git a/xmtp_id/src/associations/builder.rs b/xmtp_id/src/associations/builder.rs index b1e689411..5426cd8b7 100644 --- a/xmtp_id/src/associations/builder.rs +++ b/xmtp_id/src/associations/builder.rs @@ -2,10 +2,11 @@ //! resolved into an [`IdentityUpdate`]. An [`IdentityUpdate`] may be used for updating the state //! of an XMTP ID according to [XIP-46](https://github.com/xmtp/XIPs/pull/53) -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; -use crate::{scw_verifier::SmartContractSignatureVerifier, utils::now_ns}; +use crate::scw_verifier::SmartContractSignatureVerifier; use thiserror::Error; +use xmtp_common::time::now_ns; use super::{ unsigned_actions::{ @@ -194,29 +195,18 @@ impl SignatureRequest { } } - pub fn missing_signatures(&self) -> Vec { - let signers: HashSet = self - .pending_actions + pub fn missing_signatures(&self) -> Vec<&MemberIdentifier> { + self.pending_actions .iter() - .flat_map(|pending_action| { - pending_action - .pending_signatures - .values() - .cloned() - .collect::>() - }) - .collect(); - - let signatures: HashSet = self.signatures.keys().cloned().collect(); - - signers.difference(&signatures).cloned().collect() + .flat_map(|pending_action| pending_action.pending_signatures.values()) + .filter(|ident| !self.signatures.contains_key(ident)) + .collect() } - pub fn missing_address_signatures(&self) -> Vec { + pub fn missing_address_signatures(&self) -> Vec<&MemberIdentifier> { self.missing_signatures() - .iter() + .into_iter() .filter(|member| member.kind() == MemberKind::Address) - .cloned() .collect() } @@ -277,7 +267,7 @@ impl SignatureRequest { "adding verified signature"); // Make sure the signer is someone actually in the request - if !missing_signatures.contains(signer_identity) { + if !missing_signatures.contains(&signer_identity) { return Err(SignatureRequestError::UnknownSigner); } diff --git a/xmtp_id/src/associations/member.rs b/xmtp_id/src/associations/member.rs index 8c7d5cab7..f23e19101 100644 --- a/xmtp_id/src/associations/member.rs +++ b/xmtp_id/src/associations/member.rs @@ -1,3 +1,4 @@ +use ed25519_dalek::VerifyingKey; use xmtp_cryptography::XmtpInstallationCredential; #[derive(Clone, Debug, PartialEq)] @@ -106,6 +107,12 @@ impl From> for MemberIdentifier { } } +impl From for MemberIdentifier { + fn from(installation: VerifyingKey) -> Self { + installation.as_bytes().to_vec().into() + } +} + impl<'a> From<&'a XmtpInstallationCredential> for MemberIdentifier { fn from(cred: &'a XmtpInstallationCredential) -> MemberIdentifier { MemberIdentifier::Installation(cred.public_slice().to_vec()) @@ -158,15 +165,12 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::associations::test_utils; - use super::*; - - use test_utils::rand_string; + use xmtp_common::rand_hexstring; impl Default for MemberIdentifier { fn default() -> Self { - MemberIdentifier::Address(rand_string()) + MemberIdentifier::Address(rand_hexstring()) } } diff --git a/xmtp_id/src/associations/mod.rs b/xmtp_id/src/associations/mod.rs index 7cd29ba17..ca6e6e7d3 100644 --- a/xmtp_id/src/associations/mod.rs +++ b/xmtp_id/src/associations/mod.rs @@ -42,11 +42,11 @@ pub fn get_state>( #[cfg(any(test, feature = "test-utils"))] pub mod test_defaults { use self::{ - test_utils::{rand_string, rand_u64, rand_vec}, unverified::{UnverifiedAction, UnverifiedIdentityUpdate}, verified_signature::VerifiedSignature, }; use super::*; + use xmtp_common::{rand_hexstring, rand_u64, rand_vec}; impl IdentityUpdate { pub fn new_test(actions: Vec, inbox_id: String) -> Self { @@ -62,19 +62,19 @@ pub mod test_defaults { impl Default for AddAssociation { fn default() -> Self { - let existing_member = rand_string(); - let new_member = rand_vec(); + let existing_member = rand_hexstring(); + let new_member = rand_vec::<32>(); Self { existing_member_signature: VerifiedSignature::new( existing_member.into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), new_member_signature: VerifiedSignature::new( new_member.clone().into(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), new_member_identifier: new_member.into(), @@ -85,14 +85,14 @@ pub mod test_defaults { // Default will create an inbox with a ERC-191 signature impl Default for CreateInbox { fn default() -> Self { - let signer = rand_string(); + let signer = rand_hexstring(); Self { nonce: rand_u64(), account_address: signer.clone(), initial_address_signature: VerifiedSignature::new( signer.into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), } @@ -101,15 +101,15 @@ pub mod test_defaults { impl Default for RevokeAssociation { fn default() -> Self { - let signer = rand_string(); + let signer = rand_hexstring(); Self { recovery_address_signature: VerifiedSignature::new( signer.into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), - revoked_member: rand_string().into(), + revoked_member: rand_hexstring().into(), } } } @@ -119,10 +119,11 @@ pub mod test_defaults { pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use wasm_bindgen_test::wasm_bindgen_test; - use self::test_utils::{rand_string, rand_vec}; use super::*; use crate::associations::verified_signature::VerifiedSignature; + use xmtp_common::{rand_hexstring, rand_vec}; pub fn new_test_inbox() -> AssociationState { let create_request = CreateInbox::default(); @@ -144,7 +145,7 @@ pub(crate) mod tests { existing_member_signature: VerifiedSignature::new( initial_wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), ..Default::default() @@ -157,7 +158,7 @@ pub(crate) mod tests { .unwrap() } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_create_inbox() { let create_request = CreateInbox::default(); let inbox_id = @@ -172,11 +173,11 @@ pub(crate) mod tests { assert!(existing_entity.identifier.eq(&account_address.into())); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn create_and_add_separately() { let initial_state = new_test_inbox(); let inbox_id = initial_state.inbox_id().to_string(); - let new_installation_identifier: MemberIdentifier = rand_vec().into(); + let new_installation_identifier: MemberIdentifier = rand_vec::<32>().into(); let first_member: MemberIdentifier = initial_state.recovery_address().clone().into(); let update = Action::AddAssociation(AddAssociation { @@ -184,13 +185,13 @@ pub(crate) mod tests { new_member_signature: VerifiedSignature::new( new_installation_identifier.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), existing_member_signature: VerifiedSignature::new( first_member.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -206,24 +207,24 @@ pub(crate) mod tests { assert_eq!(new_member.added_by_entity, Some(first_member)); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn create_and_add_together() { let create_action = CreateInbox::default(); let account_address = create_action.account_address.clone(); let inbox_id = generate_inbox_id(&account_address, &create_action.nonce).unwrap(); - let new_member_identifier: MemberIdentifier = rand_vec().into(); + let new_member_identifier: MemberIdentifier = rand_vec::<32>().into(); let add_action = AddAssociation { existing_member_signature: VerifiedSignature::new( account_address.clone().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), // Add an installation ID new_member_signature: VerifiedSignature::new( new_member_identifier.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), new_member_identifier: new_member_identifier.clone(), @@ -243,9 +244,9 @@ pub(crate) mod tests { ); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn create_from_legacy_key() { - let member_identifier: MemberIdentifier = rand_string().into(); + let member_identifier: MemberIdentifier = rand_hexstring().into(); let create_action = CreateInbox { nonce: 0, account_address: member_identifier.to_string(), @@ -282,7 +283,7 @@ pub(crate) mod tests { assert!(matches!(update_result, Err(AssociationError::Replay))); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn add_wallet_from_installation_key() { let initial_state = new_test_inbox_with_installation(); let inbox_id = initial_state.inbox_id().to_string(); @@ -293,19 +294,19 @@ pub(crate) mod tests { .unwrap() .identifier; - let new_wallet_address: MemberIdentifier = rand_string().into(); + let new_wallet_address: MemberIdentifier = rand_hexstring().into(); let add_association = Action::AddAssociation(AddAssociation { new_member_identifier: new_wallet_address.clone(), new_member_signature: VerifiedSignature::new( new_wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), existing_member_signature: VerifiedSignature::new( installation_id.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -318,13 +319,13 @@ pub(crate) mod tests { assert_eq!(new_state.members().len(), 3); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn reject_invalid_signature_on_create() { // Creates a signature with the wrong signer let bad_signature = VerifiedSignature::new( - rand_string().into(), + rand_hexstring().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ); let action = CreateInbox { @@ -334,7 +335,7 @@ pub(crate) mod tests { let state_result = get_state(vec![IdentityUpdate::new_test( vec![Action::CreateInbox(action)], - rand_string(), + rand_hexstring(), )]); assert!(state_result.is_err()); @@ -344,15 +345,15 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn reject_invalid_signature_on_update() { let initial_state = new_test_inbox(); let inbox_id = initial_state.inbox_id().to_string(); // Signature is from a random address let bad_signature = VerifiedSignature::new( - rand_string().into(), + rand_hexstring().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ); @@ -376,7 +377,7 @@ pub(crate) mod tests { existing_member_signature: VerifiedSignature::new( initial_state.recovery_address().clone().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), ..Default::default() @@ -392,7 +393,7 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn reject_if_signer_not_existing_member() { let create_inbox = CreateInbox::default(); let inbox_id = @@ -402,9 +403,9 @@ pub(crate) mod tests { let update = Action::AddAssociation(AddAssociation { // Existing member signature is coming from a random wallet existing_member_signature: VerifiedSignature::new( - rand_string().into(), + rand_hexstring().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), ..Default::default() @@ -420,26 +421,26 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn reject_if_installation_adding_installation() { let existing_state = new_test_inbox_with_installation(); let inbox_id = existing_state.inbox_id().to_string(); let existing_installations = existing_state.members_by_kind(MemberKind::Installation); let existing_installation = existing_installations.first().unwrap(); - let new_installation_id: MemberIdentifier = rand_vec().into(); + let new_installation_id: MemberIdentifier = rand_vec::<32>().into(); let update = Action::AddAssociation(AddAssociation { existing_member_signature: VerifiedSignature::new( existing_installation.identifier.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), new_member_identifier: new_installation_id.clone(), new_member_signature: VerifiedSignature::new( new_installation_id.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -457,7 +458,7 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn revoke() { let initial_state = new_test_inbox_with_installation(); let inbox_id = initial_state.inbox_id().to_string(); @@ -471,7 +472,7 @@ pub(crate) mod tests { recovery_address_signature: VerifiedSignature::new( initial_state.recovery_address().clone().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), revoked_member: installation_id.clone(), @@ -485,7 +486,7 @@ pub(crate) mod tests { assert!(new_state.get(&installation_id).is_none()); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn revoke_children() { let initial_state = new_test_inbox_with_installation(); let inbox_id = initial_state.inbox_id().to_string(); @@ -500,7 +501,7 @@ pub(crate) mod tests { existing_member_signature: VerifiedSignature::new( wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), ..Default::default() @@ -517,7 +518,7 @@ pub(crate) mod tests { recovery_address_signature: VerifiedSignature::new( wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), revoked_member: wallet_address.clone(), @@ -532,7 +533,7 @@ pub(crate) mod tests { assert_eq!(new_state.members().len(), 0); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn revoke_and_re_add() { let initial_state = new_test_inbox(); let wallet_address = initial_state @@ -544,19 +545,19 @@ pub(crate) mod tests { let inbox_id = initial_state.inbox_id().to_string(); - let second_wallet_address: MemberIdentifier = rand_string().into(); + let second_wallet_address: MemberIdentifier = rand_hexstring().into(); let add_second_wallet = Action::AddAssociation(AddAssociation { new_member_identifier: second_wallet_address.clone(), new_member_signature: VerifiedSignature::new( second_wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), existing_member_signature: VerifiedSignature::new( wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -565,7 +566,7 @@ pub(crate) mod tests { recovery_address_signature: VerifiedSignature::new( wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), revoked_member: second_wallet_address.clone(), @@ -586,13 +587,13 @@ pub(crate) mod tests { new_member_signature: VerifiedSignature::new( second_wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), existing_member_signature: VerifiedSignature::new( wallet_address, SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -605,19 +606,19 @@ pub(crate) mod tests { assert_eq!(state_after_re_add.members().len(), 2); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn change_recovery_address() { let initial_state = new_test_inbox_with_installation(); let inbox_id = initial_state.inbox_id().to_string(); let initial_recovery_address: MemberIdentifier = initial_state.recovery_address().clone().into(); - let new_recovery_address = rand_string(); + let new_recovery_address = rand_hexstring(); let update_recovery = Action::ChangeRecoveryAddress(ChangeRecoveryAddress { new_recovery_address: new_recovery_address.clone(), recovery_address_signature: VerifiedSignature::new( initial_state.recovery_address().clone().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -633,7 +634,7 @@ pub(crate) mod tests { recovery_address_signature: VerifiedSignature::new( initial_recovery_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), revoked_member: initial_recovery_address.clone(), @@ -650,14 +651,14 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn scw_signature_binding() { let initial_chain_id: u64 = 1; - let signer = rand_string(); + let signer = rand_hexstring(); let initial_address_signature = VerifiedSignature::new( signer.clone().into(), SignatureKind::Erc1271, - rand_vec(), + rand_vec::<32>(), Some(initial_chain_id), ); let action = CreateInbox { @@ -675,13 +676,13 @@ pub(crate) mod tests { let inbox_id = initial_state.inbox_id(); let new_chain_id: u64 = 2; - let new_member: MemberIdentifier = rand_vec().into(); + let new_member: MemberIdentifier = rand_vec::<32>().into(); // A signature from the same account address but on a different chain ID let existing_member_sig = VerifiedSignature::new( signer.clone().into(), SignatureKind::Erc1271, - rand_vec(), + rand_vec::<32>(), Some(new_chain_id), ); @@ -691,7 +692,7 @@ pub(crate) mod tests { new_member_signature: VerifiedSignature::new( new_member.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), new_member_identifier: new_member.clone(), @@ -702,7 +703,7 @@ pub(crate) mod tests { }), Action::ChangeRecoveryAddress(ChangeRecoveryAddress { recovery_address_signature: existing_member_sig.clone(), - new_recovery_address: rand_string(), + new_recovery_address: rand_hexstring(), }), ]; diff --git a/xmtp_id/src/associations/serialization.rs b/xmtp_id/src/associations/serialization.rs index fd53a28e9..41e76a0d7 100644 --- a/xmtp_id/src/associations/serialization.rs +++ b/xmtp_id/src/associations/serialization.rs @@ -74,6 +74,8 @@ pub enum DeserializationError { Unspecified(&'static str), #[error("Error creating public key from proto bytes")] Ed25519(#[from] ed25519_dalek::ed25519::Error), + #[error("Unable to deserialize")] + Bincode, } impl TryFrom for UnverifiedIdentityUpdate { @@ -579,21 +581,19 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::associations::{ - hashes::generate_inbox_id, - test_utils::{rand_string, rand_u64, rand_vec}, - }; + use crate::associations::hashes::generate_inbox_id; + use xmtp_common::{rand_hexstring, rand_u64, rand_vec}; use super::*; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn test_round_trip_unverified() { - let account_address = rand_string(); + let account_address = rand_hexstring(); let nonce = rand_u64(); let inbox_id = generate_inbox_id(&account_address, &nonce).unwrap(); let client_timestamp_ns = rand_u64(); - let signature_bytes = rand_vec(); + let signature_bytes = rand_vec::<32>(); let identity_update = UnverifiedIdentityUpdate::new( inbox_id, @@ -614,7 +614,7 @@ pub(crate) mod tests { 4, 5, 6, ]), unsigned_action: UnsignedAddAssociation { - new_member_identifier: rand_string().into(), + new_member_identifier: rand_hexstring().into(), }, }), UnverifiedAction::ChangeRecoveryAddress(UnverifiedChangeRecoveryAddress { @@ -622,7 +622,7 @@ pub(crate) mod tests { 7, 8, 9, ]), unsigned_action: UnsignedChangeRecoveryAddress { - new_recovery_address: rand_string(), + new_recovery_address: rand_hexstring(), }, }), UnverifiedAction::RevokeAssociation(UnverifiedRevokeAssociation { @@ -630,7 +630,7 @@ pub(crate) mod tests { 10, 11, 12, ]), unsigned_action: UnsignedRevokeAssociation { - revoked_member: rand_string().into(), + revoked_member: rand_hexstring().into(), }, }), ], diff --git a/xmtp_id/src/associations/signature.rs b/xmtp_id/src/associations/signature.rs index 863e0044e..90eb98a4d 100644 --- a/xmtp_id/src/associations/signature.rs +++ b/xmtp_id/src/associations/signature.rs @@ -296,8 +296,9 @@ mod tests { use ethers::core::k256::{ecdsa::Signature as K256Signature, elliptic_curve::scalar::IsHigh}; use ethers::signers::{LocalWallet, Signer}; use rand::thread_rng; + use wasm_bindgen_test::wasm_bindgen_test; - #[tokio::test] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn test_to_lower_s() { // Create a test wallet let wallet = LocalWallet::new(&mut thread_rng()); @@ -334,7 +335,7 @@ mod tests { assert!(!is_high, "Normalized signature should have low-s value"); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_invalid_signature() { // Test with invalid signature bytes let invalid_sig = vec![0u8; 65]; diff --git a/xmtp_id/src/associations/state.rs b/xmtp_id/src/associations/state.rs index a086945bb..d26569e1b 100644 --- a/xmtp_id/src/associations/state.rs +++ b/xmtp_id/src/associations/state.rs @@ -74,8 +74,8 @@ impl AssociationState { new_state } - pub fn get(&self, identifier: &MemberIdentifier) -> Option { - self.members.get(identifier).cloned() + pub fn get(&self, identifier: &MemberIdentifier) -> Option<&Member> { + self.members.get(identifier) } pub fn add_seen_signatures(&self, signatures: Vec>) -> Self { @@ -204,14 +204,14 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::associations::test_utils::rand_string; + use xmtp_common::rand_hexstring; use super::*; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn can_add_remove() { - let starting_state = AssociationState::new(rand_string(), 0, None).unwrap(); + let starting_state = AssociationState::new(rand_hexstring(), 0, None).unwrap(); let new_entity = Member::default(); let with_add = starting_state.add(new_entity.clone()); assert!(with_add.get(&new_entity.identifier).is_some()); @@ -221,7 +221,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn can_diff() { - let starting_state = AssociationState::new(rand_string(), 0, None).unwrap(); + let starting_state = AssociationState::new(rand_hexstring(), 0, None).unwrap(); let entity_1 = Member::default(); let entity_2 = Member::default(); let entity_3 = Member::default(); diff --git a/xmtp_id/src/associations/test_utils.rs b/xmtp_id/src/associations/test_utils.rs index 4ab83734d..2f0db1704 100644 --- a/xmtp_id/src/associations/test_utils.rs +++ b/xmtp_id/src/associations/test_utils.rs @@ -11,32 +11,9 @@ use ethers::{ signers::{LocalWallet, Signer}, types::Bytes, }; -use rand::Rng; use xmtp_cryptography::basic_credential::XmtpInstallationCredential; use xmtp_cryptography::CredentialSign; -pub fn rand_string() -> String { - let hex_chars = "0123456789abcdef"; - let v: String = (0..40) - .map(|_| { - let idx = rand::thread_rng().gen_range(0..hex_chars.len()); - hex_chars.chars().nth(idx).unwrap() - }) - .collect(); - - format!("0x{}", v) -} - -pub fn rand_u64() -> u64 { - rand::thread_rng().gen() -} - -pub fn rand_vec() -> Vec { - let mut buf = [0u8; 32]; - rand::thread_rng().fill(&mut buf[..]); - buf.to_vec() -} - #[derive(Debug, Clone)] pub struct MockSmartContractSignatureVerifier { is_valid_signature: bool, diff --git a/xmtp_id/src/associations/unverified.rs b/xmtp_id/src/associations/unverified.rs index a1ac2c849..64f198e0e 100644 --- a/xmtp_id/src/associations/unverified.rs +++ b/xmtp_id/src/associations/unverified.rs @@ -409,9 +409,8 @@ mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::associations::{ - generate_inbox_id, test_utils::rand_string, unsigned_actions::UnsignedCreateInbox, - }; + use crate::associations::{generate_inbox_id, unsigned_actions::UnsignedCreateInbox}; + use xmtp_common::rand_hexstring; use super::{ UnverifiedAction, UnverifiedCreateInbox, UnverifiedIdentityUpdate, @@ -421,7 +420,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn create_identity_update() { - let account_address = rand_string(); + let account_address = rand_hexstring(); let nonce = 1; let update = UnverifiedIdentityUpdate { inbox_id: generate_inbox_id(account_address.as_str(), &nonce).unwrap(), diff --git a/xmtp_id/src/associations/verified_signature.rs b/xmtp_id/src/associations/verified_signature.rs index 3b30dc6e0..255fc3972 100644 --- a/xmtp_id/src/associations/verified_signature.rs +++ b/xmtp_id/src/associations/verified_signature.rs @@ -175,15 +175,15 @@ mod tests { use super::*; use crate::{ associations::{ - sign_with_legacy_key, - test_utils::{rand_string, MockSmartContractSignatureVerifier}, - verified_signature::VerifiedSignature, - InstallationKeyContext, MemberIdentifier, SignatureKind, + sign_with_legacy_key, test_utils::MockSmartContractSignatureVerifier, + verified_signature::VerifiedSignature, InstallationKeyContext, MemberIdentifier, + SignatureKind, }, InboxOwner, }; use ethers::signers::{LocalWallet, Signer}; use prost::Message; + use xmtp_common::rand_hexstring; use xmtp_cryptography::{CredentialSign, XmtpInstallationCredential}; use xmtp_proto::xmtp::message_contents::{ signature::Union as SignatureUnion, signed_private_key, @@ -367,7 +367,7 @@ mod tests { async fn test_smart_contract_wallet() { let mock_verifier = MockSmartContractSignatureVerifier::new(true); let chain_id: u64 = 24; - let account_address = rand_string(); + let account_address = rand_hexstring(); let account_id = AccountId::new(format!("eip155:{chain_id}"), account_address.clone()); let signature_text = "test_smart_contract_wallet_signature"; let signature_bytes = &[1, 2, 3]; diff --git a/xmtp_id/src/utils/mod.rs b/xmtp_id/src/utils/mod.rs index 0acaf32ef..ec59e09c8 100644 --- a/xmtp_id/src/utils/mod.rs +++ b/xmtp_id/src/utils/mod.rs @@ -1,13 +1,2 @@ -use wasm_timer::{SystemTime, UNIX_EPOCH}; #[cfg(any(test, feature = "test-utils"))] pub mod test; - -pub const NS_IN_SEC: i64 = 1_000_000_000; - -pub fn now_ns() -> i64 { - let now = SystemTime::now(); - - now.duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_nanos() as i64 -} diff --git a/xmtp_mls/Cargo.toml b/xmtp_mls/Cargo.toml index 63aa3aba1..ef9d49670 100644 --- a/xmtp_mls/Cargo.toml +++ b/xmtp_mls/Cargo.toml @@ -23,6 +23,9 @@ bench = [ "once_cell", "dep:xmtp_api_grpc", "criterion", + "dep:fdlimit", + "dep:ethers", + "dep:const_format", ] default = ["grpc-api"] grpc-api = ["dep:xmtp_api_grpc"] @@ -35,6 +38,9 @@ test-utils = [ "xmtp_proto/test-utils", "xmtp_api_http/test-utils", "xmtp_api_grpc/test-utils", + "dep:const_format", + "mockall", + "xmtp_common/test-utils" ] update-schema = ["toml"] @@ -46,6 +52,7 @@ bincode.workspace = true diesel_migrations.workspace = true futures.workspace = true hex.workspace = true +hkdf.workspace = true openmls_rust_crypto = { workspace = true } openmls_traits = { workspace = true } parking_lot.workspace = true @@ -54,6 +61,7 @@ rand = { workspace = true } reqwest = { version = "0.12.4", features = ["stream"] } serde = { workspace = true } serde_json.workspace = true +sha2.workspace = true thiserror = { workspace = true } tls_codec = { workspace = true } tokio-stream = { version = "0.1", default-features = false, features = [ @@ -61,16 +69,20 @@ tokio-stream = { version = "0.1", default-features = false, features = [ ] } tracing.workspace = true trait-variant.workspace = true -wasm-timer.workspace = true zeroize.workspace = true +xmtp_common.workspace = true # XMTP/Local +xmtp_content_types = { path = "../xmtp_content_types" } xmtp_cryptography = { workspace = true } xmtp_id = { path = "../xmtp_id" } xmtp_proto = { workspace = true, features = ["convert"] } # Optional/Features console_error_panic_hook = { workspace = true, optional = true } +const_format = { workspace = true, optional = true } +ethers = { workspace = true, features = ["openssl"], optional = true } +fdlimit = { workspace = true, optional = true } toml = { version = "0.8.4", optional = true } tracing-wasm = { version = "0.2", optional = true } xmtp_api_http = { path = "../xmtp_api_http", optional = true } @@ -81,13 +93,17 @@ criterion = { version = "0.5", features = [ "html_reports", "async_tokio", ], optional = true } +hmac = "0.12.1" indicatif = { version = "0.17", optional = true } +mockall = { version = "0.13.1", optional = true } once_cell = { version = "1.19", optional = true } tracing-flame = { version = "0.2", optional = true } tracing-subscriber = { workspace = true, features = [ "env-filter", "fmt", "ansi", + "json", + "registry", ], optional = true } @@ -128,10 +144,13 @@ web-sys.workspace = true [dev-dependencies] anyhow.workspace = true +const_format.workspace = true mockall = "0.13.1" openmls_basic_credential.workspace = true xmtp_id = { path = "../xmtp_id", features = ["test-utils"] } xmtp_proto = { workspace = true, features = ["test-utils"] } +xmtp_common = { workspace = true, features = ["test-utils"]} +wasm-bindgen-test.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] ctor.workspace = true @@ -143,6 +162,7 @@ tracing-subscriber = { workspace = true, features = [ "env-filter", "fmt", "ansi", + "json", ] } xmtp_api_grpc = { path = "../xmtp_api_grpc", features = ["test-utils"] } xmtp_api_http = { path = "../xmtp_api_http", features = ["test-utils"] } @@ -155,7 +175,7 @@ diesel-wasm-sqlite = { workspace = true, features = [ ] } ethers = { workspace = true, features = ["rustls"] } openmls = { workspace = true, features = ["js"] } -tracing-subscriber = { workspace = true, features = ["env-filter"] } +tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } tracing-wasm = { version = "0.2" } wasm-bindgen-test.workspace = true xmtp_api_http = { path = "../xmtp_api_http", features = ["test-utils"] } @@ -175,3 +195,14 @@ required-features = ["bench"] harness = false name = "crypto" required-features = ["bench"] + +[[bench]] +harness = false +name = "identity" +required-features = ["bench"] + + +#[[bench]] +#harness = false +#name = "sync" +#required-features = ["bench"] diff --git a/xmtp_mls/benches/crypto.rs b/xmtp_mls/benches/crypto.rs index 17306e01b..c4f338ba7 100644 --- a/xmtp_mls/benches/crypto.rs +++ b/xmtp_mls/benches/crypto.rs @@ -46,7 +46,6 @@ fn bench_encrypt_welcome(c: &mut Criterion) { let keypair = crypto .derive_hpke_keypair(CIPHERSUITE.hpke_config(), ikm.as_slice()) .unwrap(); - let mut payload = vec![0; size]; OsRng.fill_bytes(payload.as_mut_slice()); (payload, keypair.public) diff --git a/xmtp_mls/benches/group_limit.rs b/xmtp_mls/benches/group_limit.rs index c2c5a6909..ba3027eb3 100755 --- a/xmtp_mls/benches/group_limit.rs +++ b/xmtp_mls/benches/group_limit.rs @@ -4,20 +4,17 @@ //! may be used to generate a flamegraph of execution from tracing logs. use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput}; use std::{collections::HashMap, sync::Arc}; -use tokio::runtime::{Builder, Handle, Runtime}; +use tokio::runtime::{Builder, Runtime}; use tracing::{trace_span, Instrument}; use xmtp_mls::{ builder::ClientBuilder, groups::GroupMetadataOptions, - utils::{ - bench::{create_identities_if_dont_exist, init_logging, Identity, BENCH_ROOT_SPAN}, - test::TestClient, + utils::bench::{ + bench_async_setup, create_identities_if_dont_exist, init_logging, BenchClient, Identity, + BENCH_ROOT_SPAN, }, - Client, }; -pub type BenchClient = Client; - pub const IDENTITY_SAMPLES: [usize; 9] = [10, 20, 40, 80, 100, 200, 300, 400, 450]; pub const MAX_IDENTITIES: usize = 1_000; pub const SAMPLE_SIZE: usize = 10; @@ -52,17 +49,6 @@ fn setup() -> (Arc, Vec, Runtime) { (client, identities, runtime) } -/// criterion `batch_iter` surrounds the closure in a `Runtime.block_on` despite being a sync -/// function, even in the async 'to_async` setup. Therefore we do this (only _slightly_) hacky -/// workaround to allow us to async setup some groups. -fn bench_async_setup(fun: F) -> T -where - F: Fn() -> Fut, - Fut: futures::future::Future, -{ - tokio::task::block_in_place(move || Handle::current().block_on(async move { fun().await })) -} - fn add_to_empty_group(c: &mut Criterion) { init_logging(); let mut benchmark_group = c.benchmark_group("add_to_empty_group"); diff --git a/xmtp_mls/benches/identity.rs b/xmtp_mls/benches/identity.rs new file mode 100644 index 000000000..a7b10709f --- /dev/null +++ b/xmtp_mls/benches/identity.rs @@ -0,0 +1,77 @@ +use crate::tracing::Instrument; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use tokio::runtime::{Builder, Runtime}; +use xmtp_id::{ + associations::{ + builder::SignatureRequest, + unverified::{UnverifiedRecoverableEcdsaSignature, UnverifiedSignature}, + }, + InboxOwner, +}; +use xmtp_mls::utils::bench::{bench_async_setup, BenchClient, BENCH_ROOT_SPAN}; +use xmtp_mls::utils::bench::{clients, init_logging}; + +#[macro_use] +extern crate tracing; + +fn setup() -> Runtime { + Builder::new_multi_thread() + .enable_time() + .enable_io() + .thread_name("xmtp-bencher") + .build() + .unwrap() +} + +async fn ecdsa_signature(client: &BenchClient, owner: impl InboxOwner) -> SignatureRequest { + let mut signature_request = client.context().signature_request().unwrap(); + let signature_text = signature_request.signature_text(); + let unverified_signature = UnverifiedSignature::RecoverableEcdsa( + UnverifiedRecoverableEcdsaSignature::new(owner.sign(&signature_text).unwrap().into()), + ); + signature_request + .add_signature(unverified_signature, client.scw_verifier()) + .await + .unwrap(); + + signature_request +} + +fn register_identity_eoa(c: &mut Criterion) { + init_logging(); + + let runtime = setup(); + + let mut benchmark_group = c.benchmark_group("register_identity"); + benchmark_group.sample_size(10); + benchmark_group.bench_function("register_identity_eoa", |b| { + let span = trace_span!(BENCH_ROOT_SPAN); + b.to_async(&runtime).iter_batched( + || { + bench_async_setup(|| async { + let (client, wallet) = clients::new_unregistered_client(false).await; + let signature_request = ecdsa_signature(&client, wallet).await; + + (client, signature_request, span.clone()) + }) + }, + |(client, request, span)| async move { + client + .register_identity(request) + .instrument(span) + .await + .unwrap() + }, + BatchSize::SmallInput, + ) + }); + + benchmark_group.finish(); +} + +criterion_group!( + name = identity; + config = Criterion::default().sample_size(10); + targets = register_identity_eoa +); +criterion_main!(identity); diff --git a/xmtp_mls/benches/sync.rs b/xmtp_mls/benches/sync.rs new file mode 100644 index 000000000..821a1de20 --- /dev/null +++ b/xmtp_mls/benches/sync.rs @@ -0,0 +1,49 @@ +// //! Benchmarking for syncing functions +// use crate::tracing::Instrument; +// use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +// use tokio::runtime::{Builder, Runtime}; +// use xmtp_mls::utils::bench::{bench_async_setup, BENCH_ROOT_SPAN}; +// use xmtp_mls::utils::bench::{clients, init_logging}; +// +// #[macro_use] +// extern crate tracing; +// +// fn setup() -> Runtime { +// Builder::new_multi_thread() +// .enable_time() +// .enable_io() +// .thread_name("xmtp-bencher") +// .build() +// .unwrap() +// } +// +// fn start_sync_worker(c: &mut Criterion) { +// init_logging(); +// +// let runtime = setup(); +// let mut benchmark_group = c.benchmark_group("start_sync_worker"); +// benchmark_group.sample_size(10); +// benchmark_group.bench_function("start_sync_worker", |b| { +// let span = trace_span!(BENCH_ROOT_SPAN); +// b.to_async(&runtime).iter_batched( +// || { +// bench_async_setup(|| async { +// let client = clients::new_client(true).await; +// // set history sync URL +// (client, span.clone()) +// }) +// }, +// |(client, span)| async move { client.start_sync_worker().instrument(span) }, +// BatchSize::SmallInput, +// ) +// }); +// +// benchmark_group.finish(); +// } + +// criterion_group!( +// name = sync; +// config = Criterion::default().sample_size(10); +// targets = start_sync_worker +// ); +// criterion_main!(sync); diff --git a/xmtp_mls/migrations/2024-12-05-185829_create_user_preferences/down.sql b/xmtp_mls/migrations/2024-12-05-185829_create_user_preferences/down.sql new file mode 100644 index 000000000..f6b21295a --- /dev/null +++ b/xmtp_mls/migrations/2024-12-05-185829_create_user_preferences/down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS "user_preferences"; diff --git a/xmtp_mls/migrations/2024-12-05-185829_create_user_preferences/up.sql b/xmtp_mls/migrations/2024-12-05-185829_create_user_preferences/up.sql new file mode 100644 index 000000000..751210aec --- /dev/null +++ b/xmtp_mls/migrations/2024-12-05-185829_create_user_preferences/up.sql @@ -0,0 +1,6 @@ +CREATE TABLE "user_preferences"( + -- The latest id is the current preference + id INTEGER PRIMARY KEY ASC, + -- HMAC root key + hmac_key BLOB +); diff --git a/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/down.sql b/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/down.sql new file mode 100644 index 000000000..409db041b --- /dev/null +++ b/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE user_preferences DROP COLUMN hmac_key; +ALTER TABLE user_preferences ADD COLUMN hmac_key BLOB; diff --git a/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/up.sql b/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/up.sql new file mode 100644 index 000000000..584d0f375 --- /dev/null +++ b/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE user_preferences DROP COLUMN hmac_key; +ALTER TABLE user_preferences ADD COLUMN hmac_key BLOB NOT NULL; diff --git a/xmtp_mls/migrations/2024-12-11-183736_recreate_user_preferences_with_primary_key/down.sql b/xmtp_mls/migrations/2024-12-11-183736_recreate_user_preferences_with_primary_key/down.sql new file mode 100644 index 000000000..9cf18dcce --- /dev/null +++ b/xmtp_mls/migrations/2024-12-11-183736_recreate_user_preferences_with_primary_key/down.sql @@ -0,0 +1,6 @@ +DROP TABLE user_preferences; + +CREATE TABLE user_preferences( + id INTEGER PRIMARY KEY ASC, + hmac_key BLOB NOT NULL +); diff --git a/xmtp_mls/migrations/2024-12-11-183736_recreate_user_preferences_with_primary_key/up.sql b/xmtp_mls/migrations/2024-12-11-183736_recreate_user_preferences_with_primary_key/up.sql new file mode 100644 index 000000000..910b462a7 --- /dev/null +++ b/xmtp_mls/migrations/2024-12-11-183736_recreate_user_preferences_with_primary_key/up.sql @@ -0,0 +1,6 @@ +DROP TABLE user_preferences; + +CREATE TABLE user_preferences( + id INTEGER PRIMARY KEY ASC NOT NULL, + hmac_key BLOB +); diff --git a/xmtp_mls/src/api/identity.rs b/xmtp_mls/src/api/identity.rs index 02e6be91c..f2ef21a75 100644 --- a/xmtp_mls/src/api/identity.rs +++ b/xmtp_mls/src/api/identity.rs @@ -151,8 +151,9 @@ pub(crate) mod tests { use super::super::test_utils::*; use super::GetIdentityUpdatesV2Filter; - use crate::{api::ApiClientWrapper, retry::Retry}; - use xmtp_id::associations::{test_utils::rand_string, unverified::UnverifiedIdentityUpdate}; + use crate::api::ApiClientWrapper; + use xmtp_common::{rand_hexstring, Retry}; + use xmtp_id::associations::unverified::UnverifiedIdentityUpdate; use xmtp_proto::xmtp::identity::api::v1::{ get_identity_updates_response::{ IdentityUpdateLog, Response as GetIdentityUpdatesResponseItem, @@ -173,7 +174,7 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn publish_identity_update() { let mut mock_api = MockApiClient::new(); - let inbox_id = rand_string(); + let inbox_id = rand_hexstring(); let identity_update = create_identity_update(inbox_id.clone()); mock_api @@ -191,7 +192,7 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_identity_update_v2() { let mut mock_api = MockApiClient::new(); - let inbox_id = rand_string(); + let inbox_id = rand_hexstring(); let inbox_id_clone = inbox_id.clone(); let inbox_id_clone_2 = inbox_id.clone(); mock_api @@ -238,10 +239,10 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_inbox_ids() { let mut mock_api = MockApiClient::new(); - let inbox_id = rand_string(); + let inbox_id = rand_hexstring(); let inbox_id_clone = inbox_id.clone(); let inbox_id_clone_2 = inbox_id.clone(); - let address = rand_string(); + let address = rand_hexstring(); let address_clone = address.clone(); let address_clone_2 = address.clone(); diff --git a/xmtp_mls/src/api/mls.rs b/xmtp_mls/src/api/mls.rs index 01f903546..3994cd8fa 100644 --- a/xmtp_mls/src/api/mls.rs +++ b/xmtp_mls/src/api/mls.rs @@ -1,16 +1,16 @@ use std::collections::HashMap; use super::ApiClientWrapper; -use crate::{retry_async, XmtpApi}; +use crate::XmtpApi; +use xmtp_common::retry_async; use xmtp_proto::api_client::XmtpMlsStreams; use xmtp_proto::xmtp::mls::api::v1::{ - group_message_input::{Version as GroupMessageInputVersion, V1 as GroupMessageInputV1}, subscribe_group_messages_request::Filter as GroupFilterProto, - subscribe_welcome_messages_request::Filter as WelcomeFilterProto, - FetchKeyPackagesRequest, GroupMessage, GroupMessageInput, KeyPackageUpload, PagingInfo, - QueryGroupMessagesRequest, QueryWelcomeMessagesRequest, SendGroupMessagesRequest, - SendWelcomeMessagesRequest, SortDirection, SubscribeGroupMessagesRequest, - SubscribeWelcomeMessagesRequest, UploadKeyPackageRequest, WelcomeMessage, WelcomeMessageInput, + subscribe_welcome_messages_request::Filter as WelcomeFilterProto, FetchKeyPackagesRequest, + GroupMessage, GroupMessageInput, KeyPackageUpload, PagingInfo, QueryGroupMessagesRequest, + QueryWelcomeMessagesRequest, SendGroupMessagesRequest, SendWelcomeMessagesRequest, + SortDirection, SubscribeGroupMessagesRequest, SubscribeWelcomeMessagesRequest, + UploadKeyPackageRequest, WelcomeMessage, WelcomeMessageInput, }; use xmtp_proto::{Error as ApiError, ErrorKind}; @@ -70,6 +70,12 @@ where group_id: Vec, id_cursor: Option, ) -> Result, ApiError> { + tracing::debug!( + group_id = hex::encode(&group_id), + id_cursor, + inbox_id = self.inbox_id, + "query group messages" + ); let mut out: Vec = vec![]; let page_size = 100; let mut id_cursor = id_cursor; @@ -109,11 +115,17 @@ where } #[tracing::instrument(level = "trace", skip_all)] - pub async fn query_welcome_messages( + pub async fn query_welcome_messages + Copy>( &self, - installation_id: Vec, + installation_id: Id, id_cursor: Option, ) -> Result, ApiError> { + tracing::debug!( + installation_id = hex::encode(installation_id), + cursor = id_cursor, + inbox_id = self.inbox_id, + "query welcomes" + ); let mut out: Vec = vec![]; let page_size = 100; let mut id_cursor = id_cursor; @@ -123,7 +135,7 @@ where (async { self.api_client .query_welcome_messages(QueryWelcomeMessagesRequest { - installation_key: installation_id.clone(), + installation_key: installation_id.as_ref().to_vec(), paging_info: Some(PagingInfo { id_cursor: id_cursor.unwrap_or(0), limit: page_size, @@ -162,6 +174,7 @@ where key_package: Vec, is_inbox_id_credential: bool, ) -> Result<(), ApiError> { + tracing::debug!(inbox_id = self.inbox_id, "upload key packages"); retry_async!( self.retry_strategy, (async { @@ -184,6 +197,7 @@ where &self, installation_keys: Vec>, ) -> Result { + tracing::debug!(inbox_id = self.inbox_id, "fetch key packages"); let res = retry_async!( self.retry_strategy, (async { @@ -220,6 +234,7 @@ where &self, messages: &[WelcomeMessageInput], ) -> Result<(), ApiError> { + tracing::debug!(inbox_id = self.inbox_id, "send welcome messages"); retry_async!( self.retry_strategy, (async { @@ -235,23 +250,22 @@ where } #[tracing::instrument(level = "trace", skip_all)] - pub async fn send_group_messages(&self, group_messages: Vec<&[u8]>) -> Result<(), ApiError> { - let to_send: Vec = group_messages - .iter() - .map(|msg| GroupMessageInput { - version: Some(GroupMessageInputVersion::V1(GroupMessageInputV1 { - data: msg.to_vec(), - sender_hmac: vec![], - })), - }) - .collect(); + pub async fn send_group_messages( + &self, + group_messages: Vec, + ) -> Result<(), ApiError> { + tracing::debug!( + inbox_id = self.inbox_id, + "sending [{}] group messages", + group_messages.len() + ); retry_async!( self.retry_strategy, (async { self.api_client .send_group_messages(SendGroupMessagesRequest { - messages: to_send.clone(), + messages: group_messages.clone(), }) .await }) @@ -267,6 +281,7 @@ where where ApiClient: XmtpMlsStreams, { + tracing::debug!(inbox_id = self.inbox_id, "subscribing to group messages"); self.api_client .subscribe_group_messages(SubscribeGroupMessagesRequest { filters: filters.into_iter().map(|f| f.into()).collect(), @@ -276,16 +291,17 @@ where pub async fn subscribe_welcome_messages( &self, - installation_key: Vec, + installation_key: &[u8], id_cursor: Option, ) -> Result> + '_, ApiError> where ApiClient: XmtpMlsStreams, { + tracing::debug!(inbox_id = self.inbox_id, "subscribing to welcome messages"); self.api_client .subscribe_welcome_messages(SubscribeWelcomeMessagesRequest { filters: vec![WelcomeFilterProto { - installation_key, + installation_key: installation_key.to_vec(), id_cursor: id_cursor.unwrap_or(0), }], }) diff --git a/xmtp_mls/src/api/mod.rs b/xmtp_mls/src/api/mod.rs index 64d173c13..e9515058d 100644 --- a/xmtp_mls/src/api/mod.rs +++ b/xmtp_mls/src/api/mod.rs @@ -1,16 +1,14 @@ pub mod identity; pub mod mls; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] pub mod test_utils; use std::sync::Arc; -use crate::{ - retry::{Retry, RetryableError}, - XmtpApi, -}; +use crate::XmtpApi; use thiserror::Error; -use xmtp_id::associations::DeserializationError as AssociationDeserializationError; +use xmtp_common::{Retry, RetryableError}; +use xmtp_id::{associations::DeserializationError as AssociationDeserializationError, InboxId}; use xmtp_proto::Error as ApiError; pub use identity::*; @@ -34,6 +32,7 @@ impl RetryableError for WrappedApiError { pub struct ApiClientWrapper { pub(crate) api_client: Arc, pub(crate) retry_strategy: Retry, + pub(crate) inbox_id: Option, } impl ApiClientWrapper @@ -44,6 +43,13 @@ where Self { api_client, retry_strategy, + inbox_id: None, } } + + /// Attach an InboxId to this API Client Wrapper. + /// Attaches an inbox_id context to tracing logs, useful for debugging + pub(crate) fn attach_inbox_id(&mut self, inbox_id: Option) { + self.inbox_id = inbox_id; + } } diff --git a/xmtp_mls/src/builder.rs b/xmtp_mls/src/builder.rs index 1e0fc216f..f8411744e 100644 --- a/xmtp_mls/src/builder.rs +++ b/xmtp_mls/src/builder.rs @@ -11,10 +11,10 @@ use crate::{ client::Client, identity::{Identity, IdentityStrategy}, identity_updates::load_identity_updates, - retry::Retry, storage::EncryptedMessageStore, - StorageError, XmtpApi, + StorageError, XmtpApi, XmtpOpenMlsProvider, }; +use xmtp_common::Retry; #[derive(Error, Debug)] pub enum ClientBuilderError { @@ -110,8 +110,8 @@ impl ClientBuilder { impl ClientBuilder where - ApiClient: XmtpApi + 'static, - V: SmartContractSignatureVerifier + 'static, + ApiClient: XmtpApi + 'static + Send + Sync, + V: SmartContractSignatureVerifier + 'static + Send + Sync, { /// Build with a custom smart contract wallet verifier pub async fn build_with_verifier(self) -> Result, ClientBuilderError> { @@ -122,7 +122,7 @@ where impl ClientBuilder> where - ApiClient: XmtpApi + 'static, + ApiClient: XmtpApi + 'static + Send + Sync, { /// Build with the default [`RemoteSignatureVerifier`] pub async fn build(self) -> Result, ClientBuilderError> { @@ -163,8 +163,8 @@ async fn inner_build( api_client: Arc, ) -> Result, ClientBuilderError> where - C: XmtpApi + 'static, - V: SmartContractSignatureVerifier + 'static, + C: XmtpApi + 'static + Send + Sync, + V: SmartContractSignatureVerifier + 'static + Send + Sync, { let ClientBuilder { mut store, @@ -186,27 +186,39 @@ where let store = store .take() .ok_or(ClientBuilderError::MissingParameter { parameter: "store" })?; - debug!("Initializing identity"); - + let conn = store.conn()?; + let provider = XmtpOpenMlsProvider::new(conn); let identity = identity_strategy - .initialize_identity(&api_client_wrapper, &store, &scw_verifier) + .initialize_identity(&api_client_wrapper, &provider, &scw_verifier) .await?; + debug!( + inbox_id = identity.inbox_id(), + installation_id = hex::encode(identity.installation_keys.public_bytes()), + "Initialized identity" + ); + // get sequence_id from identity updates and loaded into the DB load_identity_updates( &api_client_wrapper, - &store.conn()?, + provider.conn_ref(), vec![identity.inbox_id.as_str()].as_slice(), ) .await?; - Ok(Client::new( + let client = Client::new( api_client_wrapper, identity, store, scw_verifier, history_sync_url.clone(), - )) + ); + + if history_sync_url.is_some() { + client.start_sync_worker(); + } + + Ok(client) } #[cfg(test)] @@ -218,24 +230,22 @@ pub(crate) mod tests { use crate::api::ApiClientWrapper; use crate::builder::ClientBuilderError; use crate::identity::IdentityError; - use crate::retry::Retry; use crate::utils::test::TestClient; use crate::XmtpApi; - use crate::{ - api::test_utils::*, identity::Identity, storage::identity::StoredIdentity, - utils::test::rand_vec, Store, - }; + use crate::{api::test_utils::*, identity::Identity, storage::identity::StoredIdentity, Store}; + use xmtp_common::{rand_vec, tmp_path, Retry}; use openmls::credentials::{Credential, CredentialType}; use prost::Message; + use xmtp_common::rand_u64; use xmtp_cryptography::utils::{generate_local_wallet, rng}; use xmtp_cryptography::XmtpInstallationCredential; + use xmtp_id::associations::generate_inbox_id; use xmtp_id::associations::test_utils::MockSmartContractSignatureVerifier; use xmtp_id::associations::unverified::{ UnverifiedRecoverableEcdsaSignature, UnverifiedSignature, }; use xmtp_id::associations::ValidatedLegacySignedPublicKey; - use xmtp_id::associations::{generate_inbox_id, test_utils::rand_u64}; use xmtp_id::scw_verifier::SmartContractSignatureVerifier; use xmtp_proto::api_client::XmtpTestClient; use xmtp_proto::xmtp::identity::api::v1::{ @@ -251,7 +261,6 @@ pub(crate) mod tests { use super::{ClientBuilder, IdentityStrategy}; use crate::{ storage::{EncryptedMessageStore, StorageOption}, - utils::test::tmp_path, Client, InboxOwner, }; @@ -340,7 +349,7 @@ pub(crate) mod tests { IdentityStrategyTestCase { strategy: { let (legacy_key, legacy_account_address) = generate_random_legacy_key().await; - IdentityStrategy::CreateIfNotFound( + IdentityStrategy::new( generate_inbox_id(&legacy_account_address, &1).unwrap(), legacy_account_address.clone(), 1, @@ -352,7 +361,7 @@ pub(crate) mod tests { IdentityStrategyTestCase { strategy: { let (legacy_key, legacy_account_address) = generate_random_legacy_key().await; - IdentityStrategy::CreateIfNotFound( + IdentityStrategy::new( generate_inbox_id(&legacy_account_address, &1).unwrap(), legacy_account_address.clone(), 0, @@ -364,7 +373,7 @@ pub(crate) mod tests { IdentityStrategyTestCase { strategy: { let (legacy_key, legacy_account_address) = generate_random_legacy_key().await; - IdentityStrategy::CreateIfNotFound( + IdentityStrategy::new( generate_inbox_id(&legacy_account_address, &0).unwrap(), legacy_account_address.clone(), 0, @@ -377,7 +386,7 @@ pub(crate) mod tests { IdentityStrategyTestCase { strategy: { let account_address = generate_local_wallet().get_address(); - IdentityStrategy::CreateIfNotFound( + IdentityStrategy::new( generate_inbox_id(&account_address, &1).unwrap(), account_address.clone(), 0, @@ -390,7 +399,7 @@ pub(crate) mod tests { strategy: { let nonce = 1; let account_address = generate_local_wallet().get_address(); - IdentityStrategy::CreateIfNotFound( + IdentityStrategy::new( generate_inbox_id(&account_address, &nonce).unwrap(), account_address.clone(), nonce, @@ -403,7 +412,7 @@ pub(crate) mod tests { strategy: { let nonce = 0; let account_address = generate_local_wallet().get_address(); - IdentityStrategy::CreateIfNotFound( + IdentityStrategy::new( generate_inbox_id(&account_address, &nonce).unwrap(), account_address.clone(), nonce, @@ -446,7 +455,7 @@ pub(crate) mod tests { let id = generate_inbox_id(&legacy_account_address, &0).unwrap(); println!("{}", id.len()); - let identity_strategy = IdentityStrategy::CreateIfNotFound( + let identity_strategy = IdentityStrategy::new( generate_inbox_id(&legacy_account_address, &0).unwrap(), legacy_account_address.clone(), 0, @@ -479,7 +488,7 @@ pub(crate) mod tests { assert!(client1.inbox_id() == client2.inbox_id()); assert!(client1.installation_public_key() == client2.installation_public_key()); - let client3 = ClientBuilder::new(IdentityStrategy::CreateIfNotFound( + let client3 = ClientBuilder::new(IdentityStrategy::new( generate_inbox_id(&legacy_account_address, &0).unwrap(), legacy_account_address.to_string(), 0, @@ -495,7 +504,7 @@ pub(crate) mod tests { assert!(client1.inbox_id() == client3.inbox_id()); assert!(client1.installation_public_key() == client3.installation_public_key()); - let client4 = ClientBuilder::new(IdentityStrategy::CreateIfNotFound( + let client4 = ClientBuilder::new(IdentityStrategy::new( generate_inbox_id(&legacy_account_address, &0).unwrap(), legacy_account_address.to_string(), 0, @@ -544,11 +553,10 @@ pub(crate) mod tests { let wrapper = ApiClientWrapper::new(mock_api.into(), Retry::default()); - let identity = - IdentityStrategy::CreateIfNotFound("other_inbox_id".to_string(), address, nonce, None); + let identity = IdentityStrategy::new("other_inbox_id".to_string(), address, nonce, None); assert!(matches!( identity - .initialize_identity(&wrapper, &store, &scw_verifier) + .initialize_identity(&wrapper, &store.mls_provider().unwrap(), &scw_verifier) .await .unwrap_err(), IdentityError::NewIdentity(msg) if msg == "Inbox ID mismatch" @@ -586,10 +594,10 @@ pub(crate) mod tests { let wrapper = ApiClientWrapper::new(mock_api.into(), Retry::default()); - let identity = IdentityStrategy::CreateIfNotFound(inbox_id.clone(), address, nonce, None); + let identity = IdentityStrategy::new(inbox_id.clone(), address, nonce, None); assert!(dbg!( identity - .initialize_identity(&wrapper, &store, &scw_verifier) + .initialize_identity(&wrapper, &store.mls_provider().unwrap(), &scw_verifier) .await ) .is_ok()); @@ -616,7 +624,7 @@ pub(crate) mod tests { let stored: StoredIdentity = (&Identity { inbox_id: inbox_id.clone(), installation_keys: XmtpInstallationCredential::new(), - credential: Credential::new(CredentialType::Basic, rand_vec()), + credential: Credential::new(CredentialType::Basic, rand_vec::<24>()), signature_request: None, is_ready: AtomicBool::new(true), }) @@ -625,9 +633,9 @@ pub(crate) mod tests { stored.store(&store.conn().unwrap()).unwrap(); let wrapper = ApiClientWrapper::new(mock_api.into(), Retry::default()); - let identity = IdentityStrategy::CreateIfNotFound(inbox_id.clone(), address, nonce, None); + let identity = IdentityStrategy::new(inbox_id.clone(), address, nonce, None); assert!(identity - .initialize_identity(&wrapper, &store, &scw_verifier) + .initialize_identity(&wrapper, &store.mls_provider().unwrap(), &scw_verifier) .await .is_ok()); } @@ -653,7 +661,7 @@ pub(crate) mod tests { let stored: StoredIdentity = (&Identity { inbox_id: stored_inbox_id.clone(), installation_keys: Default::default(), - credential: Credential::new(CredentialType::Basic, rand_vec()), + credential: Credential::new(CredentialType::Basic, rand_vec::<24>()), signature_request: None, is_ready: AtomicBool::new(true), }) @@ -665,10 +673,9 @@ pub(crate) mod tests { let wrapper = ApiClientWrapper::new(mock_api.into(), Retry::default()); let inbox_id = "inbox_id".to_string(); - let identity = - IdentityStrategy::CreateIfNotFound(inbox_id.clone(), address.clone(), nonce, None); + let identity = IdentityStrategy::new(inbox_id.clone(), address.clone(), nonce, None); let err = identity - .initialize_identity(&wrapper, &store, &scw_verifier) + .initialize_identity(&wrapper, &store.mls_provider().unwrap(), &scw_verifier) .await .unwrap_err(); @@ -691,7 +698,7 @@ pub(crate) mod tests { let nonce = 1; let inbox_id = generate_inbox_id(&wallet.get_address(), &nonce).unwrap(); - let client_a = Client::builder(IdentityStrategy::CreateIfNotFound( + let client_a = Client::builder(IdentityStrategy::new( inbox_id.clone(), wallet.get_address(), nonce, @@ -707,7 +714,7 @@ pub(crate) mod tests { register_client(&client_a, wallet).await; assert!(client_a.identity().is_ready()); - let keybytes_a = client_a.installation_public_key(); + let keybytes_a = client_a.installation_public_key().to_vec(); drop(client_a); // Reload the existing store and wallet @@ -715,7 +722,7 @@ pub(crate) mod tests { .await .unwrap(); - let client_b = Client::builder(IdentityStrategy::CreateIfNotFound( + let client_b = Client::builder(IdentityStrategy::new( inbox_id, wallet.get_address(), nonce, @@ -727,7 +734,7 @@ pub(crate) mod tests { .build_with_verifier() .await .unwrap(); - let keybytes_b = client_b.installation_public_key(); + let keybytes_b = client_b.installation_public_key().to_vec(); drop(client_b); // Ensure the persistence was used to store the generated keys @@ -739,7 +746,7 @@ pub(crate) mod tests { // EncryptedMessageStore::new_unencrypted(StorageOption::Persistent(tmpdb.clone())) // .unwrap(); - // ClientBuilder::new(IdentityStrategy::CreateIfNotFound( + // ClientBuilder::new(IdentityStrategy::new( // generate_local_wallet().get_address(), // None, // )) @@ -760,7 +767,7 @@ pub(crate) mod tests { .build_with_verifier() .await .unwrap(); - assert_eq!(client_d.installation_public_key(), keybytes_a); + assert_eq!(client_d.installation_public_key().to_vec(), keybytes_a); } /// anvil cannot be used in WebAssembly @@ -801,7 +808,7 @@ pub(crate) mod tests { let account_address = format!("{scw_addr:?}"); let account_id = AccountId::new_evm(anvil_meta.chain_id, account_address.clone()); - let identity_strategy = IdentityStrategy::CreateIfNotFound( + let identity_strategy = IdentityStrategy::new( generate_inbox_id(&account_address, &0).unwrap(), account_address, 0, diff --git a/xmtp_mls/src/client.rs b/xmtp_mls/src/client.rs index d025705d9..6db9747da 100644 --- a/xmtp_mls/src/client.rs +++ b/xmtp_mls/src/client.rs @@ -12,7 +12,6 @@ use openmls::{ messages::Welcome, prelude::tls_codec::{Deserialize, Error as TlsCodecError}, }; -use openmls_traits::OpenMlsProvider; use thiserror::Error; use tokio::sync::broadcast; @@ -31,29 +30,35 @@ use xmtp_proto::xmtp::mls::api::v1::{ GroupMessage, WelcomeMessage, }; +#[cfg(any(test, feature = "test-utils"))] +use crate::groups::device_sync::WorkerHandle; + use crate::{ api::ApiClientWrapper, - groups::{group_permissions::PolicySet, GroupError, GroupMetadataOptions, MlsGroup}, + groups::{ + device_sync::preference_sync::UserPreferenceUpdate, group_permissions::PolicySet, + GroupError, GroupMetadataOptions, MlsGroup, + }, identity::{parse_credential, Identity, IdentityError}, identity_updates::{load_identity_updates, IdentityUpdateError}, - intents::Intents, + intents::ProcessIntentError, mutex_registry::MutexRegistry, - retry::Retry, - retry_async, retryable, - storage::wallet_addresses::WalletEntry, storage::{ consent_record::{ConsentState, ConsentType, StoredConsentRecord}, db_connection::DbConnection, group::{GroupMembershipState, GroupQueryArgs, StoredGroup}, group_message::StoredGroupMessage, refresh_state::EntityKind, + wallet_addresses::WalletEntry, EncryptedMessageStore, StorageError, }, subscriptions::{LocalEventError, LocalEvents}, + types::InstallationId, verified_key_package_v2::{KeyPackageVerificationError, VerifiedKeyPackageV2}, xmtp_openmls_provider::XmtpOpenMlsProvider, Fetch, Store, XmtpApi, }; +use xmtp_common::{retry_async, retryable, Retry}; /// Enum representing the network the Client is connected to #[derive(Clone, Copy, Default, Debug)] @@ -109,7 +114,7 @@ impl From for ClientError { } } -impl crate::retry::RetryableError for ClientError { +impl xmtp_common::RetryableError for ClientError { fn is_retryable(&self) -> bool { match self { ClientError::Group(group_error) => retryable!(group_error), @@ -137,12 +142,14 @@ impl From<&str> for ClientError { /// Clients manage access to the network, identity, and data store pub struct Client> { pub(crate) api_client: Arc>, - pub(crate) intents: Arc, pub(crate) context: Arc, pub(crate) history_sync_url: Option, pub(crate) local_events: broadcast::Sender>, /// The method of verifying smart contract wallet signatures for this Client pub(crate) scw_verifier: Arc, + + #[cfg(any(test, feature = "test-utils"))] + pub(crate) sync_worker_handle: Arc>>>, } // most of these things are `Arc`'s @@ -154,7 +161,9 @@ impl Clone for Client { history_sync_url: self.history_sync_url.clone(), local_events: self.local_events.clone(), scw_verifier: self.scw_verifier.clone(), - intents: self.intents.clone(), + + #[cfg(any(test, feature = "test-utils"))] + sync_worker_handle: self.sync_worker_handle.clone(), } } } @@ -172,8 +181,8 @@ pub struct XmtpMlsLocalContext { impl XmtpMlsLocalContext { /// The installation public key is the primary identifier for an installation - pub fn installation_public_key(&self) -> Vec { - self.identity.installation_keys.public_slice().to_vec() + pub fn installation_public_key(&self) -> InstallationId { + (*self.identity.installation_keys.public_bytes()).into() } /// Get the account address of the blockchain account associated with this client @@ -191,7 +200,7 @@ impl XmtpMlsLocalContext { } /// Pulls a new database connection and creates a new provider - pub fn mls_provider(&self) -> Result { + pub fn mls_provider(&self) -> Result { Ok(self.store.conn()?.into()) } @@ -218,7 +227,7 @@ where /// It is expected that most users will use the [`ClientBuilder`](crate::builder::ClientBuilder) instead of instantiating /// a client directly. pub fn new( - api_client: ApiClientWrapper, + mut api_client: ApiClientWrapper, identity: Identity, store: EncryptedMessageStore, scw_verifier: V, @@ -227,14 +236,12 @@ where where V: SmartContractSignatureVerifier, { + api_client.attach_inbox_id(Some(identity.inbox_id().to_string())); let context = Arc::new(XmtpMlsLocalContext { identity, store, mutexes: MutexRegistry::new(), }); - let intents = Arc::new(Intents { - context: context.clone(), - }); let (tx, _) = broadcast::channel(32); Self { @@ -242,8 +249,9 @@ where context, history_sync_url, local_events: tx, + #[cfg(any(test, feature = "test-utils"))] + sync_worker_handle: Arc::new(parking_lot::Mutex::default()), scw_verifier: scw_verifier.into(), - intents, } } @@ -252,13 +260,32 @@ where } } +impl Client +where + ApiClient: XmtpApi + Send + Sync + 'static, + V: SmartContractSignatureVerifier + Send + Sync + 'static, +{ + /// Reconnect to the client's database if it has previously been released + pub fn reconnect_db(&self) -> Result<(), ClientError> { + self.context.store.reconnect()?; + // restart all the workers + // TODO: The only worker we have right now are the + // sync workers. if we have other workers we + // should create a better way to track them. + if self.history_sync_url.is_some() { + self.start_sync_worker(); + } + Ok(()) + } +} + impl Client where ApiClient: XmtpApi, V: SmartContractSignatureVerifier, { /// Retrieves the client's installation public key, sometimes also called `installation_id` - pub fn installation_public_key(&self) -> Vec { + pub fn installation_public_key(&self) -> InstallationId { self.context.installation_public_key() } /// Retrieves the client's inbox ID @@ -266,12 +293,8 @@ where self.context.inbox_id() } - pub fn intents(&self) -> &Arc { - &self.intents - } - /// Pulls a connection and creates a new MLS Provider - pub fn mls_provider(&self) -> Result { + pub fn mls_provider(&self) -> Result { self.context.mls_provider() } @@ -282,9 +305,10 @@ where /// Calls the server to look up the `inbox_id` associated with a given address pub async fn find_inbox_id_from_address( &self, + conn: &DbConnection, address: String, ) -> Result, ClientError> { - let results = self.find_inbox_ids_from_addresses(&[address]).await?; + let results = self.find_inbox_ids_from_addresses(conn, &[address]).await?; if let Some(first_result) = results.into_iter().next() { Ok(first_result) } else { @@ -294,12 +318,12 @@ where /// Calls the server to look up the `inbox_id`s` associated with a list of addresses. /// If no `inbox_id` is found, returns None. - pub async fn find_inbox_ids_from_addresses( + pub(crate) async fn find_inbox_ids_from_addresses( &self, + conn: &DbConnection, addresses: &[String], ) -> Result>, ClientError> { let sanitized_addresses = sanitize_evm_addresses(addresses)?; - let conn = self.store().conn()?; let local_results: Vec = conn.fetch_wallets_list_with_key(&sanitized_addresses)?; @@ -333,7 +357,7 @@ where inbox_id: InboxId::from(inbox_id), wallet_address: address, }; - new_entry.store(&conn).ok(); + new_entry.store(conn).ok(); } let inbox_ids: Vec> = sanitized_addresses @@ -365,19 +389,14 @@ where } /// Get the [`AssociationState`] for each `inbox_id` - pub async fn inbox_addresses>( + pub async fn inbox_addresses<'a>( &self, refresh_from_network: bool, - inbox_ids: Vec, + inbox_ids: Vec>, ) -> Result, ClientError> { let conn = self.store().conn()?; if refresh_from_network { - load_identity_updates( - &self.api_client, - &conn, - &inbox_ids.iter().map(|s| s.as_ref()).collect::>(), - ) - .await?; + load_identity_updates(&self.api_client, &conn, &inbox_ids).await?; } let state = self .batch_get_association_state( @@ -408,7 +427,7 @@ where } let inbox_ids = self - .find_inbox_ids_from_addresses(&addresses_to_lookup) + .find_inbox_ids_from_addresses(&conn, &addresses_to_lookup) .await?; for (i, inbox_id_opt) in inbox_ids.into_iter().enumerate() { @@ -428,9 +447,13 @@ where if self.history_sync_url.is_some() { let mut records = records.to_vec(); records.append(&mut new_records); - self.local_events - .send(LocalEvents::OutgoingConsentUpdates(records)) - .map_err(|e| ClientError::Generic(e.to_string()))?; + let records = records + .into_iter() + .map(UserPreferenceUpdate::ConsentUpdate) + .collect(); + let _ = self + .local_events + .send(LocalEvents::OutgoingPreferenceUpdates(records)); } Ok(()) @@ -444,7 +467,10 @@ where ) -> Result { let conn = self.store().conn()?; let record = if entity_type == ConsentType::Address { - if let Some(inbox_id) = self.find_inbox_id_from_address(entity.clone()).await? { + if let Some(inbox_id) = self + .find_inbox_id_from_address(&conn, entity.clone()) + .await? + { conn.get_consent_record(inbox_id, ConsentType::InboxId)? } else { conn.get_consent_record(entity, entity_type)? @@ -471,11 +497,6 @@ where Ok(()) } - /// Reconnect to the client's database if it has previously been released - pub fn reconnect_db(&self) -> Result<(), ClientError> { - self.context.store.reconnect()?; - Ok(()) - } /// Get a reference to the client's identity struct pub fn identity(&self) -> &Identity { &self.context.identity @@ -494,9 +515,10 @@ where opts: GroupMetadataOptions, ) -> Result, ClientError> { tracing::info!("creating group"); - + let provider = self.mls_provider()?; let group: MlsGroup> = MlsGroup::create_and_insert( Arc::new(self.clone()), + &provider, GroupMembershipState::Allowed, permissions_policy_set.unwrap_or_default(), opts, @@ -526,9 +548,10 @@ where /// Create a new Direct Message with the default settings pub async fn create_dm(&self, account_address: String) -> Result, ClientError> { tracing::info!("creating dm with address: {}", account_address); + let provider = self.mls_provider()?; let inbox_id = match self - .find_inbox_id_from_address(account_address.clone()) + .find_inbox_id_from_address(provider.conn_ref(), account_address.clone()) .await? { Some(id) => id, @@ -540,23 +563,26 @@ where } }; - self.create_dm_by_inbox_id(inbox_id).await + self.create_dm_by_inbox_id(&provider, inbox_id).await } /// Create a new Direct Message with the default settings - pub async fn create_dm_by_inbox_id( + pub(crate) async fn create_dm_by_inbox_id( &self, + provider: &XmtpOpenMlsProvider, dm_target_inbox_id: InboxId, ) -> Result, ClientError> { tracing::info!("creating dm with {}", dm_target_inbox_id); - let group: MlsGroup> = MlsGroup::create_dm_and_insert( + provider, Arc::new(self.clone()), GroupMembershipState::Allowed, dm_target_inbox_id.clone(), )?; - group.add_members_by_inbox_id(&[dm_target_inbox_id]).await?; + group + .add_members_by_inbox_id_with_provider(provider, &[dm_target_inbox_id]) + .await?; // notify any streams of the new group let _ = self.local_events.send(LocalEvents::NewGroup(group.clone())); @@ -564,20 +590,25 @@ where Ok(group) } - pub(crate) fn create_sync_group(&self) -> Result, ClientError> { + pub(crate) fn create_sync_group( + &self, + provider: &XmtpOpenMlsProvider, + ) -> Result, ClientError> { tracing::info!("creating sync group"); - let sync_group = MlsGroup::create_and_insert_sync_group(Arc::new(self.clone()))?; + let sync_group = MlsGroup::create_and_insert_sync_group(Arc::new(self.clone()), provider)?; Ok(sync_group) } - /** - * Look up a group by its ID - * - * Returns a [`MlsGroup`] if the group exists, or an error if it does not - */ - pub fn group(&self, group_id: Vec) -> Result, ClientError> { - let conn = &mut self.store().conn()?; + /// Look up a group by its ID + /// + /// Returns a [`MlsGroup`] if the group exists, or an error if it does not + /// + pub fn group_with_conn( + &self, + conn: &DbConnection, + group_id: Vec, + ) -> Result, ClientError> { let stored_group: Option = conn.fetch(&group_id)?; match stored_group { Some(group) => Ok(MlsGroup::new(self.clone(), group.id, group.created_at_ns)), @@ -588,6 +619,15 @@ where } } + /// Look up a group by its ID + /// + /// Returns a [`MlsGroup`] if the group exists, or an error if it does not + /// + pub fn group(&self, group_id: Vec) -> Result, ClientError> { + let conn = &mut self.store().conn()?; + self.group_with_conn(conn, group_id) + } + /** * Look up a DM group by the target's inbox_id. * @@ -659,18 +699,25 @@ where .await?; self.apply_signature_request(signature_request).await?; - + self.identity().set_ready(); Ok(()) } /// Upload a new key package to the network replacing an existing key package /// This is expected to be run any time the client receives new Welcome messages - pub async fn rotate_key_package(&self) -> Result<(), ClientError> { + pub async fn rotate_key_package( + &self, + provider: &XmtpOpenMlsProvider, + ) -> Result<(), ClientError> { self.store() - .transaction_async(|provider| async move { - self.identity() - .rotate_key_package(&provider, &self.api_client) - .await + .transaction_async(provider, move |provider| { + let provider = &provider; + async { + self.identity() + .rotate_key_package(provider, &self.api_client) + .await?; + Ok::<_, IdentityError>(()) + } }) .await?; @@ -701,11 +748,11 @@ where conn: &DbConnection, ) -> Result, ClientError> { let installation_id = self.installation_public_key(); - let id_cursor = conn.get_last_cursor_for_id(&installation_id, EntityKind::Welcome)?; + let id_cursor = conn.get_last_cursor_for_id(installation_id, EntityKind::Welcome)?; let welcomes = self .api_client - .query_welcome_messages(installation_id, Some(id_cursor as u64)) + .query_welcome_messages(installation_id.as_ref(), Some(id_cursor as u64)) .await?; Ok(welcomes) @@ -719,22 +766,22 @@ where ) -> Result, ClientError> { let key_package_results = self.api_client.fetch_key_packages(installation_ids).await?; - let mls_provider = self.mls_provider()?; + let crypto_provider = XmtpOpenMlsProvider::new_crypto(); Ok(key_package_results .values() - .map(|bytes| VerifiedKeyPackageV2::from_bytes(mls_provider.crypto(), bytes.as_slice())) + .map(|bytes| VerifiedKeyPackageV2::from_bytes(&crypto_provider, bytes.as_slice())) .collect::>()?) } /// Download all unread welcome messages and converts to a group struct, ignoring malformed messages. /// Returns any new groups created in the operation + #[tracing::instrument(level = "debug", skip_all)] pub async fn sync_welcomes( &self, - conn: &DbConnection, + provider: &XmtpOpenMlsProvider, ) -> Result>, ClientError> { - let envelopes = self.query_welcome_messages(conn).await?; + let envelopes = self.query_welcome_messages(provider.conn_ref()).await?; let num_envelopes = envelopes.len(); - let id = self.installation_public_key(); let groups: Vec> = stream::iter(envelopes.into_iter()) .filter_map(|envelope: WelcomeMessage| async { @@ -747,73 +794,86 @@ where }; retry_async!( Retry::default(), - (async { - let welcome_v1 = &welcome_v1; - self.intents.process_for_id( - &id, - EntityKind::Welcome, - welcome_v1.id, - |provider| async move { - let result = MlsGroup::create_from_encrypted_welcome( - Arc::new(self.clone()), - &provider, - welcome_v1.hpke_public_key.as_slice(), - &welcome_v1.data, - welcome_v1.id as i64, - ) - .await; - - match result { - Ok(mls_group) => Ok(Some(mls_group)), - Err(err) => { - use crate::StorageError::*; - use crate::DuplicateItem::*; - - if matches!(err, GroupError::Storage(Duplicate(WelcomeId(_)))) { - tracing::warn!("failed to create group from welcome due to duplicate welcome ID: {}", err); - } else { - tracing::error!("failed to create group from welcome: {}", err); - } - - Err(err) - } - } - }, - ) - .await - }) + (async { self.process_new_welcome(provider, &welcome_v1).await }) ) .ok() - .flatten() }) .collect() .await; // If any welcomes were found, rotate your key package if num_envelopes > 0 { - self.rotate_key_package().await?; + self.rotate_key_package(provider).await?; } Ok(groups) } + /// Internal API to process a unread welcome message and convert to a group. + /// In a database transaction, increments the cursor for a given installation and + /// applies the update after the welcome processed succesfully. + async fn process_new_welcome( + &self, + provider: &XmtpOpenMlsProvider, + welcome: &WelcomeMessageV1, + ) -> Result, GroupError> { + self.store() + .transaction_async(provider, |provider| async move { + let cursor = welcome.id; + let is_updated = provider.conn_ref().update_cursor( + self.installation_public_key(), + EntityKind::Welcome, + welcome.id as i64, + )?; + if !is_updated { + return Err(ProcessIntentError::AlreadyProcessed(cursor).into()); + } + let result = MlsGroup::create_from_encrypted_welcome( + Arc::new(self.clone()), + provider, + welcome.hpke_public_key.as_slice(), + &welcome.data, + welcome.id as i64, + ) + .await; + + match result { + Ok(mls_group) => Ok(mls_group), + Err(err) => { + use crate::DuplicateItem::*; + use crate::StorageError::*; + + if matches!(err, GroupError::Storage(Duplicate(WelcomeId(_)))) { + tracing::warn!( + "failed to create group from welcome due to duplicate welcome ID: {}", + err + ); + } else { + tracing::error!("failed to create group from welcome: {}", err); + } + + Err(err) + } + } + }) + .await + } + /// Sync all groups for the current installation and return the number of groups that were synced. /// Only active groups will be synced. - pub async fn sync_all_groups(&self, groups: Vec>) -> Result { - // Acquire a single connection to be reused - let provider: XmtpOpenMlsProvider = self.mls_provider()?; - + pub async fn sync_all_groups( + &self, + groups: Vec>, + provider: &XmtpOpenMlsProvider, + ) -> Result { let active_group_count = Arc::new(AtomicUsize::new(0)); let sync_futures = groups .into_iter() .map(|group| { - // create new provider ref that gets moved, leaving original - // provider alone. - let provider_ref = &provider; let active_group_count = Arc::clone(&active_group_count); async move { - let mls_group = group.load_mls_group(provider_ref)?; + let mls_group = group.load_mls_group(provider)?; tracing::info!( inbox_id = self.inbox_id(), "[{}] syncing group", @@ -827,9 +887,9 @@ where mls_group.epoch() ); if mls_group.is_active() { - group.maybe_update_installations(provider_ref, None).await?; + group.maybe_update_installations(provider, None).await?; - group.sync_with_conn(provider_ref).await?; + group.sync_with_conn(provider).await?; active_group_count.fetch_add(1, Ordering::SeqCst); } @@ -847,6 +907,30 @@ where Ok(active_group_count.load(Ordering::SeqCst)) } + /// Sync all unread welcome messages and then sync all groups. + /// Returns the total number of active groups synced. + pub async fn sync_all_welcomes_and_groups( + &self, + provider: &XmtpOpenMlsProvider, + consent_state: Option, + ) -> Result { + self.sync_welcomes(provider).await?; + let query_args = GroupQueryArgs { + consent_state, + include_sync_groups: true, + ..GroupQueryArgs::default() + }; + let groups = provider + .conn_ref() + .find_groups(query_args)? + .into_iter() + .map(|g| MlsGroup::new(self.clone(), g.id, g.created_at_ns)) + .collect(); + let active_groups_count = self.sync_all_groups(groups, provider).await?; + + Ok(active_groups_count) + } + /** * Validates a credential against the given installation public key * @@ -1011,17 +1095,20 @@ pub(crate) mod tests { // Get original KeyPackage. let kp1 = client - .get_key_packages_for_installation_ids(vec![client.installation_public_key()]) + .get_key_packages_for_installation_ids(vec![client.installation_public_key().to_vec()]) .await .unwrap(); assert_eq!(kp1.len(), 1); let init1 = kp1[0].inner.hpke_init_key(); // Rotate and fetch again. - client.rotate_key_package().await.unwrap(); + client + .rotate_key_package(&client.mls_provider().unwrap()) + .await + .unwrap(); let kp2 = client - .get_key_packages_for_installation_ids(vec![client.installation_public_key()]) + .get_key_packages_for_installation_ids(vec![client.installation_public_key().to_vec()]) .await .unwrap(); assert_eq!(kp2.len(), 1); @@ -1054,7 +1141,7 @@ pub(crate) mod tests { let client = ClientBuilder::new_test_client(&wallet).await; assert_eq!( client - .find_inbox_id_from_address(wallet.get_address()) + .find_inbox_id_from_address(&client.store().conn().unwrap(), wallet.get_address()) .await .unwrap(), Some(client.inbox_id().to_string()) @@ -1079,7 +1166,7 @@ pub(crate) mod tests { .unwrap(); let bob_received_groups = bob - .sync_welcomes(&bob.store().conn().unwrap()) + .sync_welcomes(&bob.mls_provider().unwrap()) .await .unwrap(); assert_eq!(bob_received_groups.len(), 1); @@ -1089,7 +1176,7 @@ pub(crate) mod tests { ); let duplicate_received_groups = bob - .sync_welcomes(&bob.store().conn().unwrap()) + .sync_welcomes(&bob.mls_provider().unwrap()) .await .unwrap(); assert_eq!(duplicate_received_groups.len(), 0); @@ -1119,7 +1206,7 @@ pub(crate) mod tests { .await .unwrap(); - let bob_received_groups = bo.sync_welcomes(&bo.store().conn().unwrap()).await.unwrap(); + let bob_received_groups = bo.sync_welcomes(&bo.mls_provider().unwrap()).await.unwrap(); assert_eq!(bob_received_groups.len(), 2); let bo_groups = bo.find_groups(GroupQueryArgs::default()).unwrap(); @@ -1138,7 +1225,9 @@ pub(crate) mod tests { .await .unwrap(); - bo.sync_all_groups(bo_groups).await.unwrap(); + bo.sync_all_groups(bo_groups, &bo.mls_provider().unwrap()) + .await + .unwrap(); let bo_messages1 = bo_group1.find_messages(&MsgQueryArgs::default()).unwrap(); assert_eq!(bo_messages1.len(), 1); @@ -1147,6 +1236,115 @@ pub(crate) mod tests { assert_eq!(bo_messages2.len(), 1); } + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr( + not(target_arch = "wasm32"), + tokio::test(flavor = "multi_thread", worker_threads = 2) + )] + async fn test_sync_all_groups_and_welcomes() { + let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; + let bo = ClientBuilder::new_test_client(&generate_local_wallet()).await; + + // Create two groups and add Bob + let alix_bo_group1 = alix + .create_group(None, GroupMetadataOptions::default()) + .unwrap(); + let alix_bo_group2 = alix + .create_group(None, GroupMetadataOptions::default()) + .unwrap(); + + alix_bo_group1 + .add_members_by_inbox_id(&[bo.inbox_id()]) + .await + .unwrap(); + alix_bo_group2 + .add_members_by_inbox_id(&[bo.inbox_id()]) + .await + .unwrap(); + + // Initial sync (None): Bob should fetch both groups + let bob_received_groups = bo + .sync_all_welcomes_and_groups(&bo.mls_provider().unwrap(), None) + .await + .unwrap(); + assert_eq!(bob_received_groups, 2); + + // Verify Bob initially has no messages + let bo_group1 = bo.group(alix_bo_group1.group_id.clone()).unwrap(); + assert_eq!( + bo_group1 + .find_messages(&MsgQueryArgs::default()) + .unwrap() + .len(), + 0 + ); + let bo_group2 = bo.group(alix_bo_group2.group_id.clone()).unwrap(); + assert_eq!( + bo_group2 + .find_messages(&MsgQueryArgs::default()) + .unwrap() + .len(), + 0 + ); + + // Alix sends a message to both groups + alix_bo_group1 + .send_message(vec![1, 2, 3].as_slice()) + .await + .unwrap(); + alix_bo_group2 + .send_message(vec![4, 5, 6].as_slice()) + .await + .unwrap(); + + // Sync with `Unknown`: Bob should not fetch new messages + let bob_received_groups_unknown = bo + .sync_all_welcomes_and_groups(&bo.mls_provider().unwrap(), Some(ConsentState::Allowed)) + .await + .unwrap(); + assert_eq!(bob_received_groups_unknown, 0); + + // Verify Bob still has no messages + assert_eq!( + bo_group1 + .find_messages(&MsgQueryArgs::default()) + .unwrap() + .len(), + 0 + ); + assert_eq!( + bo_group2 + .find_messages(&MsgQueryArgs::default()) + .unwrap() + .len(), + 0 + ); + + // Alix sends another message to both groups + alix_bo_group1 + .send_message(vec![7, 8, 9].as_slice()) + .await + .unwrap(); + alix_bo_group2 + .send_message(vec![10, 11, 12].as_slice()) + .await + .unwrap(); + + // Sync with `None`: Bob should fetch all messages + let bob_received_groups_all = bo + .sync_all_welcomes_and_groups(&bo.mls_provider().unwrap(), Some(ConsentState::Unknown)) + .await + .unwrap(); + assert_eq!(bob_received_groups_all, 2); + + // Verify Bob now has all messages + let bo_messages1 = bo_group1.find_messages(&MsgQueryArgs::default()).unwrap(); + assert_eq!(bo_messages1.len(), 2); + + let bo_messages2 = bo_group2.find_messages(&MsgQueryArgs::default()).unwrap(); + assert_eq!(bo_messages2.len(), 2); + } + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr( not(target_arch = "wasm32"), @@ -1195,7 +1393,7 @@ pub(crate) mod tests { assert_eq!(amal_group.members().await.unwrap().len(), 1); tracing::info!("Syncing bolas welcomes"); // See if Bola can see that they were added to the group - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(Default::default()).unwrap(); @@ -1216,7 +1414,7 @@ pub(crate) mod tests { .add_members_by_inbox_id(&[bola.inbox_id()]) .await .unwrap(); - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); @@ -1266,12 +1464,13 @@ pub(crate) mod tests { async fn get_key_package_init_key< ApiClient: XmtpApi, Verifier: SmartContractSignatureVerifier, + Id: AsRef<[u8]>, >( client: &Client, - installation_id: &[u8], + installation_id: Id, ) -> Vec { let kps = client - .get_key_packages_for_installation_ids(vec![installation_id.to_vec()]) + .get_key_packages_for_installation_ids(vec![installation_id.as_ref().to_vec()]) .await .unwrap(); let kp = kps.first().unwrap(); @@ -1289,9 +1488,9 @@ pub(crate) mod tests { let bo_store = bo.store(); let alix_original_init_key = - get_key_package_init_key(&alix, &alix.installation_public_key()).await; + get_key_package_init_key(&alix, alix.installation_public_key()).await; let bo_original_init_key = - get_key_package_init_key(&bo, &bo.installation_public_key()).await; + get_key_package_init_key(&bo, bo.installation_public_key()).await; // Bo's original key should be deleted let bo_original_from_db = bo_store @@ -1308,21 +1507,21 @@ pub(crate) mod tests { .await .unwrap(); - bo.sync_welcomes(&bo.store().conn().unwrap()).await.unwrap(); + bo.sync_welcomes(&bo.mls_provider().unwrap()).await.unwrap(); - let bo_new_key = get_key_package_init_key(&bo, &bo.installation_public_key()).await; + let bo_new_key = get_key_package_init_key(&bo, bo.installation_public_key()).await; // Bo's key should have changed assert_ne!(bo_original_init_key, bo_new_key); - bo.sync_welcomes(&bo.store().conn().unwrap()).await.unwrap(); - let bo_new_key_2 = get_key_package_init_key(&bo, &bo.installation_public_key()).await; + bo.sync_welcomes(&bo.mls_provider().unwrap()).await.unwrap(); + let bo_new_key_2 = get_key_package_init_key(&bo, bo.installation_public_key()).await; // Bo's key should not have changed syncing the second time. assert_eq!(bo_new_key, bo_new_key_2); - alix.sync_welcomes(&alix.store().conn().unwrap()) + alix.sync_welcomes(&alix.mls_provider().unwrap()) .await .unwrap(); - let alix_key_2 = get_key_package_init_key(&alix, &alix.installation_public_key()).await; + let alix_key_2 = get_key_package_init_key(&alix, alix.installation_public_key()).await; // Alix's key should not have changed at all assert_eq!(alix_original_init_key, alix_key_2); @@ -1333,7 +1532,7 @@ pub(crate) mod tests { ) .await .unwrap(); - bo.sync_welcomes(&bo.store().conn().unwrap()).await.unwrap(); + bo.sync_welcomes(&bo.mls_provider().unwrap()).await.unwrap(); // Bo should have two groups now let bo_groups = bo.find_groups(GroupQueryArgs::default()).unwrap(); diff --git a/xmtp_mls/src/configuration.rs b/xmtp_mls/src/configuration.rs index 9b36d7045..d8fa3ea65 100644 --- a/xmtp_mls/src/configuration.rs +++ b/xmtp_mls/src/configuration.rs @@ -57,3 +57,4 @@ pub const DEFAULT_GROUP_PINNED_FRAME_URL: &str = ""; // If a metadata field name starts with this character, // and it does not have a policy set, it is a super admin only field pub const SUPER_ADMIN_METADATA_PREFIX: &str = "_"; +pub(crate) const HMAC_SALT: &[u8] = b"libXMTP HKDF salt!"; diff --git a/xmtp_mls/src/groups/device_sync.rs b/xmtp_mls/src/groups/device_sync.rs index 872a2e53e..65f233256 100644 --- a/xmtp_mls/src/groups/device_sync.rs +++ b/xmtp_mls/src/groups/device_sync.rs @@ -1,11 +1,9 @@ use super::{GroupError, MlsGroup}; use crate::configuration::NS_IN_HOUR; -use crate::retry::{RetryBuilder, RetryableError}; use crate::storage::group::{ConversationType, GroupQueryArgs}; use crate::storage::group_message::MsgQueryArgs; use crate::storage::DbConnection; use crate::subscriptions::{LocalEvents, StreamMessages, SubscribeError, SyncMessage}; -use crate::utils::time::now_ns; use crate::xmtp_openmls_provider::XmtpOpenMlsProvider; use crate::{ client::ClientError, @@ -15,23 +13,28 @@ use crate::{ group_message::{GroupMessageKind, StoredGroupMessage}, StorageError, }, - Client, + Client, Store, }; -use crate::{retry_async, Store}; use aes_gcm::aead::generic_array::GenericArray; use aes_gcm::{ aead::{Aead, KeyInit}, Aes256Gcm, }; -use futures::{pin_mut, Stream, StreamExt}; -use rand::{ - distributions::{Alphanumeric, DistString}, - Rng, RngCore, -}; +use futures::{Stream, StreamExt}; +use preference_sync::UserPreferenceUpdate; +use rand::{Rng, RngCore}; use serde::{Deserialize, Serialize}; -use std::time::Duration; +use std::future::Future; +use std::pin::Pin; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; use thiserror::Error; -use tracing::warn; +use tokio::sync::{Notify, OnceCell}; +use tokio::time::error::Elapsed; +use tokio::time::timeout; +use tracing::{instrument, warn}; +use xmtp_common::time::{now_ns, Duration}; +use xmtp_common::{retry_async, Retry, RetryableError}; use xmtp_cryptography::utils as crypto_utils; use xmtp_id::scw_verifier::SmartContractSignatureVerifier; use xmtp_proto::api_client::trait_impls::XmtpApi; @@ -47,6 +50,7 @@ use xmtp_proto::xmtp::mls::message_contents::{ pub mod consent_sync; pub mod message_sync; +pub mod preference_sync; pub const ENC_KEY_SIZE: usize = 32; // 256-bit key pub const NONCE_SIZE: usize = 12; // 96-bit nonce @@ -105,6 +109,8 @@ pub enum DeviceSyncError { SyncPayloadTooOld, #[error(transparent)] Subscribe(#[from] SubscribeError), + #[error(transparent)] + Bincode(#[from] bincode::Error), } impl RetryableError for DeviceSyncError { @@ -113,149 +119,315 @@ impl RetryableError for DeviceSyncError { } } +#[cfg(any(test, feature = "test-utils"))] +impl Client { + pub fn sync_worker_handle(&self) -> Option> { + self.sync_worker_handle.lock().clone() + } + + pub(crate) fn set_sync_worker_handle(&self, handle: Arc) { + *self.sync_worker_handle.lock() = Some(handle); + } +} + impl Client where ApiClient: XmtpApi + Send + Sync + 'static, V: SmartContractSignatureVerifier + Send + Sync + 'static, { - pub async fn start_sync_worker( - &self, - provider: &XmtpOpenMlsProvider, - ) -> Result<(), DeviceSyncError> { - self.sync_init(provider).await?; + #[instrument(level = "trace", skip_all)] + pub fn start_sync_worker(&self) { + let client = self.clone(); + tracing::debug!( + inbox_id = client.inbox_id(), + installation_id = hex::encode(client.installation_public_key()), + "starting sync worker" + ); + + let worker = SyncWorker::new(client); + #[cfg(any(test, feature = "test-utils"))] + self.set_sync_worker_handle(worker.handle.clone()); + worker.spawn_worker(); + } +} - crate::spawn(None, { - let client = self.clone(); +pub struct SyncWorker { + client: Client, + /// The sync events stream + #[allow(clippy::type_complexity)] + stream: Pin< + Box>, SubscribeError>> + Send>, + >, + init: OnceCell<()>, + retry: Retry, + + // Number of events processed + #[cfg(any(test, feature = "test-utils"))] + handle: Arc, +} - let receiver = client.local_events.subscribe(); - let sync_stream = receiver.stream_sync_messages(); +#[cfg(any(test, feature = "test-utils"))] +pub struct WorkerHandle { + processed: AtomicUsize, + notify: Notify, +} - async move { - pin_mut!(sync_stream); +#[cfg(any(test, feature = "test-utils"))] +impl WorkerHandle { + pub async fn wait_for_new_events(&self, mut count: usize) -> Result<(), Elapsed> { + timeout(Duration::from_secs(3), async { + while count > 0 { + self.notify.notified().await; + count -= 1; + } + }) + .await?; - while let Err(err) = client.sync_worker(&mut sync_stream).await { - tracing::error!("Sync worker error: {err}"); - } + Ok(()) + } + + pub async fn wait_for_processed_count(&self, expected: usize) -> Result<(), Elapsed> { + timeout(Duration::from_secs(3), async { + while self.processed.load(Ordering::SeqCst) < expected { + self.notify.notified().await; } - }); + }) + .await?; + + Ok(()) + } + pub async fn block_for_num_events(&self, num_events: usize, op: Fut) -> Result<(), Elapsed> + where + Fut: Future, + { + let processed_count = self.processed_count(); + op.await; + self.wait_for_processed_count(processed_count + num_events) + .await?; Ok(()) } + + pub fn processed_count(&self) -> usize { + self.processed.load(Ordering::SeqCst) + } } -impl Client +impl SyncWorker where - ApiClient: XmtpApi, - V: SmartContractSignatureVerifier, + ApiClient: XmtpApi + 'static, + V: SmartContractSignatureVerifier + 'static, { - pub(crate) async fn sync_worker( - &self, - sync_stream: &mut (impl Stream, SubscribeError>> + Unpin), - ) -> Result<(), DeviceSyncError> { - let provider = self.mls_provider()?; - - let query_retry = RetryBuilder::default() - .retries(5) - .duration(Duration::from_millis(20)) - .build(); + async fn run(&mut self) -> Result<(), DeviceSyncError> { + // Wait for the identity to be ready & verified before doing anything + while !self.client.identity().is_ready() { + xmtp_common::yield_().await + } + self.sync_init().await?; - while let Some(event) = sync_stream.next().await { + while let Some(event) = self.stream.next().await { let event = event?; match event { LocalEvents::SyncMessage(msg) => match msg { SyncMessage::Reply { message_id } => { - let conn = provider.conn_ref(); - let msg = retry_async!( - &query_retry, - (async { - conn.get_group_message(&message_id)?.ok_or( - DeviceSyncError::Storage(StorageError::NotFound(format!( - "Message id {message_id:?} not found." - ))), - ) - }) - )?; - - let msg_content: DeviceSyncContent = - serde_json::from_slice(&msg.decrypted_message_bytes)?; - let DeviceSyncContent::Reply(reply) = msg_content else { - unreachable!(); - }; - - if let Err(err) = self.process_sync_reply(&provider, reply).await { - tracing::warn!("Sync worker error: {err}"); - } + let provider = self.client.mls_provider()?; + self.on_reply(message_id, &provider).await? } SyncMessage::Request { message_id } => { - let conn = provider.conn_ref(); - let msg = retry_async!( - &query_retry, - (async { - conn.get_group_message(&message_id)?.ok_or( - DeviceSyncError::Storage(StorageError::NotFound(format!( - "Message id {message_id:?} not found." - ))), - ) - }) - )?; - - let msg_content: DeviceSyncContent = - serde_json::from_slice(&msg.decrypted_message_bytes)?; - let DeviceSyncContent::Request(request) = msg_content else { - unreachable!(); - }; - - if let Err(err) = self.reply_to_sync_request(&provider, request).await { - tracing::warn!("Sync worker error: {err}"); - } + let provider = self.client.mls_provider()?; + self.on_request(message_id, &provider).await? } }, - LocalEvents::OutgoingConsentUpdates(consent_records) => { - for consent_record in consent_records { - self.send_consent_update(&provider, &consent_record).await?; - } + LocalEvents::OutgoingPreferenceUpdates(preference_updates) => { + tracing::error!("Outgoing preference update {preference_updates:?}"); + UserPreferenceUpdate::sync_across_devices(preference_updates, &self.client) + .await?; } - LocalEvents::IncomingConsentUpdates(consent_records) => { - let conn = provider.conn_ref(); - - conn.insert_or_replace_consent_records(&consent_records)?; + LocalEvents::IncomingPreferenceUpdate(_) => { + tracing::error!("Incoming preference update"); } _ => {} } + + #[cfg(any(test, feature = "test-utils"))] + { + self.handle.processed.fetch_add(1, Ordering::SeqCst); + self.handle.notify.notify_waiters(); + } } + Ok(()) + } + + async fn on_reply( + &mut self, + message_id: Vec, + provider: &XmtpOpenMlsProvider, + ) -> Result<(), DeviceSyncError> { + let conn = provider.conn_ref(); + let Self { + ref client, + ref retry, + .. + } = self; + + let msg = retry_async!( + retry, + (async { + conn.get_group_message(&message_id)? + .ok_or(DeviceSyncError::Storage(StorageError::NotFound(format!( + "Message id {message_id:?} not found." + )))) + }) + )?; + + let msg_content: DeviceSyncContent = serde_json::from_slice(&msg.decrypted_message_bytes)?; + let DeviceSyncContent::Reply(reply) = msg_content else { + unreachable!(); + }; + client.process_sync_reply(provider, reply).await?; Ok(()) } - /** - * Ideally called when the client is registered. - * Will auto-send a sync request if sync group is created. - */ - pub async fn sync_init(&self, provider: &XmtpOpenMlsProvider) -> Result<(), DeviceSyncError> { - tracing::info!( - "Initializing device sync... url: {:?}", - self.history_sync_url - ); - if self.get_sync_group().is_err() { - self.ensure_sync_group(provider).await?; + async fn on_request( + &mut self, + message_id: Vec, + provider: &XmtpOpenMlsProvider, + ) -> Result<(), DeviceSyncError> { + let conn = provider.conn_ref(); + let Self { + ref client, retry, .. + } = self; + + let msg = retry_async!( + retry, + (async { + conn.get_group_message(&message_id)? + .ok_or(DeviceSyncError::Storage(StorageError::NotFound(format!( + "Message id {message_id:?} not found." + )))) + }) + )?; + + let msg_content: DeviceSyncContent = serde_json::from_slice(&msg.decrypted_message_bytes)?; + let DeviceSyncContent::Request(request) = msg_content else { + unreachable!(); + }; + + client.reply_to_sync_request(provider, request).await?; + Ok(()) + } + + //// Ideally called when the client is registered. + //// Will auto-send a sync request if sync group is created. + #[instrument(level = "trace", skip_all)] + pub async fn sync_init(&mut self) -> Result<(), DeviceSyncError> { + let Self { + ref init, + ref client, + .. + } = self; + + init.get_or_try_init(|| async { + let provider = self.client.mls_provider()?; + tracing::info!( + inbox_id = client.inbox_id(), + installation_id = hex::encode(client.installation_public_key()), + "Initializing device sync... url: {:?}", + client.history_sync_url + ); + if client.get_sync_group(provider.conn_ref()).is_err() { + client.ensure_sync_group(&provider).await?; + + client + .send_sync_request(&provider, DeviceSyncKind::Consent) + .await?; + client + .send_sync_request(&provider, DeviceSyncKind::MessageHistory) + .await?; + } + tracing::info!( + inbox_id = client.inbox_id(), + installation_id = hex::encode(client.installation_public_key()), + "Device sync initialized." + ); + + Ok(()) + }) + .await + .copied() + } +} + +impl SyncWorker +where + ApiClient: XmtpApi + Send + Sync + 'static, + V: SmartContractSignatureVerifier + Send + Sync + 'static, +{ + fn new(client: Client) -> Self { + let retry = Retry::builder() + .retries(5) + .duration(Duration::from_millis(20)) + .build(); + + let receiver = client.local_events.subscribe(); + let stream = Box::pin(receiver.stream_sync_messages()); - self.send_sync_request(provider, DeviceSyncKind::Consent) - .await?; - self.send_sync_request(provider, DeviceSyncKind::MessageHistory) - .await?; + Self { + client, + stream, + init: OnceCell::new(), + retry, + + #[cfg(any(test, feature = "test-utils"))] + handle: Arc::new(WorkerHandle { + processed: AtomicUsize::new(0), + notify: Notify::new(), + }), } - tracing::info!("Device sync initialized."); + } - Ok(()) + fn spawn_worker(mut self) { + crate::spawn(None, async move { + let inbox_id = self.client.inbox_id().to_string(); + let installation_id = hex::encode(self.client.installation_public_key()); + while let Err(err) = self.run().await { + tracing::info!("Running worker.."); + match err { + DeviceSyncError::Client(ClientError::Storage( + StorageError::PoolNeedsConnection, + )) => { + tracing::warn!( + inbox_id, + installation_id, + "Pool disconnected. task will restart on reconnect" + ); + break; + } + _ => { + tracing::error!(inbox_id, installation_id, "sync worker error {err}"); + // Wait 2 seconds before restarting. + xmtp_common::time::sleep(Duration::from_secs(2)).await; + } + } + } + }); } +} +impl Client +where + ApiClient: XmtpApi, + V: SmartContractSignatureVerifier, +{ + #[instrument(level = "trace", skip_all)] async fn ensure_sync_group( &self, provider: &XmtpOpenMlsProvider, ) -> Result, GroupError> { - let sync_group = match self.get_sync_group() { + let sync_group = match self.get_sync_group(provider.conn_ref()) { Ok(group) => group, - Err(_) => self.create_sync_group()?, + Err(_) => self.create_sync_group(provider)?, }; sync_group .maybe_update_installations(provider, None) @@ -265,16 +437,21 @@ where Ok(sync_group) } + #[instrument(level = "trace", skip_all)] pub async fn send_sync_request( &self, provider: &XmtpOpenMlsProvider, kind: DeviceSyncKind, ) -> Result { - tracing::info!("Sending a sync request for {kind:?}"); + tracing::info!( + inbox_id = self.inbox_id(), + installation_id = hex::encode(self.installation_public_key()), + "Sending a sync request for {kind:?}" + ); let request = DeviceSyncRequest::new(kind); // find the sync group - let sync_group = self.get_sync_group()?; + let sync_group = self.get_sync_group(provider.conn_ref())?; // sync the group sync_group.sync_with_conn(provider).await?; @@ -290,20 +467,18 @@ where let content = DeviceSyncContent::Request(request.clone()); let content_bytes = serde_json::to_vec(&content)?; - let _message_id = sync_group.prepare_message(&content_bytes, provider.conn_ref(), { + let _message_id = sync_group.prepare_message(&content_bytes, provider, { let request = request.clone(); - move |_time_ns| PlaintextEnvelope { + move |now| PlaintextEnvelope { content: Some(Content::V2(V2 { message_type: Some(MessageType::DeviceSyncRequest(request)), - idempotency_key: new_request_id(), + idempotency_key: now.to_string(), })), } })?; // publish the intent - if let Err(err) = sync_group.publish_intents(provider).await { - tracing::error!("error publishing sync group intents: {:?}", err); - } + sync_group.publish_intents(provider).await?; Ok(request) } @@ -336,9 +511,8 @@ where provider: &XmtpOpenMlsProvider, contents: DeviceSyncReplyProto, ) -> Result<(), DeviceSyncError> { - let conn = provider.conn_ref(); // find the sync group - let sync_group = self.get_sync_group()?; + let sync_group = self.get_sync_group(provider.conn_ref())?; // sync the group sync_group.sync_with_conn(provider).await?; @@ -348,7 +522,7 @@ where .await?; // add original sender to all groups on this device on the node - self.ensure_member_of_all_groups(conn, &msg.sender_inbox_id) + self.ensure_member_of_all_groups(provider, &msg.sender_inbox_id) .await?; // the reply message @@ -362,14 +536,14 @@ where (content_bytes, contents) }; - sync_group.prepare_message(&content_bytes, conn, |_time_ns| PlaintextEnvelope { + sync_group.prepare_message(&content_bytes, provider, |now| PlaintextEnvelope { content: Some(Content::V2(V2 { - idempotency_key: new_request_id(), message_type: Some(MessageType::DeviceSyncReply(contents)), + idempotency_key: now.to_string(), })), })?; - sync_group.sync_until_last_intent_resolved(provider).await?; + sync_group.publish_intents(provider).await?; Ok(()) } @@ -379,11 +553,13 @@ where provider: &XmtpOpenMlsProvider, kind: DeviceSyncKind, ) -> Result<(StoredGroupMessage, DeviceSyncRequestProto), DeviceSyncError> { - let sync_group = self.get_sync_group()?; + let sync_group = self.get_sync_group(provider.conn_ref())?; sync_group.sync_with_conn(provider).await?; - let messages = sync_group - .find_messages(&MsgQueryArgs::default().kind(GroupMessageKind::Application))?; + let messages = provider.conn_ref().get_group_messages( + &sync_group.group_id, + &MsgQueryArgs::default().kind(GroupMessageKind::Application), + )?; for msg in messages.into_iter().rev() { let Ok(msg_content) = @@ -412,7 +588,7 @@ where provider: &XmtpOpenMlsProvider, kind: DeviceSyncKind, ) -> Result, DeviceSyncError> { - let sync_group = self.get_sync_group()?; + let sync_group = self.get_sync_group(provider.conn_ref())?; sync_group.sync_with_conn(provider).await?; let messages = sync_group @@ -456,13 +632,14 @@ where self.insert_encrypted_syncables(provider, enc_payload, &enc_key.try_into()?) .await?; - self.sync_welcomes(provider.conn_ref()).await?; + self.sync_welcomes(provider).await?; let groups = conn.find_groups(GroupQueryArgs::default().conversation_type(ConversationType::Group))?; for crate::storage::group::StoredGroup { id, .. } in groups.into_iter() { - let group = self.group(id)?; - Box::pin(group.sync()).await?; + let group = self.group_with_conn(provider.conn_ref(), id)?; + group.maybe_update_installations(provider, None).await?; + Box::pin(group.sync_with_conn(provider)).await?; } Ok(()) @@ -470,14 +647,18 @@ where async fn ensure_member_of_all_groups( &self, - conn: &DbConnection, + provider: &XmtpOpenMlsProvider, inbox_id: &str, ) -> Result<(), GroupError> { + let conn = provider.conn_ref(); let groups = conn.find_groups(GroupQueryArgs::default().conversation_type(ConversationType::Group))?; for group in groups { - let group = self.group(group.id)?; - Box::pin(group.add_members_by_inbox_id(&[inbox_id.to_string()])).await?; + let group = self.group_with_conn(conn, group.id)?; + Box::pin( + group.add_members_by_inbox_id_with_provider(provider, &[inbox_id.to_string()]), + ) + .await?; } Ok(()) @@ -496,7 +677,11 @@ where return Err(DeviceSyncError::MissingHistorySyncUrl); }; let upload_url = format!("{url}/upload"); - tracing::info!("Using upload url {upload_url}"); + tracing::info!( + inbox_id = self.inbox_id(), + installation_id = hex::encode(self.installation_public_key()), + "Using upload url {upload_url}", + ); let response = reqwest::Client::new() .post(upload_url) @@ -506,6 +691,8 @@ where if !response.status().is_success() { tracing::error!( + inbox_id = self.inbox_id(), + installation_id = hex::encode(self.installation_public_key()), "Failed to upload file. Status code: {} Response: {response:?}", response.status() ); @@ -572,8 +759,8 @@ where if existing_consent_record.state != consent_record.state { warn!("Existing consent record exists and does not match payload state. Streaming consent_record update to sync group."); self.local_events - .send(LocalEvents::OutgoingConsentUpdates(vec![ - existing_consent_record, + .send(LocalEvents::OutgoingPreferenceUpdates(vec![ + UserPreferenceUpdate::ConsentUpdate(existing_consent_record), ])) .map_err(|e| DeviceSyncError::Generic(e.to_string()))?; } @@ -585,13 +772,13 @@ where Ok(()) } - pub fn get_sync_group(&self) -> Result, GroupError> { - let conn = self.store().conn()?; + #[instrument(level = "trace", skip_all)] + pub fn get_sync_group(&self, conn: &DbConnection) -> Result, GroupError> { let sync_group_id = conn .latest_sync_group()? .ok_or(GroupError::GroupNotFound)? .id; - let sync_group = self.group(sync_group_id.clone())?; + let sync_group = self.group_with_conn(conn, sync_group_id.clone())?; Ok(sync_group) } @@ -736,14 +923,11 @@ impl TryFrom for DeviceSyncKeyType { } pub(super) fn new_request_id() -> String { - Alphanumeric.sample_string(&mut rand::thread_rng(), ENC_KEY_SIZE) + xmtp_common::rand_string::() } pub(super) fn generate_nonce() -> [u8; NONCE_SIZE] { - let mut nonce = [0u8; NONCE_SIZE]; - let mut rng = crypto_utils::rng(); - rng.fill_bytes(&mut nonce); - nonce + xmtp_common::rand_array::() } pub(super) fn new_pin() -> String { diff --git a/xmtp_mls/src/groups/device_sync/consent_sync.rs b/xmtp_mls/src/groups/device_sync/consent_sync.rs index cd23e10b0..ea3d5587d 100644 --- a/xmtp_mls/src/groups/device_sync/consent_sync.rs +++ b/xmtp_mls/src/groups/device_sync/consent_sync.rs @@ -1,54 +1,12 @@ use super::*; -use crate::{ - storage::consent_record::{ConsentState, ConsentType}, - Client, XmtpApi, -}; +use crate::{Client, XmtpApi}; use xmtp_id::scw_verifier::SmartContractSignatureVerifier; -use xmtp_proto::xmtp::mls::message_contents::{ - ConsentEntityType, ConsentState as ConsentStateProto, ConsentUpdate as ConsentUpdateProto, -}; impl Client where ApiClient: XmtpApi, V: SmartContractSignatureVerifier, { - pub(crate) async fn send_consent_update( - &self, - provider: &XmtpOpenMlsProvider, - record: &StoredConsentRecord, - ) -> Result<(), DeviceSyncError> { - tracing::info!("Streaming consent update. {:?}", record); - let conn = provider.conn_ref(); - - let consent_update_proto = ConsentUpdateProto { - entity: record.entity.clone(), - entity_type: match record.entity_type { - ConsentType::Address => ConsentEntityType::Address, - ConsentType::ConversationId => ConsentEntityType::ConversationId, - ConsentType::InboxId => ConsentEntityType::InboxId, - } as i32, - state: match record.state { - ConsentState::Allowed => ConsentStateProto::Allowed, - ConsentState::Denied => ConsentStateProto::Denied, - ConsentState::Unknown => ConsentStateProto::Unspecified, - } as i32, - }; - - let sync_group = self.ensure_sync_group(provider).await?; - let content_bytes = serde_json::to_vec(&consent_update_proto)?; - sync_group.prepare_message(&content_bytes, conn, |_time_ns| PlaintextEnvelope { - content: Some(Content::V2(V2 { - idempotency_key: new_request_id(), - message_type: Some(MessageType::ConsentUpdate(consent_update_proto)), - })), - })?; - - sync_group.sync_until_last_intent_resolved(provider).await?; - - Ok(()) - } - pub(super) fn syncable_consent_records( &self, conn: &DbConnection, @@ -62,32 +20,38 @@ where } } -#[cfg(all(not(target_arch = "wasm32"), test))] +#[cfg(test)] pub(crate) mod tests { + #[cfg(target_arch = "wasm32")] + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use wasm_bindgen_test::wasm_bindgen_test; + const HISTORY_SERVER_HOST: &str = "localhost"; const HISTORY_SERVER_PORT: u16 = 5558; - use std::time::{Duration, Instant}; - use super::*; use crate::{ - assert_ok, builder::ClientBuilder, - groups::scoped_client::LocalScopedGroupClient, + groups::scoped_client::ScopedGroupClient, storage::consent_record::{ConsentState, ConsentType}, }; + use xmtp_common::{ + assert_ok, + time::{Duration, Instant}, + }; use xmtp_cryptography::utils::generate_local_wallet; use xmtp_id::InboxOwner; - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] + #[cfg_attr(target_family = "wasm", ignore)] async fn test_consent_sync() { + xmtp_common::logger(); let history_sync_url = format!("http://{}:{}", HISTORY_SERVER_HOST, HISTORY_SERVER_PORT); - let wallet = generate_local_wallet(); let amal_a = ClientBuilder::new_test_client_with_history(&wallet, &history_sync_url).await; - let amal_a_provider = amal_a.mls_provider().unwrap(); let amal_a_conn = amal_a_provider.conn_ref(); + let amal_a_worker = amal_a.sync_worker_handle().unwrap(); // create an alix installation and consent with alix let alix_wallet = generate_local_wallet(); @@ -104,51 +68,60 @@ pub(crate) mod tests { // Create a second installation for amal with sync. let amal_b = ClientBuilder::new_test_client_with_history(&wallet, &history_sync_url).await; + let amal_b_provider = amal_b.mls_provider().unwrap(); let amal_b_conn = amal_b_provider.conn_ref(); + let amal_b_worker = amal_b.sync_worker_handle().unwrap(); let consent_records_b = amal_b.syncable_consent_records(amal_b_conn).unwrap(); assert_eq!(consent_records_b.len(), 0); - - let old_group_id = amal_a.get_sync_group().unwrap().group_id; + // make sure amal's workers have time to sync + // 3 Intents: + // 1.) UpdateGroupMembership Intent for new sync group + // 2.) Device Sync Request + // 3.) MessageHistory Sync Request + amal_b_worker.wait_for_new_events(1).await.unwrap(); + + let old_group_id = amal_a.get_sync_group(amal_a_conn).unwrap().group_id; // Check for new welcomes to new groups in the first installation (should be welcomed to a new sync group from amal_b). - amal_a.sync_welcomes(amal_a_conn).await.unwrap(); - let new_group_id = amal_a.get_sync_group().unwrap().group_id; + amal_a.sync_welcomes(&amal_a_provider).await.unwrap(); + let new_group_id = amal_a.get_sync_group(amal_a_conn).unwrap().group_id; // group id should have changed to the new sync group created by the second installation assert_ne!(old_group_id, new_group_id); let consent_a = amal_a.syncable_consent_records(amal_a_conn).unwrap().len(); // Have amal_a receive the message (and auto-process) - let amal_a_sync_group = amal_a.get_sync_group().unwrap(); - assert_ok!(amal_a_sync_group.sync_with_conn(&amal_a_provider).await); - // Wait for up to 3 seconds for the reply on amal_b (usually is almost instant) - let start = Instant::now(); - let mut reply = None; - while reply.is_none() { - reply = amal_b + amal_a_worker + .block_for_num_events(1, async { + let amal_a_sync_group = amal_a.get_sync_group(amal_a_conn).unwrap(); + assert_ok!(amal_a_sync_group.sync_with_conn(&amal_a_provider).await); + }) + .await + .unwrap(); + + xmtp_common::wait_for_some(|| async { + amal_b .get_latest_sync_reply(&amal_b_provider, DeviceSyncKind::Consent) .await - .unwrap(); - if start.elapsed() > Duration::from_secs(3) { - panic!("Did not receive sync reply."); - } - } - - // Wait up to 3 seconds for sync to process (typically is almost instant) - let mut consent_b = 0; - let start = Instant::now(); - while consent_b != consent_a { - consent_b = amal_b.syncable_consent_records(amal_b_conn).unwrap().len(); - - if start.elapsed() > Duration::from_secs(3) { - panic!("Consent sync did not work. Consent: {consent_b}/{consent_a}"); - } - } + .unwrap() + }) + .await; + + // Wait up to 20 seconds for sync to process (typically is almost instant) + xmtp_common::wait_for_eq( + || { + let consent_b = amal_b.syncable_consent_records(amal_b_conn).unwrap().len(); + futures::future::ready(consent_b != consent_a) + }, + true, + ) + .await + .unwrap(); // Test consent streaming - let amal_b_sync_group = amal_b.get_sync_group().unwrap(); + let amal_b_sync_group = amal_b.get_sync_group(amal_b_conn).unwrap(); let bo_wallet = generate_local_wallet(); // Ensure bo is not consented with amal_b diff --git a/xmtp_mls/src/groups/device_sync/message_sync.rs b/xmtp_mls/src/groups/device_sync/message_sync.rs index 44748ff61..053dea448 100644 --- a/xmtp_mls/src/groups/device_sync/message_sync.rs +++ b/xmtp_mls/src/groups/device_sync/message_sync.rs @@ -42,26 +42,28 @@ where } } -#[cfg(all(not(target_arch = "wasm32"), test))] +#[cfg(test)] pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - - const HISTORY_SERVER_HOST: &str = "localhost"; - const HISTORY_SERVER_PORT: u16 = 5558; + use wasm_bindgen_test::wasm_bindgen_test; use super::*; - use crate::{assert_ok, builder::ClientBuilder, groups::GroupMetadataOptions}; - use std::time::{Duration, Instant}; + + use crate::{ + builder::ClientBuilder, + groups::GroupMetadataOptions, + utils::test::{wait_for_min_intents, HISTORY_SYNC_URL}, + }; + use xmtp_common::{assert_ok, wait_for_some}; use xmtp_cryptography::utils::generate_local_wallet; use xmtp_id::InboxOwner; - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] + #[cfg_attr(target_family = "wasm", ignore)] async fn test_message_history_sync() { - let history_sync_url = format!("http://{}:{}", HISTORY_SERVER_HOST, HISTORY_SERVER_PORT); - let wallet = generate_local_wallet(); - let amal_a = ClientBuilder::new_test_client_with_history(&wallet, &history_sync_url).await; + let amal_a = ClientBuilder::new_test_client_with_history(&wallet, HISTORY_SYNC_URL).await; let amal_a_provider = amal_a.mls_provider().unwrap(); let amal_a_conn = amal_a_provider.conn_ref(); @@ -87,20 +89,28 @@ pub(crate) mod tests { assert_eq!(syncable_messages.len(), 2); // welcome message, and message that was just sent // Create a second installation for amal. - let amal_b = ClientBuilder::new_test_client_with_history(&wallet, &history_sync_url).await; + let amal_b = ClientBuilder::new_test_client_with_history(&wallet, HISTORY_SYNC_URL).await; let amal_b_provider = amal_b.mls_provider().unwrap(); let amal_b_conn = amal_b_provider.conn_ref(); let groups_b = amal_b.syncable_groups(amal_b_conn).unwrap(); assert_eq!(groups_b.len(), 0); - let old_group_id = amal_a.get_sync_group().unwrap().group_id; + // make sure amal's worker has time to sync + // 3 Intents: + // 1.) UpdateGroupMembership Intent for new sync group + // 2.) Device Sync Request + // 3.) MessageHistory Sync Request + wait_for_min_intents(amal_b_conn, 3).await; + tracing::info!("Waiting for intents published"); + + let old_group_id = amal_a.get_sync_group(amal_a_conn).unwrap().group_id; // Check for new welcomes to new groups in the first installation (should be welcomed to a new sync group from amal_b). amal_a - .sync_welcomes(amal_a_conn) + .sync_welcomes(&amal_a_provider) .await .expect("sync_welcomes"); - let new_group_id = amal_a.get_sync_group().unwrap().group_id; + let new_group_id = amal_a.get_sync_group(amal_a_conn).unwrap().group_id; // group id should have changed to the new sync group created by the second installation assert_ne!(old_group_id, new_group_id); @@ -111,38 +121,95 @@ pub(crate) mod tests { .unwrap(); // Have amal_a receive the message (and auto-process) - let amal_a_sync_group = amal_a.get_sync_group().unwrap(); + let amal_a_sync_group = amal_a.get_sync_group(amal_a_conn).unwrap(); assert_ok!(amal_a_sync_group.sync_with_conn(&amal_a_provider).await); - // Wait for up to 3 seconds for the reply on amal_b (usually is almost instant) - let start = Instant::now(); - let mut reply = None; - while reply.is_none() { - reply = amal_b + xmtp_common::wait_for_some(|| async { + amal_b .get_latest_sync_reply(&amal_b_provider, DeviceSyncKind::MessageHistory) .await - .unwrap(); - if start.elapsed() > Duration::from_secs(3) { - panic!("Did not receive sync reply."); - } - } + .unwrap() + }) + .await + .unwrap(); + + xmtp_common::wait_for_eq( + || { + let groups_a = amal_a.syncable_groups(amal_a_conn).unwrap().len(); + let groups_b = amal_b.syncable_groups(amal_b_conn).unwrap().len(); + let messages_a = amal_a.syncable_messages(amal_a_conn).unwrap().len(); + let messages_b = amal_b.syncable_messages(amal_b_conn).unwrap().len(); + futures::future::ready(groups_a != groups_b || messages_a != messages_b) + }, + true, + ) + .await + .unwrap(); + } - // Wait up to 3 seconds for sync to process (typically is almost instant) - let [mut groups_a, mut groups_b, mut messages_a, mut messages_b] = [0; 4]; - let start = Instant::now(); - while groups_a != groups_b || messages_a != messages_b { - groups_a = amal_a.syncable_groups(amal_a_conn).unwrap().len(); - groups_b = amal_b.syncable_groups(amal_b_conn).unwrap().len(); - messages_a = amal_a.syncable_messages(amal_a_conn).unwrap().len(); - messages_b = amal_b.syncable_messages(amal_b_conn).unwrap().len(); - - if start.elapsed() > Duration::from_secs(3) { - panic!("Message sync did not work. Groups: {groups_a}/{groups_b} | Messages: {messages_a}/{messages_b}"); - } - } + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] + async fn test_sync_continues_during_db_disconnect() { + let wallet = generate_local_wallet(); + let amal_a = ClientBuilder::new_test_client_with_history(&wallet, HISTORY_SYNC_URL).await; + + let amal_a_provider = amal_a.mls_provider().unwrap(); + let amal_a_conn = amal_a_provider.conn_ref(); + + // make sure amal's worker has time to sync + // 3 Intents: + // 1.) UpdateGroupMembership Intent for new sync group + // 2.) Device Sync Request + // 3.) MessageHistory Sync Request + wait_for_min_intents(amal_a_conn, 3).await; + tracing::info!("Waiting for intents published"); + let old_group_id = amal_a.get_sync_group(amal_a_conn).unwrap().group_id; + + // let old_group_id = amal_a.get_sync_group(amal_a_conn).unwrap().group_id; + tracing::info!("Disconnecting"); + amal_a.release_db_connection().unwrap(); + + // Create a second installation for amal. + let amal_b = ClientBuilder::new_test_client_with_history(&wallet, HISTORY_SYNC_URL).await; + let amal_b_provider = amal_b.mls_provider().unwrap(); + let amal_b_conn = amal_b_provider.conn_ref(); + + let groups_b = amal_b.syncable_groups(amal_b_conn).unwrap(); + assert_eq!(groups_b.len(), 0); + + // make sure amal's worker has time to sync + // 3 Intents: + // 1.) UpdateGroupMembership Intent for new sync group + // 2.) Device Sync Request + // 3.) MessageHistory Sync Request + wait_for_min_intents(amal_b_conn, 3).await; + tracing::info!("Waiting for intents published"); + + // Have the second installation request for a consent sync. + amal_b + .send_sync_request(&amal_b_provider, DeviceSyncKind::MessageHistory) + .await + .unwrap(); + + amal_a.reconnect_db().unwrap(); + + // make sure amal's worker has time to sync + // 2 Intents: + // 1.) Device Sync Request + // 2.) MessageHistory Sync Request + wait_for_min_intents(amal_a_conn, 2).await; + tracing::info!("Waiting for intents published"); + + // Check for new welcomes to new groups in the first installation (should be welcomed to a new sync group from amal_b). + amal_a + .sync_welcomes(&amal_a_provider) + .await + .expect("sync_welcomes"); + let new_group_id = amal_a.get_sync_group(amal_a_conn).unwrap().group_id; + // group id should have changed to the new sync group created by the second installation + assert_ne!(old_group_id, new_group_id); } - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] async fn test_prepare_groups_to_sync() { let wallet = generate_local_wallet(); let amal_a = ClientBuilder::new_test_client(&wallet).await; @@ -159,62 +226,57 @@ pub(crate) mod tests { assert_eq!(result.len(), 2); } - #[tokio::test] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] async fn test_externals_cant_join_sync_group() { - let history_sync_url = format!("http://{}:{}", HISTORY_SERVER_HOST, HISTORY_SERVER_PORT); let wallet = generate_local_wallet(); - let amal = ClientBuilder::new_test_client_with_history(&wallet, &history_sync_url).await; - amal.sync_welcomes(&amal.store().conn().unwrap()) + let amal = ClientBuilder::new_test_client_with_history(&wallet, HISTORY_SYNC_URL).await; + amal.sync_welcomes(&amal.mls_provider().unwrap()) .await .expect("sync welcomes"); - let external_wallet = generate_local_wallet(); - let external_client = - ClientBuilder::new_test_client_with_history(&external_wallet, &history_sync_url).await; + let bo_wallet = generate_local_wallet(); + let bo_client = + ClientBuilder::new_test_client_with_history(&bo_wallet, HISTORY_SYNC_URL).await; - external_client - .sync_welcomes(&external_client.store().conn().unwrap()) + bo_client + .sync_welcomes(&bo_client.mls_provider().unwrap()) .await .expect("sync welcomes"); - let amal_sync_group = amal - .store() - .conn() - .unwrap() - .latest_sync_group() - .expect("find sync group"); + let amal_sync_group = + wait_for_some(|| async { amal.store().conn().unwrap().latest_sync_group().unwrap() }) + .await; + assert!(amal_sync_group.is_some()); + let amal_sync_group = amal_sync_group.unwrap(); // try to join amal's sync group let sync_group_id = amal_sync_group.id.clone(); let created_at_ns = amal_sync_group.created_at_ns; - let external_client_group = MlsGroup::new( - external_client.clone(), - sync_group_id.clone(), - created_at_ns, - ); + let external_client_group = + MlsGroup::new(bo_client.clone(), sync_group_id.clone(), created_at_ns); let result = external_client_group - .add_members(&[external_wallet.get_address()]) + .add_members(&[bo_wallet.get_address()]) .await; assert!(result.is_err()); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_new_pin() { let pin = new_pin(); assert!(pin.chars().all(|c| c.is_numeric())); assert_eq!(pin.len(), 4); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_new_request_id() { let request_id = new_request_id(); assert_eq!(request_id.len(), ENC_KEY_SIZE); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_new_key() { let sig_key = DeviceSyncKeyType::new_aes_256_gcm_key(); let enc_key = DeviceSyncKeyType::new_aes_256_gcm_key(); @@ -224,7 +286,7 @@ pub(crate) mod tests { assert_ne!(sig_key, enc_key); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_generate_nonce() { let nonce_1 = generate_nonce(); let nonce_2 = generate_nonce(); diff --git a/xmtp_mls/src/groups/device_sync/preference_sync.rs b/xmtp_mls/src/groups/device_sync/preference_sync.rs new file mode 100644 index 000000000..9f23374b1 --- /dev/null +++ b/xmtp_mls/src/groups/device_sync/preference_sync.rs @@ -0,0 +1,162 @@ +use super::*; +use crate::{ + storage::{consent_record::StoredConsentRecord, user_preferences::StoredUserPreferences}, + Client, +}; +use serde::{Deserialize, Serialize}; +use xmtp_proto::{ + api_client::trait_impls::XmtpApi, + xmtp::mls::message_contents::UserPreferenceUpdate as UserPreferenceUpdateProto, +}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[repr(i32)] +pub enum UserPreferenceUpdate { + ConsentUpdate(StoredConsentRecord) = 1, + HmacKeyUpdate { key: Vec } = 2, +} + +impl UserPreferenceUpdate { + /// Send a preference update through the sync group for other devices to consume + pub(crate) async fn sync_across_devices( + updates: Vec, + client: &Client, + ) -> Result<(), DeviceSyncError> { + let provider = client.mls_provider()?; + let sync_group = client.ensure_sync_group(&provider).await?; + + let updates = updates + .iter() + .map(bincode::serialize) + .collect::, _>>()?; + let update_proto = UserPreferenceUpdateProto { contents: updates }; + let content_bytes = serde_json::to_vec(&update_proto)?; + sync_group.prepare_message(&content_bytes, &provider, |now| PlaintextEnvelope { + content: Some(Content::V2(V2 { + message_type: Some(MessageType::UserPreferenceUpdate(update_proto)), + idempotency_key: now.to_string(), + })), + })?; + + sync_group.publish_intents(&provider).await?; + + Ok(()) + } + + /// Process and insert incoming preference updates over the sync group + pub(crate) fn process_incoming_preference_update( + update_proto: UserPreferenceUpdateProto, + provider: &XmtpOpenMlsProvider, + ) -> Result, StorageError> { + let conn = provider.conn_ref(); + + let proto_content = update_proto.contents; + + let mut updates = Vec::with_capacity(proto_content.len()); + let mut consent_updates = vec![]; + + for update in proto_content { + if let Ok(update) = bincode::deserialize::(&update) { + updates.push(update.clone()); + match update { + UserPreferenceUpdate::ConsentUpdate(consent_record) => { + consent_updates.push(consent_record); + } + UserPreferenceUpdate::HmacKeyUpdate { key } => { + StoredUserPreferences { + hmac_key: Some(key), + ..StoredUserPreferences::load(conn)? + } + .store(conn)?; + } + } + } else { + // Don't fail on errors since this may come from a newer version of the lib + // that has new update types. + tracing::warn!( + "Failed to deserialize preference update. Is this libxmtp version outdated?" + ); + } + } + + // Insert all of the consent records at once. + if !consent_updates.is_empty() { + conn.insert_or_replace_consent_records(&consent_updates)?; + } + + Ok(updates) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + builder::ClientBuilder, + storage::consent_record::{ConsentState, ConsentType}, + }; + use crypto_utils::generate_local_wallet; + use wasm_bindgen_test::wasm_bindgen_test; + + #[derive(Serialize, Deserialize, Clone)] + #[repr(i32)] + enum OldUserPreferenceUpdate { + ConsentUpdate(StoredConsentRecord) = 1, + } + + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] + #[cfg_attr(target_family = "wasm", ignore)] + async fn test_can_deserialize_between_versions() { + let consent_record = StoredConsentRecord { + entity: "hello there".to_string(), + entity_type: ConsentType::Address, + state: ConsentState::Allowed, + }; + let update = UserPreferenceUpdate::ConsentUpdate(consent_record); + + let bytes = bincode::serialize(&update).unwrap(); + + let old_update: OldUserPreferenceUpdate = bincode::deserialize(&bytes).unwrap(); + + let OldUserPreferenceUpdate::ConsentUpdate(update) = old_update; + assert_eq!(update.state, ConsentState::Allowed); + } + + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] + #[cfg_attr(target_family = "wasm", ignore)] + async fn test_hmac_sync() { + let wallet = generate_local_wallet(); + let amal_a = + ClientBuilder::new_test_client_with_history(&wallet, "http://localhost:5558").await; + let amal_a_provider = amal_a.mls_provider().unwrap(); + let amal_a_conn = amal_a_provider.conn_ref(); + let amal_a_worker = amal_a.sync_worker_handle().unwrap(); + + let amal_b = + ClientBuilder::new_test_client_with_history(&wallet, "http://localhost:5558").await; + let amal_b_provider = amal_b.mls_provider().unwrap(); + let amal_b_conn = amal_b_provider.conn_ref(); + let amal_b_worker = amal_b.sync_worker_handle().unwrap(); + + // wait for the new sync group + amal_a_worker.wait_for_processed_count(1).await.unwrap(); + amal_b_worker.wait_for_processed_count(1).await.unwrap(); + + amal_a.sync_welcomes(&amal_a_provider).await.unwrap(); + + let sync_group_a = amal_a.get_sync_group(amal_a_conn).unwrap(); + let sync_group_b = amal_b.get_sync_group(amal_b_conn).unwrap(); + assert_eq!(sync_group_a.group_id, sync_group_b.group_id); + + sync_group_a.sync_with_conn(&amal_a_provider).await.unwrap(); + sync_group_b.sync_with_conn(&amal_a_provider).await.unwrap(); + + // Wait for a to process the new hmac key + amal_a_worker.wait_for_processed_count(2).await.unwrap(); + + let pref_a = StoredUserPreferences::load(amal_a_conn).unwrap(); + let pref_b = StoredUserPreferences::load(amal_b_conn).unwrap(); + + assert_eq!(pref_a.hmac_key, pref_b.hmac_key); + } +} diff --git a/xmtp_mls/src/groups/group_metadata.rs b/xmtp_mls/src/groups/group_metadata.rs index 528e0e9ce..e06c7e6a1 100644 --- a/xmtp_mls/src/groups/group_metadata.rs +++ b/xmtp_mls/src/groups/group_metadata.rs @@ -106,7 +106,6 @@ impl TryFrom<&Extensions> for GroupMetadata { * *DM*: A conversation between 2 members with simplified permissions * *Sync*: A conversation between all the devices of a single member with simplified permissions */ - impl From for ConversationTypeProto { fn from(value: ConversationType) -> Self { match value { diff --git a/xmtp_mls/src/groups/group_mutable_metadata.rs b/xmtp_mls/src/groups/group_mutable_metadata.rs index 36facf8d5..2db12769e 100644 --- a/xmtp_mls/src/groups/group_mutable_metadata.rs +++ b/xmtp_mls/src/groups/group_mutable_metadata.rs @@ -220,12 +220,12 @@ impl TryFrom for GroupMutableMetadata { fn try_from(value: GroupMutableMetadataProto) -> Result { let admin_list = value .admin_list - .ok_or_else(|| GroupMutableMetadataError::MissingMetadataField)? + .ok_or(GroupMutableMetadataError::MissingMetadataField)? .inbox_ids; let super_admin_list = value .super_admin_list - .ok_or_else(|| GroupMutableMetadataError::MissingMetadataField)? + .ok_or(GroupMutableMetadataError::MissingMetadataField)? .inbox_ids; Ok(Self::new( diff --git a/xmtp_mls/src/groups/group_permissions.rs b/xmtp_mls/src/groups/group_permissions.rs index 62aedf158..1a1b04594 100644 --- a/xmtp_mls/src/groups/group_permissions.rs +++ b/xmtp_mls/src/groups/group_permissions.rs @@ -1275,19 +1275,17 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::{ - groups::{ - group_metadata::DmMembers, group_mutable_metadata::MetadataField, - validated_commit::MutableMetadataChanges, - }, - utils::test::{rand_string, rand_vec}, + use crate::groups::{ + group_metadata::DmMembers, group_mutable_metadata::MetadataField, + validated_commit::MutableMetadataChanges, }; + use xmtp_common::{rand_string, rand_vec}; use super::*; fn build_change(inbox_id: Option, is_admin: bool, is_super_admin: bool) -> Inbox { Inbox { - inbox_id: inbox_id.unwrap_or(rand_string()), + inbox_id: inbox_id.unwrap_or(rand_string::<24>()), is_creator: is_super_admin, is_super_admin, is_admin, @@ -1302,8 +1300,8 @@ pub(crate) mod tests { is_super_admin: bool, ) -> CommitParticipant { CommitParticipant { - inbox_id: inbox_id.unwrap_or(rand_string()), - installation_id: installation_id.unwrap_or_else(rand_vec), + inbox_id: inbox_id.unwrap_or(rand_string::<24>()), + installation_id: installation_id.unwrap_or_else(rand_vec::<24>), is_creator: is_super_admin, is_admin, is_super_admin, @@ -1344,7 +1342,13 @@ pub(crate) mod tests { let field_changes = metadata_fields_changed .unwrap_or_default() .into_iter() - .map(|field| MetadataFieldChange::new(field, Some(rand_string()), Some(rand_string()))) + .map(|field| { + MetadataFieldChange::new( + field, + Some(rand_string::<24>()), + Some(rand_string::<24>()), + ) + }) .collect(); let dm_members = if let Some(dm_target_inbox_id) = dm_target_inbox_id { diff --git a/xmtp_mls/src/groups/intents.rs b/xmtp_mls/src/groups/intents.rs index 13767ff5c..d756f204a 100644 --- a/xmtp_mls/src/groups/intents.rs +++ b/xmtp_mls/src/groups/intents.rs @@ -33,6 +33,7 @@ use crate::{ }, types::Address, verified_key_package_v2::{KeyPackageVerificationError, VerifiedKeyPackageV2}, + XmtpOpenMlsProvider, }; use super::{ @@ -58,16 +59,17 @@ pub enum IntentError { impl MlsGroup { pub fn queue_intent( &self, + provider: &XmtpOpenMlsProvider, intent_kind: IntentKind, intent_data: Vec, ) -> Result { - self.context().store().transaction(|provider| { + self.context().store().transaction(provider, |provider| { let conn = provider.conn_ref(); self.queue_intent_with_conn(conn, intent_kind, intent_data) }) } - pub fn queue_intent_with_conn( + fn queue_intent_with_conn( &self, conn: &DbConnection, intent_kind: IntentKind, @@ -86,13 +88,14 @@ impl MlsGroup { if intent_kind != IntentKind::SendMessage { conn.update_rotated_at_ns(self.group_id.clone())?; } + tracing::debug!(inbox_id = self.client.inbox_id(), intent_kind = %intent_kind, "queued intent"); Ok(intent) } fn maybe_insert_key_update_intent(&self, conn: &DbConnection) -> Result<(), GroupError> { let last_rotated_at_ns = conn.get_rotated_at_ns(self.group_id.clone())?; - let now_ns = crate::utils::time::now_ns(); + let now_ns = xmtp_common::time::now_ns(); let elapsed_ns = now_ns - last_rotated_at_ns; if elapsed_ns > GROUP_KEY_ROTATION_INTERVAL_NS { self.queue_intent_with_conn(conn, IntentKind::KeyUpdate, vec![])?; @@ -799,7 +802,7 @@ pub(crate) mod tests { // Client B sends a message to Client A let groups_b = client_b - .sync_welcomes(&client_b.store().conn().unwrap()) + .sync_welcomes(&client_b.mls_provider().unwrap()) .await .unwrap(); assert_eq!(groups_b.len(), 1); diff --git a/xmtp_mls/src/groups/members.rs b/xmtp_mls/src/groups/members.rs index 71a4595c2..5ca53a40a 100644 --- a/xmtp_mls/src/groups/members.rs +++ b/xmtp_mls/src/groups/members.rs @@ -66,7 +66,7 @@ where { return None; } - Some((id.clone(), Some(*sequence))) + Some((id.as_str(), Some(*sequence))) }) .collect(); diff --git a/xmtp_mls/src/groups/mls_sync.rs b/xmtp_mls/src/groups/mls_sync.rs index e52b85547..6a642bc41 100644 --- a/xmtp_mls/src/groups/mls_sync.rs +++ b/xmtp_mls/src/groups/mls_sync.rs @@ -6,21 +6,22 @@ use super::{ UpdateAdminListIntentData, UpdateGroupMembershipIntentData, UpdatePermissionIntentData, }, validated_commit::{extract_group_membership, CommitValidationError}, - GroupError, IntentError, MlsGroup, ScopedGroupClient, + GroupError, HmacKey, IntentError, MlsGroup, ScopedGroupClient, }; use crate::{ - codecs::{group_updated::GroupUpdatedCodec, ContentCodec}, configuration::{ - GRPC_DATA_LIMIT, MAX_GROUP_SIZE, MAX_INTENT_PUBLISH_ATTEMPTS, MAX_PAST_EPOCHS, + GRPC_DATA_LIMIT, HMAC_SALT, MAX_GROUP_SIZE, MAX_INTENT_PUBLISH_ATTEMPTS, MAX_PAST_EPOCHS, SYNC_UPDATE_INSTALLATIONS_INTERVAL_NS, }, - groups::{intents::UpdateMetadataIntentData, validated_commit::ValidatedCommit}, + groups::device_sync::DeviceSyncContent, + groups::{ + device_sync::preference_sync::UserPreferenceUpdate, intents::UpdateMetadataIntentData, + validated_commit::ValidatedCommit, + }, hpke::{encrypt_welcome, HpkeError}, identity::{parse_credential, IdentityError}, identity_updates::load_identity_updates, intents::ProcessIntentError, - retry::{Retry, RetryableError}, - retry_async, storage::{ db_connection::DbConnection, group_intent::{IntentKind, IntentState, StoredGroupIntent, ID}, @@ -28,14 +29,18 @@ use crate::{ refresh_state::EntityKind, serialization::{db_deserialize, db_serialize}, sql_key_store, + user_preferences::StoredUserPreferences, + StorageError, }, subscriptions::LocalEvents, - utils::{hash::sha256, id::calculate_message_id}, + subscriptions::SyncMessage, + utils::{hash::sha256, id::calculate_message_id, time::hmac_epoch}, xmtp_openmls_provider::XmtpOpenMlsProvider, Delete, Fetch, StoreOrIgnore, }; -use crate::{groups::device_sync::DeviceSyncContent, subscriptions::SyncMessage}; use futures::future::try_join_all; +use hkdf::Hkdf; +use hmac::{Hmac, Mac}; use openmls::{ credentials::BasicCredential, extensions::Extensions, @@ -53,20 +58,25 @@ use openmls::{framing::WireFormat, prelude::BasicCredentialError}; use openmls_traits::{signatures::Signer, OpenMlsProvider}; use prost::bytes::Bytes; use prost::Message; +use sha2::Sha256; use std::{ collections::{HashMap, HashSet}, mem::{discriminant, Discriminant}, + ops::RangeInclusive, }; use thiserror::Error; use tracing::debug; +use xmtp_common::{retry_async, Retry, RetryableError}; +use xmtp_content_types::{group_updated::GroupUpdatedCodec, CodecError, ContentCodec}; use xmtp_id::{InboxId, InboxIdRef}; use xmtp_proto::xmtp::mls::{ api::v1::{ group_message::{Version as GroupMessageVersion, V1 as GroupMessageV1}, + group_message_input::{Version as GroupMessageInputVersion, V1 as GroupMessageInputV1}, welcome_message_input::{ Version as WelcomeMessageInputVersion, V1 as WelcomeMessageInputV1, }, - GroupMessage, WelcomeMessageInput, + GroupMessage, GroupMessageInput, WelcomeMessageInput, }, message_contents::{ plaintext_envelope::{v2::MessageType, Content, V1, V2}, @@ -116,7 +126,7 @@ pub enum GroupMessageProcessingError { #[error(transparent)] Intent(#[from] IntentError), #[error(transparent)] - Codec(#[from] crate::codecs::CodecError), + Codec(#[from] CodecError), #[error("wrong credential type")] WrongCredentialType(#[from] BasicCredentialError), #[error(transparent)] @@ -125,7 +135,7 @@ pub enum GroupMessageProcessingError { AssociationDeserialization(#[from] xmtp_id::associations::DeserializationError), } -impl crate::retry::RetryableError for GroupMessageProcessingError { +impl RetryableError for GroupMessageProcessingError { fn is_retryable(&self) -> bool { match self { Self::Diesel(err) => err.is_retryable(), @@ -171,6 +181,7 @@ where let mls_provider = XmtpOpenMlsProvider::from(conn); tracing::info!( inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), current_epoch = self.load_mls_group(&mls_provider)?.epoch().as_u64(), "[{}] syncing group", @@ -178,6 +189,7 @@ where ); tracing::info!( inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), current_epoch = self.load_mls_group(&mls_provider)?.epoch().as_u64(), "current epoch for [{}] in sync() is Epoch: [{}]", @@ -364,6 +376,7 @@ where let group_epoch = openmls_group.epoch(); debug!( inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), current_epoch = openmls_group.epoch().as_u64(), msg_id, @@ -392,6 +405,7 @@ where if published_in_epoch_u64 != group_epoch_u64 { tracing::warn!( inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), current_epoch = openmls_group.epoch().as_u64(), msg_id, @@ -491,6 +505,7 @@ where tracing::info!( inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), sender_inbox_id = sender_inbox_id, sender_installation_id = hex::encode(&sender_installation_id), group_id = hex::encode(&self.group_id), @@ -512,6 +527,8 @@ where tracing::info!( inbox_id = self.client.inbox_id(), sender_inbox_id = sender_inbox_id, + sender_installation_id = hex::encode(&sender_installation_id), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), current_epoch = openmls_group.epoch().as_u64(), msg_epoch, @@ -606,17 +623,19 @@ where SyncMessage::Reply { message_id }, )); } - Some(MessageType::ConsentUpdate(update)) => { - tracing::info!( - "Incoming streamed consent update: {:?} {} updated to {:?}.", - update.entity_type(), - update.entity, - update.state() - ); - - let _ = self.client.local_events().send( - LocalEvents::IncomingConsentUpdates(vec![update.try_into()?]), - ); + Some(MessageType::UserPreferenceUpdate(update)) => { + // This function inserts the updates appropriately, + // and returns a copy of what was inserted + let updates = + UserPreferenceUpdate::process_incoming_preference_update( + update, provider, + )?; + + // Broadcast those updates for integrators to be notified of changes + let _ = self + .client + .local_events() + .send(LocalEvents::IncomingPreferenceUpdate(updates)); } _ => { return Err(GroupMessageProcessingError::InvalidPayload); @@ -636,6 +655,7 @@ where tracing::info!( inbox_id = self.client.inbox_id(), sender_inbox_id = sender_inbox_id, + installation_id = %self.client.installation_id(), sender_installation_id = hex::encode(&sender_installation_id), group_id = hex::encode(&self.group_id), current_epoch = openmls_group.epoch().as_u64(), @@ -659,6 +679,7 @@ where tracing::info!( inbox_id = self.client.inbox_id(), sender_inbox_id = sender_inbox_id, + installation_id = %self.client.installation_id(), sender_installation_id = hex::encode(&sender_installation_id), group_id = hex::encode(&self.group_id), current_epoch = openmls_group.epoch().as_u64(), @@ -705,6 +726,7 @@ where .find_group_intent_by_payload_hash(sha256(envelope.data.as_slice())); tracing::info!( inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), current_epoch = openmls_group.epoch().as_u64(), msg_id = envelope.id, @@ -718,6 +740,7 @@ where let intent_id = intent.id; tracing::info!( inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), current_epoch = openmls_group.epoch().as_u64(), msg_id = envelope.id, @@ -752,6 +775,7 @@ where Ok(None) => { tracing::info!( inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), current_epoch = openmls_group.epoch().as_u64(), msg_id = envelope.id, @@ -769,9 +793,9 @@ where #[tracing::instrument(level = "trace", skip_all)] async fn consume_message( &self, + provider: &XmtpOpenMlsProvider, envelope: &GroupMessage, openmls_group: &mut OpenMlsGroup, - conn: &DbConnection, ) -> Result<(), GroupMessageProcessingError> { let msgv1 = match &envelope.version { Some(GroupMessageVersion::V1(value)) => value, @@ -784,12 +808,15 @@ where _ => EntityKind::Group, }; - let last_cursor = conn.get_last_cursor_for_id(&self.group_id, message_entity_kind)?; + let last_cursor = provider + .conn_ref() + .get_last_cursor_for_id(&self.group_id, message_entity_kind)?; tracing::info!("### last cursor --> [{:?}]", last_cursor); let should_skip_message = last_cursor > msgv1.id as i64; if should_skip_message { tracing::info!( inbox_id = "self.inbox_id()", + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), "Message already processed: skipped msgId:[{}] entity kind:[{:?}] last cursor in db: [{}]", msgv1.id, @@ -798,19 +825,36 @@ where ); Err(GroupMessageProcessingError::AlreadyProcessed(msgv1.id)) } else { - self.client - .intents() - .process_for_id( - &msgv1.group_id, - EntityKind::Group, - msgv1.id, - |provider| async move { - self.process_message(openmls_group, &provider, msgv1, true) - .await?; - Ok::<(), GroupMessageProcessingError>(()) - }, - ) - .await?; + let cursor = &msgv1.id; + // Download all unread welcome messages and convert to groups. + // In a database transaction, increment the cursor for a given entity and + // apply the update after the provided `ProcessingFn` has completed successfully. + self.client.store().transaction_async(provider, |provider| async move { + let is_updated = + provider + .conn_ref() + .update_cursor(&msgv1.group_id, EntityKind::Group, *cursor as i64)?; + if !is_updated { + return Err(ProcessIntentError::AlreadyProcessed(*cursor).into()); + } + self.process_message(openmls_group, provider, msgv1, true).await?; + Ok::<_, GroupMessageProcessingError>(()) + }).await + .inspect(|_| { + tracing::info!( + "Transaction completed successfully: process for group [{}] envelope cursor[{}]", + hex::encode(&msgv1.group_id), + cursor + ); + }) + .inspect_err(|err| { + tracing::info!( + "Transaction failed: process for group [{}] envelope cursor [{}] error:[{}]", + hex::encode(&msgv1.group_id), + cursor, + err + ); + })?; Ok(()) } } @@ -828,7 +872,7 @@ where let result = retry_async!( Retry::default(), (async { - self.consume_message(&message, &mut openmls_group, provider.conn_ref()) + self.consume_message(provider, &message, &mut openmls_group) .await }) ); @@ -852,7 +896,13 @@ where if receive_errors.is_empty() { Ok(()) } else { - tracing::error!("Message processing errors: {:?}", receive_errors); + tracing::error!( + group_id = hex::encode(&self.group_id), + inbox_id = self.client.inbox_id(), + installation_id = hex::encode(self.client.installation_id()), + "Message processing errors: {:?}", + receive_errors + ); Err(GroupError::ReceiveErrors(receive_errors)) } } @@ -944,6 +994,7 @@ where intent.id, intent.kind = %intent.kind, inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), "intent {} has reached max publish attempts", intent.id); // TODO: Eventually clean up errored attempts @@ -973,24 +1024,24 @@ where openmls_group.epoch().as_u64() as i64, )?; tracing::debug!( + inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), intent.id, intent.kind = %intent.kind, - inbox_id = self.client.inbox_id(), group_id = hex::encode(&self.group_id), "client [{}] set stored intent [{}] to state `published`", self.client.inbox_id(), intent.id ); - self.client - .api() - .send_group_messages(vec![payload_slice]) - .await?; + let messages = self.prepare_group_messages(vec![payload_slice])?; + self.client.api().send_group_messages(messages).await?; tracing::info!( intent.id, intent.kind = %intent.kind, inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), group_id = hex::encode(&self.group_id), "[{}] published intent [{}] of type [{}]", self.client.inbox_id(), @@ -1003,7 +1054,11 @@ where } } Ok(None) => { - tracing::info!("Skipping intent because no publish data returned"); + tracing::info!( + inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), + "Skipping intent because no publish data returned" + ); let deleter: &dyn Delete = provider.conn_ref(); deleter.delete(intent.id)?; } @@ -1140,7 +1195,12 @@ where for intent in intents { if let Some(post_commit_data) = intent.post_commit_data { - tracing::debug!(intent.id, intent.kind = %intent.kind, "taking post commit action"); + tracing::debug!( + inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), + intent.id, + intent.kind = %intent.kind, "taking post commit action" + ); let post_commit_action = PostCommitAction::from_bytes(post_commit_data.as_slice())?; match post_commit_action { @@ -1167,7 +1227,7 @@ where None => SYNC_UPDATE_INSTALLATIONS_INTERVAL_NS, }; - let now_ns = crate::utils::time::now_ns(); + let now_ns = xmtp_common::time::now_ns(); let last_ns = provider .conn_ref() .get_installations_time_checked(self.group_id.clone())?; @@ -1203,10 +1263,15 @@ where return Ok(()); } - debug!("Adding missing installations {:?}", intent_data); + debug!( + inbox_id = self.client.inbox_id(), + installation_id = %self.client.installation_id(), + "Adding missing installations {:?}", + intent_data + ); - let intent = self.queue_intent_with_conn( - provider.conn_ref(), + let intent = self.queue_intent( + provider, IntentKind::UpdateGroupMembership, intent_data.into(), )?; @@ -1333,6 +1398,73 @@ where try_join_all(futures).await?; Ok(()) } + + /// Provides hmac keys for a range of epochs around current epoch + /// `group.hmac_keys(-1..=1)`` will provide 3 keys consisting of last epoch, current epoch, and next epoch + /// `group.hmac_keys(0..=0) will provide 1 key, consisting of only the current epoch + #[tracing::instrument(level = "trace", skip_all)] + pub fn hmac_keys( + &self, + epoch_delta_range: RangeInclusive, + ) -> Result, StorageError> { + let conn = self.client.store().conn()?; + + let preferences = StoredUserPreferences::load(&conn)?; + let mut ikm = match preferences.hmac_key { + Some(ikm) => ikm, + None => { + let local_events = self.client.local_events(); + StoredUserPreferences::new_hmac_key(&conn, local_events)? + } + }; + ikm.extend(&self.group_id); + let hkdf = Hkdf::::new(Some(HMAC_SALT), &ikm); + + let mut result = vec![]; + let current_epoch = hmac_epoch(); + for delta in epoch_delta_range { + let epoch = current_epoch + delta; + + let mut info = self.group_id.clone(); + info.extend(&epoch.to_le_bytes()); + + let mut key = [0; 42]; + hkdf.expand(&info, &mut key).expect("Length is correct"); + + result.push(HmacKey { key, epoch }); + } + + Ok(result) + } + + #[tracing::instrument(level = "trace", skip_all)] + pub(super) fn prepare_group_messages( + &self, + payloads: Vec<&[u8]>, + ) -> Result, GroupError> { + let hmac_key = self + .hmac_keys(0..=0)? + .pop() + .expect("Range of count 1 was provided."); + let sender_hmac = + Hmac::::new_from_slice(&hmac_key.key).expect("HMAC can take key of any size"); + + let mut result = vec![]; + for payload in payloads { + let mut sender_hmac = sender_hmac.clone(); + sender_hmac.update(payload); + let sender_hmac = sender_hmac.finalize(); + + result.push(GroupMessageInput { + version: Some(GroupMessageInputVersion::V1(GroupMessageInputV1 { + data: payload.to_vec(), + sender_hmac: sender_hmac.into_bytes().to_vec(), + })), + }); + } + + Ok(result) + } } // Extracts the message sender, but does not do any validation to ensure that the @@ -1353,10 +1485,10 @@ fn extract_message_sender( } let basic_credential = BasicCredential::try_from(decrypted_message.credential().clone())?; - return Err(GroupMessageProcessingError::InvalidSender { + Err(GroupMessageProcessingError::InvalidSender { message_time_ns: message_created_ns, credential: basic_credential.identity().to_vec(), - }); + }) } // Takes UpdateGroupMembershipIntentData and applies it to the openmls group @@ -1392,7 +1524,7 @@ async fn apply_update_group_membership_intent( let mut new_key_packages: Vec = vec![]; if !installation_diff.added_installations.is_empty() { - let my_installation_id = &client.context().installation_public_key(); + let my_installation_id = &client.context().installation_public_key().to_vec(); // Go to the network and load the key packages for any new installation let key_packages = client .get_key_packages_for_installation_ids( @@ -1515,4 +1647,30 @@ pub(crate) mod tests { } future::join_all(futures).await; } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + async fn hmac_keys_work_as_expected() { + let wallet = generate_local_wallet(); + let amal = Arc::new(ClientBuilder::new_test_client(&wallet).await); + let amal_group: Arc> = + Arc::new(amal.create_group(None, Default::default()).unwrap()); + + let hmac_keys = amal_group.hmac_keys(-1..=1).unwrap(); + let current_hmac_key = amal_group.hmac_keys(0..=0).unwrap().pop().unwrap(); + assert_eq!(hmac_keys.len(), 3); + assert_eq!(hmac_keys[1].key, current_hmac_key.key); + assert_eq!(hmac_keys[1].epoch, current_hmac_key.epoch); + + // Make sure the keys are different + assert_ne!(hmac_keys[0].key, hmac_keys[1].key); + assert_ne!(hmac_keys[0].key, hmac_keys[2].key); + assert_ne!(hmac_keys[1].key, hmac_keys[2].key); + + // Make sure the epochs align + let current_epoch = hmac_epoch(); + assert_eq!(hmac_keys[0].epoch, current_epoch - 1); + assert_eq!(hmac_keys[1].epoch, current_epoch); + assert_eq!(hmac_keys[2].epoch, current_epoch + 1); + } } diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 34232239b..9213c38ef 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -11,6 +11,7 @@ pub(super) mod mls_sync; pub(super) mod subscriptions; pub mod validated_commit; +use device_sync::preference_sync::UserPreferenceUpdate; use intents::SendMessageIntentData; use mls_sync::GroupMessageProcessingError; use openmls::{ @@ -58,6 +59,7 @@ use self::{ validated_commit::CommitValidationError, }; use std::{collections::HashSet, sync::Arc}; +use xmtp_common::time::now_ns; use xmtp_cryptography::signature::{sanitize_evm_addresses, AddressValidationError}; use xmtp_id::{InboxId, InboxIdRef}; use xmtp_proto::xmtp::mls::{ @@ -83,20 +85,20 @@ use crate::{ identity::{parse_credential, IdentityError}, identity_updates::{load_identity_updates, InstallationDiffError}, intents::ProcessIntentError, - retry::RetryableError, storage::{ consent_record::{ConsentState, ConsentType, StoredConsentRecord}, db_connection::DbConnection, group::{ConversationType, GroupMembershipState, StoredGroup}, group_intent::IntentKind, group_message::{DeliveryStatus, GroupMessageKind, MsgQueryArgs, StoredGroupMessage}, - sql_key_store, + sql_key_store, StorageError, }, subscriptions::{LocalEventError, LocalEvents}, - utils::{id::calculate_message_id, time::now_ns}, + utils::id::calculate_message_id, xmtp_openmls_provider::XmtpOpenMlsProvider, Store, }; +use xmtp_common::retry::RetryableError; #[derive(Debug, Error)] pub enum GroupError { @@ -284,6 +286,12 @@ impl Clone for MlsGroup { } } +pub struct HmacKey { + pub key: [u8; 42], + // # of 30 day periods since unix epoch + pub epoch: i64, +} + #[derive(Debug, Clone, PartialEq)] pub enum UpdateAdminListType { Add, @@ -318,8 +326,8 @@ impl MlsGroup { /// Instantiate a new [`XmtpOpenMlsProvider`] pulling a connection from the database. /// prefer to use an already-instantiated mls provider if possible. - pub fn mls_provider(&self) -> Result { - Ok(self.context().mls_provider()?) + pub fn mls_provider(&self) -> Result { + self.context().mls_provider() } // Load the stored OpenMLS group from the OpenMLS provider's keystore @@ -337,15 +345,14 @@ impl MlsGroup { } // Create a new group and save it to the DB - pub fn create_and_insert( + pub(crate) fn create_and_insert( client: Arc, + provider: &XmtpOpenMlsProvider, membership_state: GroupMembershipState, permissions_policy_set: PolicySet, opts: GroupMetadataOptions, ) -> Result { let context = client.context(); - let conn = context.store().conn()?; - let provider = XmtpOpenMlsProvider::new(conn); let creator_inbox_id = context.inbox_id(); let protected_metadata = build_protected_metadata_extension(creator_inbox_id, ConversationType::Group)?; @@ -360,7 +367,7 @@ impl MlsGroup { )?; let mls_group = OpenMlsGroup::new( - &provider, + provider, &context.identity.installation_keys, &group_config, CredentialWithKey { @@ -387,14 +394,13 @@ impl MlsGroup { } // Create a new DM and save it to the DB - pub fn create_dm_and_insert( + pub(crate) fn create_dm_and_insert( + provider: &XmtpOpenMlsProvider, client: Arc, membership_state: GroupMembershipState, dm_target_inbox_id: InboxId, ) -> Result { let context = client.context(); - let conn = context.store().conn()?; - let provider = XmtpOpenMlsProvider::new(conn); let protected_metadata = build_dm_protected_metadata_extension(context.inbox_id(), dm_target_inbox_id.clone())?; let mutable_metadata = @@ -546,10 +552,10 @@ impl MlsGroup { pub(crate) fn create_and_insert_sync_group( client: Arc, + provider: &XmtpOpenMlsProvider, ) -> Result, GroupError> { let context = client.context(); let creator_inbox_id = context.inbox_id(); - let provider = client.mls_provider()?; let protected_metadata = build_protected_metadata_extension(creator_inbox_id, ConversationType::Sync)?; @@ -591,6 +597,7 @@ impl MlsGroup { /// Send a message on this users XMTP [`Client`]. pub async fn send_message(&self, message: &[u8]) -> Result, GroupError> { + tracing::debug!(inbox_id = self.client.inbox_id(), "sending message"); let conn = self.context().store().conn()?; let provider = XmtpOpenMlsProvider::from(conn); self.send_message_with_provider(message, &provider).await @@ -606,9 +613,8 @@ impl MlsGroup { self.maybe_update_installations(provider, update_interval_ns) .await?; - let message_id = self.prepare_message(message, provider.conn_ref(), |now| { - Self::into_envelope(message, now) - }); + let message_id = + self.prepare_message(message, provider, |now| Self::into_envelope(message, now)); self.sync_until_last_intent_resolved(provider).await?; @@ -646,9 +652,9 @@ impl MlsGroup { /// Send a message, optimistically returning the ID of the message before the result of a message publish. pub fn send_message_optimistic(&self, message: &[u8]) -> Result, GroupError> { - let conn = self.context().store().conn()?; + let provider = self.mls_provider()?; let message_id = - self.prepare_message(message, &conn, |now| Self::into_envelope(message, now))?; + self.prepare_message(message, &provider, |now| Self::into_envelope(message, now))?; Ok(message_id) } @@ -662,7 +668,7 @@ impl MlsGroup { fn prepare_message( &self, message: &[u8], - conn: &DbConnection, + provider: &XmtpOpenMlsProvider, envelope: F, ) -> Result, GroupError> where @@ -676,7 +682,7 @@ impl MlsGroup { .map_err(GroupError::EncodeError)?; let intent_data: Vec = SendMessageIntentData::new(encoded_envelope).into(); - self.queue_intent_with_conn(conn, IntentKind::SendMessage, intent_data)?; + self.queue_intent(provider, IntentKind::SendMessage, intent_data)?; // store this unpublished message locally before sending let message_id = calculate_message_id(&self.group_id, message, &now.to_string()); @@ -686,11 +692,11 @@ impl MlsGroup { decrypted_message_bytes: message.to_vec(), sent_at_ns: now, kind: GroupMessageKind::Application, - sender_installation_id: self.context().installation_public_key(), + sender_installation_id: self.context().installation_public_key().into(), sender_inbox_id: self.context().inbox_id().to_string(), delivery_status: DeliveryStatus::Unpublished, }; - group_message.store(conn)?; + group_message.store(provider.conn_ref())?; Ok(message_id) } @@ -728,8 +734,9 @@ impl MlsGroup { .api() .get_inbox_ids(account_addresses.clone()) .await?; + let provider = self.mls_provider()?; // get current number of users in group - let member_count = self.members().await?.len(); + let member_count = self.members_with_provider(&provider).await?.len(); if member_count + inbox_id_map.len() > MAX_GROUP_SIZE { return Err(GroupError::UserLimitExceeded); } @@ -743,8 +750,11 @@ impl MlsGroup { )); } - self.add_members_by_inbox_id(&inbox_id_map.into_values().collect::>()) - .await + self.add_members_by_inbox_id_with_provider( + &provider, + &inbox_id_map.into_values().collect::>(), + ) + .await } #[tracing::instrument(level = "trace", skip_all)] @@ -753,9 +763,19 @@ impl MlsGroup { inbox_ids: &[S], ) -> Result<(), GroupError> { let provider = self.client.mls_provider()?; + self.add_members_by_inbox_id_with_provider(&provider, inbox_ids) + .await + } + + #[tracing::instrument(level = "trace", skip_all)] + pub async fn add_members_by_inbox_id_with_provider>( + &self, + provider: &XmtpOpenMlsProvider, + inbox_ids: &[S], + ) -> Result<(), GroupError> { let ids = inbox_ids.iter().map(AsRef::as_ref).collect::>(); let intent_data = self - .get_membership_update_intent(&provider, ids.as_slice(), &[]) + .get_membership_update_intent(provider, ids.as_slice(), &[]) .await?; // TODO:nm this isn't the best test for whether the request is valid @@ -766,13 +786,13 @@ impl MlsGroup { return Ok(()); } - let intent = self.queue_intent_with_conn( - provider.conn_ref(), + let intent = self.queue_intent( + provider, IntentKind::UpdateGroupMembership, intent_data.into(), )?; - self.sync_until_intent_resolved(&provider, intent.id).await + self.sync_until_intent_resolved(provider, intent.id).await } /// Removes members from the group by their account addresses. @@ -815,8 +835,8 @@ impl MlsGroup { .get_membership_update_intent(&provider, &[], inbox_ids) .await?; - let intent = self.queue_intent_with_conn( - provider.conn_ref(), + let intent = self.queue_intent( + &provider, IntentKind::UpdateGroupMembership, intent_data.into(), )?; @@ -833,7 +853,7 @@ impl MlsGroup { } let intent_data: Vec = UpdateMetadataIntentData::new_update_group_name(group_name).into(); - let intent = self.queue_intent(IntentKind::MetadataUpdate, intent_data)?; + let intent = self.queue_intent(&provider, IntentKind::MetadataUpdate, intent_data)?; self.sync_until_intent_resolved(&provider, intent.id).await } @@ -862,7 +882,7 @@ impl MlsGroup { ) .into(); - let intent = self.queue_intent(IntentKind::UpdatePermission, intent_data)?; + let intent = self.queue_intent(&provider, IntentKind::UpdatePermission, intent_data)?; self.sync_until_intent_resolved(&provider, intent.id).await } @@ -892,7 +912,7 @@ impl MlsGroup { } let intent_data: Vec = UpdateMetadataIntentData::new_update_group_description(group_description).into(); - let intent = self.queue_intent(IntentKind::MetadataUpdate, intent_data)?; + let intent = self.queue_intent(&provider, IntentKind::MetadataUpdate, intent_data)?; self.sync_until_intent_resolved(&provider, intent.id).await } @@ -922,7 +942,7 @@ impl MlsGroup { let intent_data: Vec = UpdateMetadataIntentData::new_update_group_image_url_square(group_image_url_square) .into(); - let intent = self.queue_intent(IntentKind::MetadataUpdate, intent_data)?; + let intent = self.queue_intent(&provider, IntentKind::MetadataUpdate, intent_data)?; self.sync_until_intent_resolved(&provider, intent.id).await } @@ -954,7 +974,7 @@ impl MlsGroup { } let intent_data: Vec = UpdateMetadataIntentData::new_update_group_pinned_frame_url(pinned_frame_url).into(); - let intent = self.queue_intent(IntentKind::MetadataUpdate, intent_data)?; + let intent = self.queue_intent(&provider, IntentKind::MetadataUpdate, intent_data)?; self.sync_until_intent_resolved(&provider, intent.id).await } @@ -1037,7 +1057,7 @@ impl MlsGroup { }; let intent_data: Vec = UpdateAdminListIntentData::new(intent_action_type, inbox_id).into(); - let intent = self.queue_intent(IntentKind::UpdateAdminList, intent_data)?; + let intent = self.queue_intent(&provider, IntentKind::UpdateAdminList, intent_data)?; self.sync_until_intent_resolved(&provider, intent.id).await } @@ -1089,10 +1109,12 @@ impl MlsGroup { if self.client.history_sync_url().is_some() { // Dispatch an update event so it can be synced across devices - self.client + let _ = self + .client .local_events() - .send(LocalEvents::OutgoingConsentUpdates(vec![consent_record])) - .map_err(|e| GroupError::Generic(e.to_string()))?; + .send(LocalEvents::OutgoingPreferenceUpdates(vec![ + UserPreferenceUpdate::ConsentUpdate(consent_record), + ])); } Ok(()) @@ -1100,9 +1122,9 @@ impl MlsGroup { /// Update this installation's leaf key in the group by creating a key update commit pub async fn key_update(&self) -> Result<(), GroupError> { - let intent = self.queue_intent(IntentKind::KeyUpdate, vec![])?; - self.sync_until_intent_resolved(&self.client.mls_provider()?, intent.id) - .await + let provider = self.client.mls_provider()?; + let intent = self.queue_intent(&provider, IntentKind::KeyUpdate, vec![])?; + self.sync_until_intent_resolved(&provider, intent.id).await } /// Checks if the the current user is active in the group. @@ -1130,8 +1152,7 @@ impl MlsGroup { } pub fn permissions(&self) -> Result { - let conn = self.context().store().conn()?; - let provider = XmtpOpenMlsProvider::new(conn); + let provider = self.mls_provider()?; let mls_group = self.load_mls_group(&provider)?; Ok(extract_group_permissions(&mls_group)?) @@ -1493,9 +1514,9 @@ async fn validate_initial_group_membership( let futures: Vec<_> = membership .members - .into_iter() + .iter() .map(|(inbox_id, sequence_id)| { - client.get_association_state(conn, inbox_id, Some(sequence_id as i64)) + client.get_association_state(conn, inbox_id, Some(*sequence_id as i64)) }) .collect(); @@ -1589,14 +1610,15 @@ pub(crate) mod tests { use openmls::prelude::Member; use prost::Message; use std::sync::Arc; + use wasm_bindgen_test::wasm_bindgen_test; + use xmtp_common::assert_err; + use xmtp_content_types::{group_updated::GroupUpdatedCodec, ContentCodec}; use xmtp_cryptography::utils::generate_local_wallet; use xmtp_proto::xmtp::mls::api::v1::group_message::Version; use xmtp_proto::xmtp::mls::message_contents::EncodedContent; use crate::{ - assert_err, builder::ClientBuilder, - codecs::{group_updated::GroupUpdatedCodec, ContentCodec}, groups::{ build_dm_protected_metadata_extension, build_mutable_metadata_extension_default, build_protected_metadata_extension, @@ -1623,7 +1645,7 @@ pub(crate) mod tests { async fn receive_group_invite(client: &FullXmtpClient) -> MlsGroup { client - .sync_welcomes(&client.store().conn().unwrap()) + .sync_welcomes(&client.mls_provider().unwrap()) .await .unwrap(); let mut groups = client.find_groups(GroupQueryArgs::default()).unwrap(); @@ -1667,14 +1689,17 @@ pub(crate) mod tests { let serialized_welcome = welcome.tls_serialize_detached().unwrap(); let send_welcomes_action = SendWelcomesAction::new( vec![Installation { - installation_key: new_member_client.installation_public_key(), + installation_key: new_member_client.installation_public_key().into(), hpke_public_key: hpke_init_key, }], serialized_welcome, ); + let messages = sender_group + .prepare_group_messages(vec![serialized_commit.as_slice()]) + .unwrap(); sender_client .api_client - .send_group_messages(vec![serialized_commit.as_slice()]) + .send_group_messages(messages) .await .unwrap(); sender_group @@ -1683,8 +1708,7 @@ pub(crate) mod tests { .unwrap(); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_send_message() { let wallet = generate_local_wallet(); let client = ClientBuilder::new_test_client(&wallet).await; @@ -1701,8 +1725,7 @@ pub(crate) mod tests { assert_eq!(messages.len(), 2); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_receive_self_message() { let wallet = generate_local_wallet(); let client = ClientBuilder::new_test_client(&wallet).await; @@ -1755,8 +1778,7 @@ pub(crate) mod tests { } // Test members function from non group creator - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_members_func_from_non_creator() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -1771,7 +1793,7 @@ pub(crate) mod tests { // Get bola's version of the same group let bola_groups = bola - .sync_welcomes(&bola.store().conn().unwrap()) + .sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_group = bola_groups.first().unwrap(); @@ -1812,8 +1834,7 @@ pub(crate) mod tests { // Amal and Bola will both try and add Charlie from the same epoch. // The group should resolve to a consistent state - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_member_conflict() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -1830,7 +1851,7 @@ pub(crate) mod tests { // Get bola's version of the same group let bola_groups = bola - .sync_welcomes(&bola.store().conn().unwrap()) + .sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_group = bola_groups.first().unwrap(); @@ -1912,8 +1933,9 @@ pub(crate) mod tests { #[cfg(not(target_arch = "wasm32"))] fn test_create_from_welcome_validation() { use crate::groups::{build_group_membership_extension, group_membership::GroupMembership}; - use crate::{assert_logged, utils::test::traced_test}; - traced_test(|| async { + use xmtp_common::assert_logged; + xmtp_common::traced_test!(async { + tracing::info!("TEST"); let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bo = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -1940,15 +1962,14 @@ pub(crate) mod tests { force_add_member(&alix, &bo, &alix_group, &mut mls_group, &provider).await; // Bo should not be able to actually read this group - bo.sync_welcomes(&bo.store().conn().unwrap()).await.unwrap(); + bo.sync_welcomes(&bo.mls_provider().unwrap()).await.unwrap(); let groups = bo.find_groups(GroupQueryArgs::default()).unwrap(); assert_eq!(groups.len(), 0); assert_logged!("failed to create group from welcome", 1); }); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_inbox() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let client_2 = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -1972,8 +1993,7 @@ pub(crate) mod tests { assert_eq!(messages.len(), 1); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_invalid_member() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let group = client @@ -1985,8 +2005,7 @@ pub(crate) mod tests { assert!(result.is_err()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_unregistered_member() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let unconnected_wallet_address = generate_local_wallet().get_address(); @@ -1998,8 +2017,7 @@ pub(crate) mod tests { assert!(result.is_err()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_remove_inbox() { let client_1 = ClientBuilder::new_test_client(&generate_local_wallet()).await; // Add another client onto the network @@ -2037,8 +2055,7 @@ pub(crate) mod tests { assert_eq!(messages.len(), 2); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_key_update() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_client = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2068,7 +2085,7 @@ pub(crate) mod tests { group.send_message(b"hello").await.expect("send message"); bola_client - .sync_welcomes(&bola_client.store().conn().unwrap()) + .sync_welcomes(&bola_client.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola_client.find_groups(GroupQueryArgs::default()).unwrap(); @@ -2078,8 +2095,7 @@ pub(crate) mod tests { assert_eq!(bola_messages.len(), 1); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_post_commit() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let client_2 = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2102,8 +2118,7 @@ pub(crate) mod tests { assert_eq!(welcome_messages.len(), 1); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_remove_by_account_address() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_wallet = &generate_local_wallet(); @@ -2151,8 +2166,7 @@ pub(crate) mod tests { .unwrap()) } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_removed_members_cannot_send_message_to_others() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_wallet = &generate_local_wallet(); @@ -2213,8 +2227,7 @@ pub(crate) mod tests { assert!(amal_messages.is_empty()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_missing_installations() { // Setup for test let amal_wallet = generate_local_wallet(); @@ -2247,11 +2260,7 @@ pub(crate) mod tests { assert_eq!(num_members, 3); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_self_resolve_epoch_mismatch() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2301,8 +2310,7 @@ pub(crate) mod tests { assert!(expected_latest_message.eq(&dave_latest_message.decrypted_message_bytes)); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_permissions() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2328,8 +2336,7 @@ pub(crate) mod tests { .is_err(),); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_options() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2371,8 +2378,7 @@ pub(crate) mod tests { assert_eq!(amal_group_pinned_frame_url, "pinned frame"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] #[ignore] async fn test_max_limit_add() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2397,8 +2403,7 @@ pub(crate) mod tests { .is_err(),); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_mutable_data() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2418,14 +2423,14 @@ pub(crate) mod tests { .attributes .get(&MetadataField::GroupName.to_string()) .unwrap() - .eq("")); + .is_empty()); // Add bola to the group amal_group .add_members_by_inbox_id(&[bola.inbox_id()]) .await .unwrap(); - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); @@ -2440,7 +2445,7 @@ pub(crate) mod tests { .attributes .get(&MetadataField::GroupName.to_string()) .unwrap() - .eq("")); + .is_empty()); // Update group name amal_group @@ -2490,8 +2495,7 @@ pub(crate) mod tests { assert_eq!(bola_group_name, "New Group Name 1"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_update_group_image_url_square() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2509,7 +2513,7 @@ pub(crate) mod tests { .attributes .get(&MetadataField::GroupImageUrlSquare.to_string()) .unwrap() - .eq("")); + .is_empty()); // Update group name amal_group @@ -2529,8 +2533,7 @@ pub(crate) mod tests { assert_eq!(amal_group_image_url, "a url"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_update_group_pinned_frame_url() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2548,7 +2551,7 @@ pub(crate) mod tests { .attributes .get(&MetadataField::GroupPinnedFrameUrl.to_string()) .unwrap() - .eq("")); + .is_empty()); // Update group name amal_group @@ -2568,8 +2571,7 @@ pub(crate) mod tests { assert_eq!(amal_group_pinned_frame_url, "a frame url"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_mutable_data_group_permissions() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_wallet = generate_local_wallet(); @@ -2589,14 +2591,14 @@ pub(crate) mod tests { .attributes .get(&MetadataField::GroupName.to_string()) .unwrap() - .eq("")); + .is_empty()); // Add bola to the group amal_group .add_members(&[bola_wallet.get_address()]) .await .unwrap(); - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); @@ -2610,7 +2612,7 @@ pub(crate) mod tests { .attributes .get(&MetadataField::GroupName.to_string()) .unwrap() - .eq("")); + .is_empty()); // Update group name amal_group @@ -2658,8 +2660,7 @@ pub(crate) mod tests { assert_eq!(amal_group_name, "New Group Name 2"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_admin_list_update() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_wallet = generate_local_wallet(); @@ -2678,7 +2679,7 @@ pub(crate) mod tests { .add_members(&[bola_wallet.get_address()]) .await .unwrap(); - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); @@ -2696,7 +2697,7 @@ pub(crate) mod tests { assert!(super_admin_list.contains(&amal.inbox_id().to_string())); // Verify that bola can not add caro because they are not an admin - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); @@ -2765,7 +2766,7 @@ pub(crate) mod tests { .contains(&bola.inbox_id().to_string())); // Verify that bola can not add charlie because they are not an admin - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); @@ -2778,8 +2779,7 @@ pub(crate) mod tests { .expect_err("expected err"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_super_admin_list_update() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2796,7 +2796,7 @@ pub(crate) mod tests { .add_members_by_inbox_id(&[bola.inbox_id()]) .await .unwrap(); - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); @@ -2814,7 +2814,7 @@ pub(crate) mod tests { assert!(super_admin_list.contains(&amal.inbox_id().to_string())); // Verify that bola can not add caro as an admin because they are not a super admin - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); @@ -2889,8 +2889,7 @@ pub(crate) mod tests { .expect_err("expected err"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_members_permission_level_update() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2987,8 +2986,7 @@ pub(crate) mod tests { assert_eq!(count_member, 0, "no members have no admin status"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_staged_welcome() { // Create Clients let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3008,7 +3006,7 @@ pub(crate) mod tests { // Bola syncs groups - this will decrypt the Welcome, identify who added Bola // and then store that value on the group and insert into the database let bola_groups = bola - .sync_welcomes(&bola.store().conn().unwrap()) + .sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); @@ -3031,8 +3029,7 @@ pub(crate) mod tests { ); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_can_read_group_creator_inbox_id() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let policy_set = Some(PreconfiguredPolicies::AllMembers.to_policy_set()); @@ -3058,8 +3055,7 @@ pub(crate) mod tests { assert_eq!(protected_metadata.creator_inbox_id, amal.inbox_id()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_can_update_gce_after_failed_commit() { // Step 1: Amal creates a group let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3077,7 +3073,7 @@ pub(crate) mod tests { .unwrap(); // Step 3: Verify that Bola can update the group name, and amal sees the update - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); @@ -3127,8 +3123,7 @@ pub(crate) mod tests { assert_eq!(bola_group_name, "Name Update 2"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_can_update_permissions_after_group_creation() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let policy_set = Some(PreconfiguredPolicies::AdminsOnly.to_policy_set()); @@ -3145,7 +3140,7 @@ pub(crate) mod tests { // Step 3: Bola attemps to add Caro, but fails because group is admin only let caro = ClientBuilder::new_test_client(&generate_local_wallet()).await; - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); @@ -3193,8 +3188,7 @@ pub(crate) mod tests { assert_eq!(members.len(), 3); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_optimistic_send() { let amal = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bola_wallet = generate_local_wallet(); @@ -3281,8 +3275,7 @@ pub(crate) mod tests { ); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_dm_creation() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3290,7 +3283,7 @@ pub(crate) mod tests { // Amal creates a dm group targetting bola let amal_dm = amal - .create_dm_by_inbox_id(bola.inbox_id().to_string()) + .create_dm_by_inbox_id(&amal.mls_provider().unwrap(), bola.inbox_id().to_string()) .await .unwrap(); @@ -3308,7 +3301,7 @@ pub(crate) mod tests { assert_eq!(members.len(), 2); // Bola can message amal - let _ = bola.sync_welcomes(&bola.store().conn().unwrap()).await; + let _ = bola.sync_welcomes(&bola.mls_provider().unwrap()).await; let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); let bola_dm: &MlsGroup<_> = bola_groups.first().unwrap(); @@ -3349,8 +3342,7 @@ pub(crate) mod tests { assert!(!is_bola_super_admin); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn process_messages_abort_on_retryable_error() { let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bo = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3404,8 +3396,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn skip_already_processed_messages() { let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3424,7 +3415,7 @@ pub(crate) mod tests { let alix_message = vec![1]; alix_group.send_message(&alix_message).await.unwrap(); bo_client - .sync_welcomes(&bo_client.store().conn().unwrap()) + .sync_welcomes(&bo_client.mls_provider().unwrap()) .await .unwrap(); let bo_groups = bo_client.find_groups(GroupQueryArgs::default()).unwrap(); @@ -3457,11 +3448,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 5) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 5))] async fn test_parallel_syncs() { let wallet = generate_local_wallet(); let alix1 = Arc::new(ClientBuilder::new_test_client(&wallet).await); @@ -3548,7 +3535,11 @@ pub(crate) mod tests { } group - .queue_intent(IntentKind::UpdateGroupMembership, intent_data.into()) + .queue_intent( + provider, + IntentKind::UpdateGroupMembership, + intent_data.into(), + ) .unwrap(); } @@ -3559,11 +3550,7 @@ pub(crate) mod tests { * We need to be safe even in situations where there are multiple * intents that do the same thing, leading to conflicts */ - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 5) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 5))] async fn add_missing_installs_reentrancy() { let wallet = generate_local_wallet(); let alix1 = ClientBuilder::new_test_client(&wallet).await; @@ -3640,11 +3627,7 @@ pub(crate) mod tests { .any(|m| m.decrypted_message_bytes == "hi from alix1".as_bytes())); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 5) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 5))] async fn respect_allow_epoch_increment() { let wallet = generate_local_wallet(); let client = ClientBuilder::new_test_client(&wallet).await; @@ -3684,8 +3667,7 @@ pub(crate) mod tests { ); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn test_get_and_set_consent() { let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3707,7 +3689,7 @@ pub(crate) mod tests { .await .unwrap(); - bola.sync_welcomes(&bola.store().conn().unwrap()) + bola.sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_groups = bola.find_groups(GroupQueryArgs::default()).unwrap(); @@ -3728,7 +3710,7 @@ pub(crate) mod tests { .await .unwrap(); - caro.sync_welcomes(&caro.store().conn().unwrap()) + caro.sync_welcomes(&caro.mls_provider().unwrap()) .await .unwrap(); let caro_groups = caro.find_groups(GroupQueryArgs::default()).unwrap(); @@ -3744,8 +3726,7 @@ pub(crate) mod tests { assert_eq!(caro_group.consent_state().unwrap(), ConsentState::Allowed); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] // TODO(rich): Generalize the test once fixed - test messages that are 0, 1, 2, 3, 4, 5 epochs behind async fn test_max_past_epochs() { // Create group with two members @@ -3761,7 +3742,7 @@ pub(crate) mod tests { .await .unwrap(); - bo.sync_welcomes(&bo.store().conn().unwrap()).await.unwrap(); + bo.sync_welcomes(&bo.mls_provider().unwrap()).await.unwrap(); let bo_groups = bo.find_groups(GroupQueryArgs::default()).unwrap(); let bo_group = bo_groups.first().unwrap(); @@ -3804,8 +3785,7 @@ pub(crate) mod tests { assert_eq!(alix_messages.len(), 3); // Fails here, 2 != 3 } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn test_validate_dm_group() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let added_by_inbox = "added_by_inbox_id"; diff --git a/xmtp_mls/src/groups/scoped_client.rs b/xmtp_mls/src/groups/scoped_client.rs index 49f25c319..e745114a1 100644 --- a/xmtp_mls/src/groups/scoped_client.rs +++ b/xmtp_mls/src/groups/scoped_client.rs @@ -3,9 +3,9 @@ use crate::{ api::ApiClientWrapper, client::{ClientError, XmtpMlsLocalContext}, identity_updates::{InstallationDiff, InstallationDiffError}, - intents::Intents, - storage::{DbConnection, EncryptedMessageStore}, + storage::{DbConnection, EncryptedMessageStore, StorageError}, subscriptions::LocalEvents, + types::InstallationId, verified_key_package_v2::VerifiedKeyPackageV2, xmtp_openmls_provider::XmtpOpenMlsProvider, Client, @@ -17,7 +17,7 @@ use xmtp_id::{ }; use xmtp_proto::{api_client::trait_impls::XmtpApi, xmtp::mls::api::v1::GroupMessage}; -#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(ScopedGroupClient: Send ))] +#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(ScopedGroupClient: Send))] #[cfg(not(target_arch = "wasm32"))] #[allow(unused)] pub trait LocalScopedGroupClient: Send + Sync + Sized { @@ -37,11 +37,13 @@ pub trait LocalScopedGroupClient: Send + Sync + Sized { self.context_ref().inbox_id() } - fn mls_provider(&self) -> Result { - self.context_ref().mls_provider() + fn installation_id(&self) -> InstallationId { + self.context_ref().installation_public_key() } - fn intents(&self) -> &Arc; + fn mls_provider(&self) -> Result { + self.context_ref().mls_provider() + } fn context_ref(&self) -> &Arc; @@ -62,17 +64,17 @@ pub trait LocalScopedGroupClient: Send + Sync + Sized { installation_ids: Vec>, ) -> Result, ClientError>; - async fn get_association_state( + async fn get_association_state<'a>( &self, conn: &DbConnection, - inbox_id: String, + inbox_id: InboxIdRef<'a>, to_sequence_id: Option, ) -> Result; - async fn batch_get_association_state( + async fn batch_get_association_state<'a>( &self, conn: &DbConnection, - identifiers: &[(String, Option)], + identifiers: &[(InboxIdRef<'a>, Option)], ) -> Result, ClientError>; async fn query_group_messages( @@ -101,11 +103,13 @@ pub trait ScopedGroupClient: Sized { self.context_ref().inbox_id() } - fn mls_provider(&self) -> Result { - self.context_ref().mls_provider() + fn installation_id(&self) -> InstallationId { + self.context_ref().installation_public_key() } - fn intents(&self) -> &Arc; + fn mls_provider(&self) -> Result { + self.context_ref().mls_provider() + } fn context_ref(&self) -> &Arc; @@ -126,17 +130,17 @@ pub trait ScopedGroupClient: Sized { installation_ids: Vec>, ) -> Result, ClientError>; - async fn get_association_state( + async fn get_association_state<'a>( &self, conn: &DbConnection, - inbox_id: String, + inbox_id: InboxIdRef<'a>, to_sequence_id: Option, ) -> Result; - async fn batch_get_association_state( + async fn batch_get_association_state<'a>( &self, conn: &DbConnection, - identifiers: &[(String, Option)], + identifiers: &[(InboxIdRef<'a>, Option)], ) -> Result, ClientError>; async fn query_group_messages( @@ -165,10 +169,6 @@ where Client::::context(self) } - fn intents(&self) -> &Arc { - crate::Client::::intents(self) - } - fn history_sync_url(&self) -> &Option { &self.history_sync_url } @@ -201,10 +201,10 @@ where .await } - async fn get_association_state( + async fn get_association_state<'a>( &self, conn: &DbConnection, - inbox_id: String, + inbox_id: InboxIdRef<'a>, to_sequence_id: Option, ) -> Result { crate::Client::::get_association_state( @@ -216,10 +216,10 @@ where .await } - async fn batch_get_association_state( + async fn batch_get_association_state<'a>( &self, conn: &DbConnection, - identifiers: &[(String, Option)], + identifiers: &[(InboxIdRef<'a>, Option)], ) -> Result, ClientError> { crate::Client::::batch_get_association_state(self, conn, identifiers) .await @@ -256,10 +256,6 @@ where (**self).store() } - fn intents(&self) -> &Arc { - (**self).intents() - } - fn inbox_id(&self) -> InboxIdRef<'_> { (**self).inbox_id() } @@ -268,7 +264,7 @@ where (**self).context_ref() } - fn mls_provider(&self) -> Result { + fn mls_provider(&self) -> Result { (**self).mls_provider() } @@ -298,10 +294,10 @@ where .await } - async fn get_association_state( + async fn get_association_state<'a>( &self, conn: &DbConnection, - inbox_id: String, + inbox_id: InboxIdRef<'a>, to_sequence_id: Option, ) -> Result { (**self) @@ -309,10 +305,10 @@ where .await } - async fn batch_get_association_state( + async fn batch_get_association_state<'a>( &self, conn: &DbConnection, - identifiers: &[(String, Option)], + identifiers: &[(InboxIdRef<'a>, Option)], ) -> Result, ClientError> { (**self) .batch_get_association_state(conn, identifiers) @@ -350,10 +346,6 @@ where (**self).history_sync_url() } - fn intents(&self) -> &Arc { - (**self).intents() - } - fn inbox_id(&self) -> InboxIdRef<'_> { (**self).inbox_id() } @@ -362,7 +354,7 @@ where (**self).context_ref() } - fn mls_provider(&self) -> Result { + fn mls_provider(&self) -> Result { (**self).mls_provider() } @@ -392,10 +384,10 @@ where .await } - async fn get_association_state( + async fn get_association_state<'a>( &self, conn: &DbConnection, - inbox_id: String, + inbox_id: InboxIdRef<'a>, to_sequence_id: Option, ) -> Result { (**self) @@ -403,10 +395,10 @@ where .await } - async fn batch_get_association_state( + async fn batch_get_association_state<'a>( &self, conn: &DbConnection, - identifiers: &[(String, Option)], + identifiers: &[(InboxIdRef<'a>, Option)], ) -> Result, ClientError> { (**self) .batch_get_association_state(conn, identifiers) diff --git a/xmtp_mls/src/groups/subscriptions.rs b/xmtp_mls/src/groups/subscriptions.rs index 5701bb882..eb354fe3c 100644 --- a/xmtp_mls/src/groups/subscriptions.rs +++ b/xmtp_mls/src/groups/subscriptions.rs @@ -14,14 +14,16 @@ use crate::storage::refresh_state::EntityKind; use crate::storage::StorageError; use crate::subscriptions::MessagesStreamInfo; use crate::subscriptions::SubscribeError; -use crate::{retry::Retry, retry_async}; +use crate::XmtpOpenMlsProvider; use prost::Message; +use xmtp_common::{retry_async, Retry}; use xmtp_proto::xmtp::mls::api::v1::GroupMessage; impl MlsGroup { /// Internal stream processing function pub(crate) async fn process_stream_entry( &self, + provider: &XmtpOpenMlsProvider, envelope: GroupMessage, ) -> Result { let msgv1 = extract_message_v1(envelope)?; @@ -45,8 +47,8 @@ impl MlsGroup { let msgv1 = &msgv1; self.context() .store() - .transaction_async(|provider| async move { - let mut openmls_group = self.load_mls_group(&provider)?; + .transaction_async(provider, |provider| async move { + let mut openmls_group = self.load_mls_group(provider)?; // Attempt processing immediately, but fail if the message is not an Application Message // Returning an error should roll back the DB tx @@ -60,7 +62,7 @@ impl MlsGroup { openmls_group.epoch() ); - self.process_message(&mut openmls_group, &provider, msgv1, false) + self.process_message(&mut openmls_group, provider, msgv1, false) .await // NOTE: We want to make sure we retry an error in process_message .map_err(SubscribeError::ReceiveGroup) @@ -78,7 +80,7 @@ impl MlsGroup { ); // Swallow errors here, since another process may have successfully saved the message // to the DB - if let Err(err) = self.sync_with_conn(&self.client.mls_provider()?).await { + if let Err(err) = self.sync_with_conn(provider).await { tracing::warn!( inbox_id = self.client.inbox_id(), group_id = hex::encode(&self.group_id), @@ -108,10 +110,8 @@ impl MlsGroup { // Load the message from the DB to handle cases where it may have been already processed in // another thread - let new_message = self - .context() - .store() - .conn()? + let new_message = provider + .conn_ref() .get_group_message_by_timestamp(&self.group_id, created_ns as i64)? .ok_or(SubscribeError::GroupMessageNotFound)?; @@ -133,20 +133,21 @@ impl MlsGroup { /// Converts some `SubscribeError` variants to an Option, if they are inconsequential. pub async fn process_streamed_group_message( &self, + provider: &XmtpOpenMlsProvider, envelope_bytes: Vec, ) -> Result { let envelope = GroupMessage::decode(envelope_bytes.as_slice())?; - self.process_stream_entry(envelope).await + self.process_stream_entry(provider, envelope).await } - pub async fn stream( - &self, + pub async fn stream<'a>( + &'a self, ) -> Result< - impl Stream> + use<'_, ScopedClient>, + impl Stream> + use<'a, ScopedClient>, ClientError, > where - ::ApiClient: XmtpMlsStreams + 'static, + ::ApiClient: XmtpMlsStreams + 'a, { let group_list = HashMap::from([( self.group_id.clone(), @@ -180,13 +181,15 @@ impl MlsGroup { } /// Stream messages from groups in `group_id_to_info` -pub(crate) async fn stream_messages( - client: &ScopedClient, +// TODO: Note when to use a None provider +#[tracing::instrument(level = "debug", skip_all)] +pub(crate) async fn stream_messages<'a, ScopedClient>( + client: &'a ScopedClient, group_id_to_info: Arc, MessagesStreamInfo>>, -) -> Result> + '_, ClientError> +) -> Result> + 'a, ClientError> where ScopedClient: ScopedGroupClient, - ::ApiClient: XmtpApi + XmtpMlsStreams + 'static, + ::ApiClient: XmtpApi + XmtpMlsStreams + 'a, { let filters: Vec = group_id_to_info .iter() @@ -199,10 +202,14 @@ where .then(move |res| { let group_id_to_info = group_id_to_info.clone(); async move { + let provider = client.mls_provider()?; let envelope = res.map_err(GroupError::from)?; - tracing::info!("Received message streaming payload"); let group_id = extract_group_id(&envelope)?; - tracing::info!("Extracted group id {}", hex::encode(&group_id)); + tracing::info!( + inbox_id = client.inbox_id(), + group_id = hex::encode(&group_id), + "Received message streaming payload" + ); let stream_info = group_id_to_info .get(&group_id) @@ -210,7 +217,8 @@ where "Received message for a non-subscribed group".to_string(), ))?; let mls_group = MlsGroup::new(client, group_id, stream_info.convo_created_at_ns); - mls_group.process_stream_entry(envelope).await + + mls_group.process_stream_entry(&provider, envelope).await } }) .inspect(|e| { @@ -255,6 +263,8 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use wasm_bindgen_test::wasm_bindgen_test; + use super::*; use tokio_stream::wrappers::UnboundedReceiverStream; use xmtp_cryptography::utils::generate_local_wallet; @@ -265,11 +275,7 @@ pub(crate) mod tests { }; use futures::StreamExt; - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 1) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_decode_group_message_bytes() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -292,8 +298,9 @@ pub(crate) mod tests { let message = messages.first().unwrap(); let mut message_bytes: Vec = Vec::new(); message.encode(&mut message_bytes).unwrap(); + let provider = amal.mls_provider().unwrap(); let message_again = amal_group - .process_streamed_group_message(message_bytes) + .process_streamed_group_message(&provider, message_bytes) .await; if let Ok(message) = message_again { @@ -303,11 +310,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_subscribe_messages() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -323,7 +326,7 @@ pub(crate) mod tests { // Get bola's version of the same group let bola_groups = bola - .sync_welcomes(&bola.store().conn().unwrap()) + .sync_welcomes(&bola.mls_provider().unwrap()) .await .unwrap(); let bola_group = Arc::new(bola_groups.first().unwrap().clone()); @@ -360,11 +363,7 @@ pub(crate) mod tests { assert_eq!(second_val.decrypted_message_bytes, "goodbye".as_bytes()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_subscribe_multiple() { let amal = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let group = Arc::new( @@ -402,11 +401,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 5) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_subscribe_membership_changes() { let amal = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -434,7 +429,7 @@ pub(crate) mod tests { // just to make sure stream is started let _ = start_rx.await; // Adding in a sleep, since the HTTP API client may acknowledge requests before they are ready - crate::sleep(core::time::Duration::from_millis(100)).await; + xmtp_common::time::sleep(core::time::Duration::from_millis(100)).await; amal_group .add_members_by_inbox_id(&[bola.inbox_id()]) diff --git a/xmtp_mls/src/groups/validated_commit.rs b/xmtp_mls/src/groups/validated_commit.rs index 567603a3d..f13557236 100644 --- a/xmtp_mls/src/groups/validated_commit.rs +++ b/xmtp_mls/src/groups/validated_commit.rs @@ -24,10 +24,9 @@ use xmtp_proto::xmtp::{ use crate::{ configuration::GROUP_MEMBERSHIP_EXTENSION_ID, identity_updates::{InstallationDiff, InstallationDiffError}, - retry::RetryableError, - retryable, storage::db_connection::DbConnection, }; +use xmtp_common::{retry::RetryableError, retryable}; use super::{ group_membership::{GroupMembership, MembershipDiff}, @@ -303,11 +302,7 @@ impl ValidatedCommit { .ok_or(CommitValidationError::SubjectDoesNotExist)?; let inbox_state = client - .get_association_state( - conn, - participant.inbox_id.clone(), - Some(*to_sequence_id as i64), - ) + .get_association_state(conn, &participant.inbox_id, Some(*to_sequence_id as i64)) .await .map_err(InstallationDiffError::from)?; diff --git a/xmtp_mls/src/hpke.rs b/xmtp_mls/src/hpke.rs index 19906a351..cf664043a 100644 --- a/xmtp_mls/src/hpke.rs +++ b/xmtp_mls/src/hpke.rs @@ -1,7 +1,5 @@ use crate::{ configuration::{CIPHERSUITE, WELCOME_HPKE_LABEL}, - retry::RetryableError, - retryable, storage::sql_key_store::{SqlKeyStoreError, KEY_PACKAGE_REFERENCES}, xmtp_openmls_provider::XmtpOpenMlsProvider, }; @@ -17,6 +15,7 @@ use openmls_rust_crypto::RustCrypto; use openmls_traits::OpenMlsProvider; use openmls_traits::{storage::StorageProvider, types::HpkeCiphertext}; use thiserror::Error; +use xmtp_common::{retryable, RetryableError}; #[derive(Debug, Error)] pub enum HpkeError { diff --git a/xmtp_mls/src/identity.rs b/xmtp_mls/src/identity.rs index 0f7ffdaa5..10a05344a 100644 --- a/xmtp_mls/src/identity.rs +++ b/xmtp_mls/src/identity.rs @@ -1,19 +1,16 @@ use std::sync::atomic::{AtomicBool, Ordering}; use crate::configuration::GROUP_PERMISSIONS_EXTENSION_ID; -use crate::retry::RetryableError; use crate::storage::db_connection::DbConnection; use crate::storage::identity::StoredIdentity; use crate::storage::sql_key_store::{SqlKeyStore, SqlKeyStoreError, KEY_PACKAGE_REFERENCES}; -use crate::storage::EncryptedMessageStore; use crate::{ api::{ApiClientWrapper, WrappedApiError}, configuration::{CIPHERSUITE, GROUP_MEMBERSHIP_EXTENSION_ID, MUTABLE_METADATA_EXTENSION_ID}, storage::StorageError, xmtp_openmls_provider::XmtpOpenMlsProvider, - XmtpApi, + Fetch, Store, XmtpApi, }; -use crate::{retryable, Fetch, Store}; use openmls::prelude::hash_ref::HashReference; use openmls::{ credentials::{errors::BasicCredentialError, BasicCredential, CredentialWithKey}, @@ -31,6 +28,7 @@ use prost::Message; use thiserror::Error; use tracing::debug; use tracing::info; +use xmtp_common::{retryable, RetryableError}; use xmtp_cryptography::{CredentialSign, XmtpInstallationCredential}; use xmtp_id::associations::unverified::UnverifiedSignature; use xmtp_id::associations::{AssociationError, InstallationKeyContext, PublicContext}; @@ -60,7 +58,12 @@ use xmtp_proto::xmtp::identity::MlsCredential; #[derive(Debug, Clone)] pub enum IdentityStrategy { /// Tries to get an identity from the disk store. If not found, getting one from backend. - CreateIfNotFound(InboxId, String, u64, Option>), // (inbox_id, address, nonce, legacy_signed_private_key) + CreateIfNotFound { + inbox_id: InboxId, + address: String, + nonce: u64, + legacy_signed_private_key: Option>, + }, /// Identity that is already in the disk store CachedOnly, /// An already-built Identity for testing purposes @@ -68,6 +71,32 @@ pub enum IdentityStrategy { ExternalIdentity(Identity), } +impl IdentityStrategy { + pub fn inbox_id(&self) -> Option> { + use IdentityStrategy::*; + match self { + CreateIfNotFound { ref inbox_id, .. } => Some(inbox_id), + _ => None, + } + } + + /// Create a new Identity Strategy, with [`IdentityStrategy::CreateIfNotFound`]. + /// If an Identity is not found in the local store, creates a new one. + pub fn new( + inbox_id: InboxId, + address: String, + nonce: u64, + legacy_signed_private_key: Option>, + ) -> Self { + Self::CreateIfNotFound { + inbox_id, + address, + nonce, + legacy_signed_private_key, + } + } +} + impl IdentityStrategy { /** * Initialize an identity from the given strategy. If a stored identity is found in the database, @@ -80,12 +109,12 @@ impl IdentityStrategy { pub(crate) async fn initialize_identity( self, api_client: &ApiClientWrapper, - store: &EncryptedMessageStore, + provider: &XmtpOpenMlsProvider, scw_signature_verifier: impl SmartContractSignatureVerifier, ) -> Result { + use IdentityStrategy::*; + info!("Initializing identity"); - let conn = store.conn()?; - let provider = XmtpOpenMlsProvider::new(conn); let stored_identity: Option = provider .conn_ref() .fetch(&())? @@ -94,15 +123,13 @@ impl IdentityStrategy { debug!("identity in store: {:?}", stored_identity); match self { - IdentityStrategy::CachedOnly => { - stored_identity.ok_or(IdentityError::RequiredIdentityNotFound) - } - IdentityStrategy::CreateIfNotFound( + CachedOnly => stored_identity.ok_or(IdentityError::RequiredIdentityNotFound), + CreateIfNotFound { inbox_id, address, nonce, legacy_signed_private_key, - ) => { + } => { if let Some(stored_identity) = stored_identity { if inbox_id != stored_identity.inbox_id { return Err(IdentityError::InboxIdMismatch { @@ -119,14 +146,14 @@ impl IdentityStrategy { nonce, legacy_signed_private_key, api_client, - &provider, + provider, scw_signature_verifier, ) .await } } #[cfg(test)] - IdentityStrategy::ExternalIdentity(identity) => Ok(identity), + ExternalIdentity(identity) => Ok(identity), } } } @@ -395,11 +422,14 @@ impl Identity { conn.get_latest_sequence_id_for_inbox(self.inbox_id.as_str()) } - #[allow(dead_code)] pub fn is_ready(&self) -> bool { self.is_ready.load(Ordering::SeqCst) } + pub(crate) fn set_ready(&self) { + self.is_ready.store(true, Ordering::SeqCst) + } + pub fn signature_request(&self) -> Option { self.signature_request.clone() } @@ -496,8 +526,6 @@ impl Identity { } self.rotate_key_package(provider, api_client).await?; - self.is_ready.store(true, Ordering::SeqCst); - Ok(StoredIdentity::try_from(self)?.store(provider.conn_ref())?) } diff --git a/xmtp_mls/src/identity_updates.rs b/xmtp_mls/src/identity_updates.rs index 83c0b9c5f..822c00bac 100644 --- a/xmtp_mls/src/identity_updates.rs +++ b/xmtp_mls/src/identity_updates.rs @@ -1,12 +1,9 @@ -use std::collections::{HashMap, HashSet}; - -use crate::{ - retry::{Retry, RetryableError}, - retry_async, retryable, - storage::association_state::StoredAssociationState, -}; +use crate::storage::association_state::StoredAssociationState; use futures::future::try_join_all; +use std::collections::{HashMap, HashSet}; use thiserror::Error; +use xmtp_common::{retry_async, retryable, Retry, RetryableError}; +use xmtp_cryptography::CredentialSign; use xmtp_id::{ associations::{ apply_update, @@ -15,12 +12,13 @@ use xmtp_id::{ unverified::{ UnverifiedIdentityUpdate, UnverifiedInstallationKeySignature, UnverifiedSignature, }, - AssociationError, AssociationState, AssociationStateDiff, IdentityUpdate, MemberIdentifier, - SignatureError, + AssociationError, AssociationState, AssociationStateDiff, IdentityAction, IdentityUpdate, + InstallationKeyContext, MemberIdentifier, SignatureError, }, - scw_verifier::SmartContractSignatureVerifier, + scw_verifier::{RemoteSignatureVerifier, SmartContractSignatureVerifier}, InboxIdRef, }; +use xmtp_proto::api_client::{ClientWithMetadata, XmtpIdentityClient, XmtpMlsClient}; use crate::{ api::{ApiClientWrapper, GetIdentityUpdatesV2Filter, InboxUpdate}, @@ -92,10 +90,10 @@ where /// Get the association state for all provided `inbox_id`/optional `sequence_id` tuples, using the cache when available /// If the association state is not available in the cache, this falls back to reconstructing the association state /// from Identity Updates in the network. - pub async fn batch_get_association_state>( + pub async fn batch_get_association_state( &self, conn: &DbConnection, - identifiers: &[(InboxId, Option)], + identifiers: &[(InboxIdRef<'a>, Option)], ) -> Result, ClientError> { let association_states = try_join_all( identifiers @@ -111,25 +109,24 @@ where } /// Get the latest association state available on the network for the given `inbox_id` - pub async fn get_latest_association_state>( + pub async fn get_latest_association_state( &self, conn: &DbConnection, - inbox_id: InboxId, + inbox_id: InboxIdRef<'a>, ) -> Result { - load_identity_updates(&self.api_client, conn, &[inbox_id.as_ref()]).await?; + load_identity_updates(&self.api_client, conn, &[inbox_id]).await?; self.get_association_state(conn, inbox_id, None).await } /// Get the association state for a given inbox_id up to the (and inclusive of) the `to_sequence_id` /// If no `to_sequence_id` is provided, use the latest value in the database - pub async fn get_association_state>( + pub async fn get_association_state( &self, conn: &DbConnection, - inbox_id: InboxId, + inbox_id: InboxIdRef<'a>, to_sequence_id: Option, ) -> Result { - let inbox_id = inbox_id.as_ref(); let updates = conn.get_identity_updates(inbox_id, None, to_sequence_id)?; let last_sequence_id = updates .last() @@ -167,35 +164,35 @@ where /// Calculate the changes between the `starting_sequence_id` and `ending_sequence_id` for the /// provided `inbox_id` - pub(crate) async fn get_association_state_diff>( + pub(crate) async fn get_association_state_diff( &self, conn: &DbConnection, - inbox_id: InboxId, + inbox_id: InboxIdRef<'a>, starting_sequence_id: Option, ending_sequence_id: Option, ) -> Result { tracing::debug!( "Computing diff for {:?} from {:?} to {:?}", - inbox_id.as_ref(), + inbox_id, starting_sequence_id, ending_sequence_id ); // If no starting sequence ID, get all updates from the beginning of the inbox's history up to the ending sequence ID if starting_sequence_id.is_none() { return Ok(self - .get_association_state(conn, inbox_id.as_ref(), ending_sequence_id) + .get_association_state(conn, inbox_id, ending_sequence_id) .await? .as_diff()); } // Get the initial state to compare against let initial_state = self - .get_association_state(conn, inbox_id.as_ref(), starting_sequence_id) + .get_association_state(conn, inbox_id, starting_sequence_id) .await?; // Get any identity updates that need to be applied let incremental_updates = - conn.get_identity_updates(inbox_id.as_ref(), starting_sequence_id, ending_sequence_id)?; + conn.get_identity_updates(inbox_id, starting_sequence_id, ending_sequence_id)?; let last_sequence_id = incremental_updates.last().map(|update| update.sequence_id); if ending_sequence_id.is_some() @@ -227,7 +224,7 @@ where if let Some(last_sequence_id) = last_sequence_id { StoredAssociationState::write_to_cache( conn, - inbox_id.as_ref().to_string(), + inbox_id.to_string(), last_sequence_id, final_state.clone(), )?; @@ -276,18 +273,32 @@ where } /// Generate a `AssociateWallet` signature request using an existing wallet and a new wallet address - pub fn associate_wallet( + pub async fn associate_wallet( &self, - existing_wallet_address: String, new_wallet_address: String, ) -> Result { tracing::info!("Associating new wallet with inbox_id {}", self.inbox_id()); let inbox_id = self.inbox_id(); let builder = SignatureRequestBuilder::new(inbox_id); + let installation_public_key = self.identity().installation_keys.verifying_key(); + let new_member_identifier = MemberIdentifier::Address(new_wallet_address); + + let mut signature_request = builder + .add_association(new_member_identifier, installation_public_key.into()) + .build(); - Ok(builder - .add_association(new_wallet_address.into(), existing_wallet_address.into()) - .build()) + let signature = self + .identity() + .installation_keys + .credential_sign::(signature_request.signature_text())?; + signature_request + .add_signature( + UnverifiedSignature::new_installation_key(signature, installation_public_key), + &self.scw_verifier, + ) + .await?; + + Ok(signature_request) } /// Revoke the given wallets from the association state for the client's inbox @@ -299,7 +310,7 @@ where let current_state = retry_async!( Retry::default(), (async { - self.get_association_state(&self.store().conn()?, &inbox_id, None) + self.get_association_state(&self.store().conn()?, inbox_id, None) .await }) )?; @@ -325,7 +336,7 @@ where let current_state = retry_async!( Retry::default(), (async { - self.get_association_state(&self.store().conn()?, &inbox_id, None) + self.get_association_state(&self.store().conn()?, inbox_id, None) .await }) )?; @@ -430,7 +441,7 @@ where let state_diff = self .get_association_state_diff( conn, - inbox_id, + inbox_id.as_str(), starting_sequence_id, new_group_membership.get(inbox_id).map(|i| *i as i64), ) @@ -514,6 +525,53 @@ async fn verify_updates( .await } +/// A static lookup method to verify if an identity is a member of an inbox +pub async fn is_member_of_association_state( + api_client: &ApiClientWrapper, + inbox_id: &str, + identifier: &MemberIdentifier, + scw_verifier: Option>, +) -> Result +where + Client: XmtpMlsClient + XmtpIdentityClient + ClientWithMetadata + Send + Sync, +{ + let filters = vec![GetIdentityUpdatesV2Filter { + inbox_id: inbox_id.to_string(), + sequence_id: None, + }]; + let mut updates = api_client.get_identity_updates_v2(filters).await?; + + let Some(updates) = updates.remove(inbox_id) else { + return Err(ClientError::Generic( + "Unable to find provided inbox_id".to_string(), + )); + }; + let updates: Vec<_> = updates.into_iter().map(|u| u.update).collect(); + + let mut association_state = None; + + let scw_verifier = scw_verifier.unwrap_or_else(|| { + Box::new(RemoteSignatureVerifier::new(api_client.api_client.clone())) + as Box + }); + + let updates: Vec<_> = updates + .iter() + .map(|u| u.to_verified(&scw_verifier)) + .collect(); + let updates = try_join_all(updates).await?; + + for update in updates { + association_state = + Some(update.update_state(association_state, update.client_timestamp_ns)?); + } + let association_state = association_state.ok_or(ClientError::Generic( + "Unable to create association state".to_string(), + ))?; + + Ok(association_state.get(identifier).is_some()) +} + #[cfg(test)] pub(crate) mod tests { #[cfg(target_arch = "wasm32")] @@ -532,22 +590,23 @@ pub(crate) mod tests { builder::ClientBuilder, groups::group_membership::GroupMembership, storage::{db_connection::DbConnection, identity_update::StoredIdentityUpdate}, - utils::test::{rand_vec, FullXmtpClient}, + utils::test::FullXmtpClient, Client, XmtpApi, }; + use xmtp_common::rand_vec; - use super::load_identity_updates; + use super::{is_member_of_association_state, load_identity_updates}; async fn get_association_state( client: &Client, - inbox_id: String, + inbox_id: &str, ) -> AssociationState where ApiClient: XmtpApi, Verifier: SmartContractSignatureVerifier, { let conn = client.store().conn().unwrap(); - load_identity_updates(&client.api_client, &conn, &[inbox_id.as_str()]) + load_identity_updates(&client.api_client, &conn, &[inbox_id]) .await .unwrap(); @@ -559,12 +618,51 @@ pub(crate) mod tests { fn insert_identity_update(conn: &DbConnection, inbox_id: &str, sequence_id: i64) { let identity_update = - StoredIdentityUpdate::new(inbox_id.to_string(), sequence_id, 0, rand_vec()); + StoredIdentityUpdate::new(inbox_id.to_string(), sequence_id, 0, rand_vec::<24>()); conn.insert_or_ignore_identity_updates(&[identity_update]) .expect("insert should succeed"); } + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + async fn test_is_member_of_association_state() { + let wallet = generate_local_wallet(); + let client = ClientBuilder::new_test_client(&wallet).await; + + let wallet2 = generate_local_wallet(); + + let mut request = client + .associate_wallet(wallet2.get_address()) + .await + .unwrap(); + add_wallet_signature(&mut request, &wallet2).await; + client.apply_signature_request(request).await.unwrap(); + + let conn = client.store().conn().unwrap(); + let state = client + .get_latest_association_state(&conn, client.inbox_id()) + .await + .unwrap(); + + // The installation, wallet1 address, and the newly associated wallet2 address + assert_eq!(state.members().len(), 3); + + let api_client = &client.api_client; + + // Check that the second wallet is associated with our new static helper + let is_member = is_member_of_association_state( + api_client, + client.inbox_id(), + &MemberIdentifier::Address(wallet2.get_address()), + None, + ) + .await + .unwrap(); + + assert!(is_member); + } + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn create_inbox_round_trip() { @@ -585,7 +683,7 @@ pub(crate) mod tests { .await .unwrap(); - let association_state = get_association_state(&client, inbox_id.to_string()).await; + let association_state = get_association_state(&client, &inbox_id).await; assert_eq!(association_state.members().len(), 2); assert_eq!(association_state.recovery_address(), &wallet_address); @@ -602,10 +700,10 @@ pub(crate) mod tests { let client = ClientBuilder::new_test_client(&wallet).await; let mut add_association_request = client - .associate_wallet(wallet_address.clone(), wallet_2_address.clone()) + .associate_wallet(wallet_2_address.clone()) + .await .unwrap(); - add_wallet_signature(&mut add_association_request, &wallet).await; add_wallet_signature(&mut add_association_request, &wallet_2).await; client @@ -613,7 +711,7 @@ pub(crate) mod tests { .await .unwrap(); - let association_state = get_association_state(&client, client.inbox_id().to_string()).await; + let association_state = get_association_state(&client, client.inbox_id()).await; let members = association_state.members_by_parent(&MemberIdentifier::Address(wallet_address.clone())); @@ -630,8 +728,8 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg(not(target_arch = "wasm32"))] fn cache_association_state() { - use crate::{assert_logged, utils::test::traced_test}; - traced_test(|| async { + use xmtp_common::assert_logged; + xmtp_common::traced_test!(async { let wallet = generate_local_wallet(); let wallet_2 = generate_local_wallet(); let wallet_address = wallet.get_address(); @@ -639,12 +737,12 @@ pub(crate) mod tests { let client = ClientBuilder::new_test_client(&wallet).await; let inbox_id = client.inbox_id(); - get_association_state(&client, inbox_id.to_string()).await; + get_association_state(&client, inbox_id).await; assert_logged!("Loaded association", 0); assert_logged!("Wrote association", 1); - let association_state = get_association_state(&client, inbox_id.to_string()).await; + let association_state = get_association_state(&client, inbox_id).await; assert_eq!(association_state.members().len(), 2); assert_eq!(association_state.recovery_address(), &wallet_address); @@ -656,10 +754,10 @@ pub(crate) mod tests { assert_logged!("Wrote association", 1); let mut add_association_request = client - .associate_wallet(wallet_address.clone(), wallet_2_address.clone()) + .associate_wallet(wallet_2_address.clone()) + .await .unwrap(); - add_wallet_signature(&mut add_association_request, &wallet).await; add_wallet_signature(&mut add_association_request, &wallet_2).await; client @@ -667,12 +765,12 @@ pub(crate) mod tests { .await .unwrap(); - get_association_state(&client, inbox_id.to_string()).await; + get_association_state(&client, inbox_id).await; assert_logged!("Loaded association", 1); assert_logged!("Wrote association", 2); - let association_state = get_association_state(&client, inbox_id.to_string()).await; + let association_state = get_association_state(&client, inbox_id).await; assert_logged!("Loaded association", 2); assert_logged!("Wrote association", 2); @@ -712,8 +810,8 @@ pub(crate) mod tests { let client_2 = ClientBuilder::new_test_client(&wallet_2).await; let client_3 = ClientBuilder::new_test_client(&wallet_3).await; - let client_2_installation_key = client_2.installation_public_key(); - let client_3_installation_key = client_3.installation_public_key(); + let client_2_installation_key = client_2.installation_public_key().to_vec(); + let client_3_installation_key = client_3.installation_public_key().to_vec(); let mut inbox_ids: Vec = vec![]; @@ -737,10 +835,10 @@ pub(crate) mod tests { .unwrap(); let new_wallet = generate_local_wallet(); let mut add_association_request = client - .associate_wallet(wallet.get_address(), new_wallet.get_address()) + .associate_wallet(new_wallet.get_address()) + .await .unwrap(); - add_wallet_signature(&mut add_association_request, &wallet).await; add_wallet_signature(&mut add_association_request, &new_wallet).await; client @@ -802,11 +900,11 @@ pub(crate) mod tests { assert_eq!(installation_diff.added_installations.len(), 1); assert!(installation_diff .added_installations - .contains(&client_3_installation_key),); + .contains(&client_3_installation_key.to_vec()),); assert_eq!(installation_diff.removed_installations.len(), 1); assert!(installation_diff .removed_installations - .contains(&client_2_installation_key)); + .contains(&client_2_installation_key.to_vec())); } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] @@ -817,10 +915,10 @@ pub(crate) mod tests { let client = ClientBuilder::new_test_client(&recovery_wallet).await; let mut add_wallet_signature_request = client - .associate_wallet(recovery_wallet.get_address(), second_wallet.get_address()) + .associate_wallet(second_wallet.get_address()) + .await .unwrap(); - add_wallet_signature(&mut add_wallet_signature_request, &recovery_wallet).await; add_wallet_signature(&mut add_wallet_signature_request, &second_wallet).await; client @@ -828,8 +926,7 @@ pub(crate) mod tests { .await .unwrap(); - let association_state_after_add = - get_association_state(&client, client.inbox_id().to_string()).await; + let association_state_after_add = get_association_state(&client, client.inbox_id()).await; assert_eq!(association_state_after_add.account_addresses().len(), 2); // Make sure the inbox ID is correctly registered @@ -855,7 +952,7 @@ pub(crate) mod tests { // Make sure that the association state has removed the second wallet let association_state_after_revoke = - get_association_state(&client, client.inbox_id().to_string()).await; + get_association_state(&client, client.inbox_id()).await; assert_eq!(association_state_after_revoke.account_addresses().len(), 1); // Make sure the inbox ID is correctly unregistered @@ -874,14 +971,13 @@ pub(crate) mod tests { let client1: FullXmtpClient = ClientBuilder::new_test_client(&wallet).await; let client2: FullXmtpClient = ClientBuilder::new_test_client(&wallet).await; - let association_state = - get_association_state(&client1, client1.inbox_id().to_string()).await; + let association_state = get_association_state(&client1, client1.inbox_id()).await; // Ensure there are two installations on the inbox assert_eq!(association_state.installation_ids().len(), 2); // Now revoke the second client let mut revoke_installation_request = client1 - .revoke_installations(vec![client2.installation_public_key()]) + .revoke_installations(vec![client2.installation_public_key().to_vec()]) .await .unwrap(); add_wallet_signature(&mut revoke_installation_request, &wallet).await; @@ -891,8 +987,7 @@ pub(crate) mod tests { .unwrap(); // Make sure there is only one installation on the inbox - let association_state = - get_association_state(&client1, client1.inbox_id().to_string()).await; + let association_state = get_association_state(&client1, client1.inbox_id()).await; assert_eq!(association_state.installation_ids().len(), 1); } } diff --git a/xmtp_mls/src/intents.rs b/xmtp_mls/src/intents.rs index b7b06ef06..87adbc539 100644 --- a/xmtp_mls/src/intents.rs +++ b/xmtp_mls/src/intents.rs @@ -8,14 +8,8 @@ //! Intents are written to local storage (SQLite), before being published to the delivery service via gRPC. An //! intent is fully resolved (success or failure) once it -use crate::{ - client::XmtpMlsLocalContext, - retry::RetryableError, - storage::{refresh_state::EntityKind, EncryptedMessageStore}, - xmtp_openmls_provider::XmtpOpenMlsProvider, -}; -use std::{future::Future, sync::Arc}; use thiserror::Error; +use xmtp_common::RetryableError; #[derive(Debug, Error)] pub enum ProcessIntentError { @@ -36,61 +30,3 @@ impl RetryableError for ProcessIntentError { } } } - -/// Intents holding the context of this Client -pub struct Intents { - pub(crate) context: Arc, -} - -impl Intents { - pub(crate) fn store(&self) -> &EncryptedMessageStore { - self.context.store() - } - - /// Download all unread welcome messages and convert to groups. - /// In a database transaction, increment the cursor for a given entity and - /// apply the update after the provided `ProcessingFn` has completed successfully. - pub(crate) async fn process_for_id( - &self, - entity_id: &Vec, - entity_kind: EntityKind, - cursor: u64, - process_envelope: ProcessingFn, - ) -> Result - where - Fut: Future>, - ProcessingFn: FnOnce(XmtpOpenMlsProvider) -> Fut, - ErrorType: From - + From - + From - + std::fmt::Display, - { - self.store() - .transaction_async(|provider| async move { - let is_updated = - provider - .conn_ref() - .update_cursor(entity_id, entity_kind, cursor as i64)?; - if !is_updated { - return Err(ProcessIntentError::AlreadyProcessed(cursor).into()); - } - process_envelope(provider).await - }) - .await - .inspect(|_| { - tracing::info!( - "Transaction completed successfully: process for entity [{:?}] envelope cursor[{}]", - entity_id, - cursor - ); - }) - .inspect_err(|err| { - tracing::info!( - "Transaction failed: process for entity [{:?}] envelope cursor[{}] error:[{}]", - entity_id, - cursor, - err - ); - }) - } -} diff --git a/xmtp_mls/src/lib.rs b/xmtp_mls/src/lib.rs index 86095c223..d287fd676 100644 --- a/xmtp_mls/src/lib.rs +++ b/xmtp_mls/src/lib.rs @@ -4,15 +4,13 @@ pub mod api; pub mod builder; pub mod client; -pub mod codecs; pub mod configuration; pub mod groups; mod hpke; pub mod identity; -mod identity_updates; +pub mod identity_updates; mod intents; mod mutex_registry; -pub mod retry; pub mod storage; mod stream_handles; pub mod subscriptions; @@ -28,12 +26,6 @@ pub use xmtp_openmls_provider::XmtpOpenMlsProvider; pub use xmtp_id::InboxOwner; pub use xmtp_proto::api_client::trait_impls::*; -/// Global Marker trait for WebAssembly -#[cfg(target_arch = "wasm32")] -pub trait Wasm {} -#[cfg(target_arch = "wasm32")] -impl Wasm for T {} - /// Inserts a model to the underlying data store, erroring if it already exists pub trait Store { fn store(&self, into: &StorageConnection) -> Result<(), StorageError>; @@ -76,108 +68,12 @@ pub use stream_handles::{ spawn, AbortHandle, GenericStreamHandle, StreamHandle, StreamHandleError, }; -#[cfg(target_arch = "wasm32")] -#[doc(hidden)] -pub async fn sleep(duration: core::time::Duration) { - gloo_timers::future::TimeoutFuture::new(duration.as_millis() as u32).await; -} - -#[cfg(not(target_arch = "wasm32"))] -#[doc(hidden)] -pub async fn sleep(duration: core::time::Duration) { - tokio::time::sleep(duration).await -} - -/// Turn the `Result` into an `Option`, logging the error with `tracing::error` and -/// returning `None` if the value matches on Result::Err(). -/// Optionally pass a message as the second argument. -#[macro_export] -macro_rules! optify { - ( $e: expr ) => { - match $e { - Ok(v) => Some(v), - Err(e) => { - tracing::error!("{}", e); - None - } - } - }; - ( $e: expr, $msg: tt ) => { - match $e { - Ok(v) => Some(v), - Err(e) => { - tracing::error!("{}: {:?}", $msg, e); - None - } - } - }; -} - #[cfg(test)] pub(crate) mod tests { // Execute once before any tests are run #[cfg_attr(not(target_arch = "wasm32"), ctor::ctor)] #[cfg(not(target_arch = "wasm32"))] fn _setup() { - use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; - - let filter = EnvFilter::builder() - .with_default_directive(tracing::metadata::LevelFilter::INFO.into()) - .from_env_lossy(); - - tracing_subscriber::registry() - .with(fmt::layer().pretty()) - .with(filter) - .init(); - } - - /// wrapper over assert!(matches!()) for Errors - /// assert_err!(fun(), StorageError::Explosion) - /// - /// or the message variant, - /// assert_err!(fun(), StorageError::Explosion, "the storage did not explode"); - #[macro_export] - macro_rules! assert_err { - ( $x:expr , $y:pat $(,)? ) => { - assert!(matches!($x, Err($y))) - }; - - ( $x:expr, $y:pat $(,)?, $($msg:tt)+) => {{ - assert!(matches!($x, Err($y)), $($msg)+) - }} - } - - /// wrapper over assert! macros for Ok's - /// - /// Make sure something is Ok(_) without caring about return value. - /// assert_ok!(fun()); - /// - /// Against an expected value, e.g Ok(true) - /// assert_ok!(fun(), true); - /// - /// or the message variant, - /// assert_ok!(fun(), Ok(_), "the storage is not ok"); - #[macro_export] - macro_rules! assert_ok { - - ( $e:expr ) => { - assert_ok!($e,) - }; - - ( $e:expr, ) => {{ - use std::result::Result::*; - match $e { - Ok(v) => v, - Err(e) => panic!("assertion failed: Err({:?})", e), - } - }}; - - ( $x:expr , $y:expr $(,)? ) => { - assert_eq!($x, Ok($y.into())); - }; - - ( $x:expr, $y:expr $(,)?, $($msg:tt)+) => {{ - assert_eq!($x, Ok($y.into()), $($msg)+); - }} + xmtp_common::logger() } } diff --git a/xmtp_mls/src/storage/encrypted_store/consent_record.rs b/xmtp_mls/src/storage/encrypted_store/consent_record.rs index f0c7b9498..b39b17eda 100644 --- a/xmtp_mls/src/storage/encrypted_store/consent_record.rs +++ b/xmtp_mls/src/storage/encrypted_store/consent_record.rs @@ -15,10 +15,6 @@ use diesel::{ upsert::excluded, }; use serde::{Deserialize, Serialize}; -use xmtp_id::associations::DeserializationError; -use xmtp_proto::xmtp::mls::message_contents::{ - ConsentEntityType, ConsentState as ConsentStateProto, ConsentUpdate as ConsentUpdateProto, -}; /// StoredConsentRecord holds a serialized ConsentRecord #[derive(Insertable, Queryable, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] @@ -182,29 +178,6 @@ where } } -impl TryFrom for StoredConsentRecord { - type Error = DeserializationError; - - fn try_from(update: ConsentUpdateProto) -> Result { - Ok(Self { - entity_type: match update.entity_type() { - ConsentEntityType::Address => ConsentType::Address, - ConsentEntityType::ConversationId => ConsentType::ConversationId, - ConsentEntityType::InboxId => ConsentType::InboxId, - ConsentEntityType::Unspecified => { - return Err(DeserializationError::Unspecified("entity_type")) - } - }, - state: match update.state() { - ConsentStateProto::Unspecified => ConsentState::Unknown, - ConsentStateProto::Allowed => ConsentState::Allowed, - ConsentStateProto::Denied => ConsentState::Denied, - }, - entity: update.entity, - }) - } -} - #[cfg(test)] mod tests { use crate::storage::encrypted_store::tests::with_connection; diff --git a/xmtp_mls/src/storage/encrypted_store/db_connection.rs b/xmtp_mls/src/storage/encrypted_store/db_connection.rs index e2ef9a970..d0ef3942a 100644 --- a/xmtp_mls/src/storage/encrypted_store/db_connection.rs +++ b/xmtp_mls/src/storage/encrypted_store/db_connection.rs @@ -51,6 +51,8 @@ where self.inner.lock() } + /// Internal-only API to get the underlying `diesel::Connection` reference + /// without a scope pub(super) fn inner_ref(&self) -> Arc> { self.inner.clone() } diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index f5241f53e..010eab713 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -32,8 +32,6 @@ pub struct StoredGroup { pub membership_state: GroupMembershipState, /// Track when the latest, most recent installations were checked pub installations_last_checked: i64, - /// Enum, [`ConversationType`] signifies the group conversation type which extends to who can access it. - pub conversation_type: ConversationType, /// The inbox_id of who added the user to a group. pub added_by_inbox_id: String, /// The sequence id of the welcome message @@ -42,6 +40,8 @@ pub struct StoredGroup { pub dm_inbox_id: Option, /// The last time the leaf node encryption key was rotated pub rotated_at_ns: i64, + /// Enum, [`ConversationType`] signifies the group conversation type which extends to who can access it. + pub conversation_type: ConversationType, } impl_fetch!(StoredGroup, groups, Vec); @@ -215,6 +215,8 @@ impl DbConnection { } = args.as_ref(); let mut query = groups_dsl::groups + // Filter out sync groups from the main query + .filter(groups_dsl::conversation_type.ne(ConversationType::Sync)) .order(groups_dsl::created_at_ns.asc()) .into_boxed(); @@ -238,13 +240,7 @@ impl DbConnection { query = query.filter(groups_dsl::conversation_type.eq(conversation_type)); } - // Were sync groups explicitly asked for? Was the include_sync_groups flag set to true? - // Otherwise filter sync groups out by default. - if !matches!(conversation_type, Some(ConversationType::Sync)) && !include_sync_groups { - query = query.filter(groups_dsl::conversation_type.ne(ConversationType::Sync)); - } - - let groups = if let Some(consent_state) = consent_state { + let mut groups = if let Some(consent_state) = consent_state { if *consent_state == ConsentState::Unknown { let query = query .left_join( @@ -278,6 +274,15 @@ impl DbConnection { self.raw_query(|conn| query.load::(conn))? }; + // Were sync groups explicitly asked for? Was the include_sync_groups flag set to true? + // Then query for those separately + if matches!(conversation_type, Some(ConversationType::Sync)) || *include_sync_groups { + let query = + groups_dsl::groups.filter(groups_dsl::conversation_type.eq(ConversationType::Sync)); + let mut sync_groups = self.raw_query(|conn| query.load(conn))?; + groups.append(&mut sync_groups); + } + Ok(groups) } @@ -285,6 +290,14 @@ impl DbConnection { Ok(self.raw_query(|conn| super::schema::consent_records::table.load(conn))?) } + pub fn all_sync_groups(&self) -> Result, StorageError> { + let query = dsl::groups + .order(dsl::created_at_ns.desc()) + .filter(dsl::conversation_type.eq(ConversationType::Sync)); + + Ok(self.raw_query(|conn| query.load(conn))?) + } + pub fn latest_sync_group(&self) -> Result, StorageError> { let query = dsl::groups .order(dsl::created_at_ns.desc()) @@ -375,7 +388,7 @@ impl DbConnection { /// Updates the 'last time checked' we checked for new installations. pub fn update_rotated_at_ns(&self, group_id: Vec) -> Result<(), StorageError> { self.raw_query(|conn| { - let now = crate::utils::time::now_ns(); + let now = xmtp_common::time::now_ns(); diesel::update(dsl::groups.find(&group_id)) .set(dsl::rotated_at_ns.eq(now)) .execute(conn) @@ -403,7 +416,7 @@ impl DbConnection { /// Updates the 'last time checked' we checked for new installations. pub fn update_installations_time_checked(&self, group_id: Vec) -> Result<(), StorageError> { self.raw_query(|conn| { - let now = crate::utils::time::now_ns(); + let now = xmtp_common::time::now_ns(); diesel::update(dsl::groups.find(&group_id)) .set(dsl::installations_last_checked.eq(now)) .execute(conn) @@ -492,6 +505,7 @@ pub enum ConversationType { Dm = 2, Sync = 3, } + impl ToSql for ConversationType where i32: ToSql, @@ -516,6 +530,17 @@ where } } +impl std::fmt::Display for ConversationType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use ConversationType::*; + match self { + Group => write!(f, "group"), + Dm => write!(f, "dm"), + Sync => write!(f, "sync"), + } + } +} + #[cfg(test)] pub(crate) mod tests { #[cfg(target_arch = "wasm32")] @@ -523,18 +548,17 @@ pub(crate) mod tests { use super::*; use crate::{ - assert_ok, storage::{ consent_record::{ConsentType, StoredConsentRecord}, encrypted_store::{schema::groups::dsl::groups, tests::with_connection}, }, - utils::{test::rand_vec, time::now_ns}, Fetch, Store, }; + use xmtp_common::{assert_ok, rand_vec, time::now_ns}; /// Generate a test group pub fn generate_group(state: Option) -> StoredGroup { - let id = rand_vec(); + let id = rand_vec::<24>(); let created_at_ns = now_ns(); let membership_state = state.unwrap_or(GroupMembershipState::Allowed); StoredGroup::new( @@ -561,7 +585,7 @@ pub(crate) mod tests { /// Generate a test dm group pub fn generate_dm(state: Option) -> StoredGroup { - let id = rand_vec(); + let id = rand_vec::<24>(); let created_at_ns = now_ns(); let membership_state = state.unwrap_or(GroupMembershipState::Allowed); let dm_inbox_id = Some("placeholder_inbox_id".to_string()); @@ -749,7 +773,7 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_new_sync_group() { with_connection(|conn| { - let id = rand_vec(); + let id = rand_vec::<24>(); let created_at_ns = now_ns(); let membership_state = GroupMembershipState::Allowed; @@ -761,7 +785,19 @@ pub(crate) mod tests { let found = conn.latest_sync_group().unwrap(); assert!(found.is_some()); - assert_eq!(found.unwrap().conversation_type, ConversationType::Sync) + assert_eq!(found.unwrap().conversation_type, ConversationType::Sync); + + // Load the sync group with a consent filter + let allowed_groups = conn + .find_groups(&GroupQueryArgs { + consent_state: Some(ConsentState::Allowed), + include_sync_groups: true, + ..Default::default() + }) + .unwrap(); + + assert_eq!(allowed_groups.len(), 1); + assert_eq!(allowed_groups[0].id, sync_group.id); }) .await } diff --git a/xmtp_mls/src/storage/encrypted_store/group_intent.rs b/xmtp_mls/src/storage/encrypted_store/group_intent.rs index a45a818c3..2cc872a3c 100644 --- a/xmtp_mls/src/storage/encrypted_store/group_intent.rs +++ b/xmtp_mls/src/storage/encrypted_store/group_intent.rs @@ -400,9 +400,9 @@ pub(crate) mod tests { group::{GroupMembershipState, StoredGroup}, tests::with_connection, }, - utils::test::rand_vec, Fetch, Store, }; + use xmtp_common::rand_vec; fn insert_group(conn: &DbConnection, group_id: Vec) { let group = StoredGroup::new( @@ -445,8 +445,8 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_store_and_fetch() { - let group_id = rand_vec(); - let data = rand_vec(); + let group_id = rand_vec::<24>(); + let data = rand_vec::<24>(); let kind = IntentKind::UpdateGroupMembership; let state = IntentState::ToPublish; @@ -479,25 +479,25 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_query() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); let test_intents: Vec = vec![ NewGroupIntent::new_test( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), IntentState::ToPublish, ), NewGroupIntent::new_test( IntentKind::KeyUpdate, group_id.clone(), - rand_vec(), + rand_vec::<24>(), IntentState::Published, ), NewGroupIntent::new_test( IntentKind::KeyUpdate, group_id.clone(), - rand_vec(), + rand_vec::<24>(), IntentState::Committed, ), ]; @@ -559,7 +559,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn find_by_payload_hash() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); @@ -568,7 +568,7 @@ pub(crate) mod tests { NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); @@ -577,8 +577,8 @@ pub(crate) mod tests { let intent = find_first_intent(conn, group_id.clone()); // Set the payload hash - let payload_hash = rand_vec(); - let post_commit_data = rand_vec(); + let payload_hash = rand_vec::<24>(); + let post_commit_data = rand_vec::<24>(); conn.set_group_intent_published( intent.id, payload_hash.clone(), @@ -602,7 +602,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_happy_path_state_transitions() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); @@ -611,7 +611,7 @@ pub(crate) mod tests { NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); @@ -619,8 +619,8 @@ pub(crate) mod tests { let mut intent = find_first_intent(conn, group_id.clone()); // Set to published - let payload_hash = rand_vec(); - let post_commit_data = rand_vec(); + let payload_hash = rand_vec::<24>(); + let post_commit_data = rand_vec::<24>(); conn.set_group_intent_published( intent.id, payload_hash.clone(), @@ -648,7 +648,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_republish_state_transition() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); @@ -657,7 +657,7 @@ pub(crate) mod tests { NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); @@ -665,8 +665,8 @@ pub(crate) mod tests { let mut intent = find_first_intent(conn, group_id.clone()); // Set to published - let payload_hash = rand_vec(); - let post_commit_data = rand_vec(); + let payload_hash = rand_vec::<24>(); + let post_commit_data = rand_vec::<24>(); conn.set_group_intent_published( intent.id, payload_hash.clone(), @@ -693,7 +693,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_invalid_state_transition() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); @@ -702,7 +702,7 @@ pub(crate) mod tests { NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); @@ -729,13 +729,13 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_increment_publish_attempts() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); diff --git a/xmtp_mls/src/storage/encrypted_store/group_message.rs b/xmtp_mls/src/storage/encrypted_store/group_message.rs index 7976a030c..743800d79 100644 --- a/xmtp_mls/src/storage/encrypted_store/group_message.rs +++ b/xmtp_mls/src/storage/encrypted_store/group_message.rs @@ -284,11 +284,11 @@ pub(crate) mod tests { use super::*; use crate::{ - assert_err, assert_ok, storage::encrypted_store::{group::tests::generate_group, tests::with_connection}, - utils::test::{rand_time, rand_vec}, Store, }; + use wasm_bindgen_test::wasm_bindgen_test; + use xmtp_common::{assert_err, assert_ok, rand_time, rand_vec}; fn generate_message( kind: Option, @@ -296,19 +296,18 @@ pub(crate) mod tests { sent_at_ns: Option, ) -> StoredGroupMessage { StoredGroupMessage { - id: rand_vec(), - group_id: group_id.map(<[u8]>::to_vec).unwrap_or(rand_vec()), - decrypted_message_bytes: rand_vec(), + id: rand_vec::<24>(), + group_id: group_id.map(<[u8]>::to_vec).unwrap_or(rand_vec::<24>()), + decrypted_message_bytes: rand_vec::<24>(), sent_at_ns: sent_at_ns.unwrap_or(rand_time()), - sender_installation_id: rand_vec(), + sender_installation_id: rand_vec::<24>(), sender_inbox_id: "0x0".to_string(), kind: kind.unwrap_or(GroupMessageKind::Application), delivery_status: DeliveryStatus::Unpublished, } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_does_not_error_on_empty_messages() { with_connection(|conn| { let id = vec![0x0]; @@ -317,8 +316,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_gets_messages() { with_connection(|conn| { let group = generate_group(None); @@ -334,8 +332,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_cannot_insert_message_without_group() { use diesel::result::{DatabaseErrorKind::ForeignKeyViolation, Error::DatabaseError}; @@ -349,8 +346,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_gets_many_messages() { use crate::storage::encrypted_store::schema::group_messages::dsl; @@ -385,8 +381,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_gets_messages_by_time() { with_connection(|conn| { let group = generate_group(None); @@ -423,8 +418,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_gets_messages_by_kind() { with_connection(|conn| { let group = generate_group(None); @@ -471,8 +465,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_orders_messages_by_sent() { with_connection(|conn| { let group = generate_group(None); diff --git a/xmtp_mls/src/storage/encrypted_store/identity.rs b/xmtp_mls/src/storage/encrypted_store/identity.rs index 4051036c5..f6f973b8f 100644 --- a/xmtp_mls/src/storage/encrypted_store/identity.rs +++ b/xmtp_mls/src/storage/encrypted_store/identity.rs @@ -71,7 +71,8 @@ pub(crate) mod tests { super::{EncryptedMessageStore, StorageOption}, StoredIdentity, }; - use crate::{utils::test::rand_vec, Store}; + use crate::Store; + use xmtp_common::rand_vec; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] @@ -84,12 +85,12 @@ pub(crate) mod tests { .unwrap(); let conn = &store.conn().unwrap(); - StoredIdentity::new("".to_string(), rand_vec(), rand_vec()) + StoredIdentity::new("".to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn) .unwrap(); let duplicate_insertion = - StoredIdentity::new("".to_string(), rand_vec(), rand_vec()).store(conn); + StoredIdentity::new("".to_string(), rand_vec::<24>(), rand_vec::<24>()).store(conn); assert!(duplicate_insertion.is_err()); } } diff --git a/xmtp_mls/src/storage/encrypted_store/identity_update.rs b/xmtp_mls/src/storage/encrypted_store/identity_update.rs index 74eb878d3..1c5c6b564 100644 --- a/xmtp_mls/src/storage/encrypted_store/identity_update.rs +++ b/xmtp_mls/src/storage/encrypted_store/identity_update.rs @@ -134,16 +134,18 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::{ - storage::encrypted_store::tests::with_connection, - utils::test::{rand_time, rand_vec}, - Store, - }; + use crate::{storage::encrypted_store::tests::with_connection, Store}; + use xmtp_common::{rand_time, rand_vec}; use super::*; fn build_update(inbox_id: &str, sequence_id: i64) -> StoredIdentityUpdate { - StoredIdentityUpdate::new(inbox_id.to_string(), sequence_id, rand_time(), rand_vec()) + StoredIdentityUpdate::new( + inbox_id.to_string(), + sequence_id, + rand_time(), + rand_vec::<24>(), + ) } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] diff --git a/xmtp_mls/src/storage/encrypted_store/key_package_history.rs b/xmtp_mls/src/storage/encrypted_store/key_package_history.rs index 7947abb0a..9e43243c0 100644 --- a/xmtp_mls/src/storage/encrypted_store/key_package_history.rs +++ b/xmtp_mls/src/storage/encrypted_store/key_package_history.rs @@ -1,7 +1,8 @@ use diesel::prelude::*; use super::{db_connection::DbConnection, schema::key_package_history, StorageError}; -use crate::{impl_store_or_ignore, utils::time::now_ns, StoreOrIgnore}; +use crate::{impl_store_or_ignore, StoreOrIgnore}; +use xmtp_common::time::now_ns; #[derive(Insertable, Debug, Clone)] #[diesel(table_name = key_package_history)] @@ -78,7 +79,8 @@ impl DbConnection { #[cfg(test)] mod tests { - use crate::{storage::encrypted_store::tests::with_connection, utils::test::rand_vec}; + use crate::storage::encrypted_store::tests::with_connection; + use xmtp_common::rand_vec; #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); @@ -86,7 +88,7 @@ mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_store_key_package_history_entry() { with_connection(|conn| { - let hash_ref = rand_vec(); + let hash_ref = rand_vec::<24>(); let new_entry = conn .store_key_package_history_entry(hash_ref.clone()) .unwrap(); @@ -100,9 +102,9 @@ mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_store_multiple() { with_connection(|conn| { - let hash_ref1 = rand_vec(); - let hash_ref2 = rand_vec(); - let hash_ref3 = rand_vec(); + let hash_ref1 = rand_vec::<24>(); + let hash_ref2 = rand_vec::<24>(); + let hash_ref3 = rand_vec::<24>(); conn.store_key_package_history_entry(hash_ref1.clone()) .unwrap(); diff --git a/xmtp_mls/src/storage/encrypted_store/mod.rs b/xmtp_mls/src/storage/encrypted_store/mod.rs index bc72fa7cc..c7172dd5b 100644 --- a/xmtp_mls/src/storage/encrypted_store/mod.rs +++ b/xmtp_mls/src/storage/encrypted_store/mod.rs @@ -26,6 +26,7 @@ pub mod refresh_state; pub mod schema; #[cfg(not(target_arch = "wasm32"))] mod sqlcipher_connection; +pub mod user_preferences; pub mod wallet_addresses; #[cfg(target_arch = "wasm32")] mod wasm; @@ -189,6 +190,13 @@ pub mod private { Ok::<_, StorageError>(()) } + pub fn mls_provider( + &self, + ) -> Result::Connection>, StorageError> { + let conn = self.conn()?; + Ok(crate::xmtp_openmls_provider::XmtpOpenMlsProviderPrivate::new(conn)) + } + /// Pulls a new connection from the store pub fn conn( &self, @@ -197,6 +205,7 @@ pub mod private { } /// Start a new database transaction with the OpenMLS Provider from XMTP + /// with the provided connection /// # Arguments /// `fun`: Scoped closure providing a MLSProvider to carry out the transaction /// @@ -209,22 +218,25 @@ pub mod private { /// provider.conn().db_operation()?; /// }) /// ``` - pub fn transaction(&self, fun: F) -> Result + pub fn transaction( + &self, + provider: &XmtpOpenMlsProviderPrivate<::Connection>, + fun: F, + ) -> Result where F: FnOnce(&XmtpOpenMlsProviderPrivate<::Connection>) -> Result, E: From + From, { tracing::debug!("Transaction beginning"); - let connection = self.db.conn()?; { + let connection = provider.conn_ref(); let mut connection = connection.inner_mut_ref(); ::TransactionManager::begin_transaction(&mut *connection)?; } - let provider = XmtpOpenMlsProviderPrivate::new(connection); let conn = provider.conn_ref(); - match fun(&provider) { + match fun(provider) { Ok(value) => { conn.raw_query(|conn| { ::TransactionManager::commit_transaction(&mut *conn) @@ -259,27 +271,29 @@ pub mod private { /// provider.conn().db_operation()?; /// }).await /// ``` - pub async fn transaction_async(&self, fun: F) -> Result + pub async fn transaction_async<'a, T, F, E, Fut>( + &self, + provider: &'a XmtpOpenMlsProviderPrivate<::Connection>, + fun: F, + ) -> Result where - F: FnOnce(XmtpOpenMlsProviderPrivate<::Connection>) -> Fut, + F: FnOnce(&'a XmtpOpenMlsProviderPrivate<::Connection>) -> Fut, Fut: futures::Future>, E: From + From, { tracing::debug!("Transaction async beginning"); - let db_connection = self.db.conn()?; { - let mut connection = db_connection.inner_mut_ref(); + let connection = provider.conn_ref(); + let mut connection = connection.inner_mut_ref(); ::TransactionManager::begin_transaction(&mut *connection)?; } - let local_connection = db_connection.inner_ref(); - let provider = XmtpOpenMlsProviderPrivate::new(db_connection); - // the other connection is dropped in the closure // ensuring we have only one strong reference let result = fun(provider).await; + let local_connection = provider.conn_ref().inner_ref(); if Arc::strong_count(&local_connection) > 1 { tracing::warn!( - "More than 1 strong connection references still exist during transaction" + "More than 1 strong connection references still exist during async transaction" ); } @@ -458,6 +472,7 @@ where pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use wasm_bindgen_test::wasm_bindgen_test; use super::*; use crate::{ @@ -465,9 +480,9 @@ pub(crate) mod tests { group::{GroupMembershipState, StoredGroup}, identity::StoredIdentity, }, - utils::test::{rand_vec, tmp_path}, - Fetch, Store, StreamHandle as _, + Fetch, Store, StreamHandle as _, XmtpOpenMlsProvider, }; + use xmtp_common::{rand_vec, tmp_path}; /// Test harness that loads an Ephemeral store. pub async fn with_connection(fun: F) -> R @@ -496,8 +511,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn ephemeral_store() { let store = EncryptedMessageStore::new( StorageOption::Ephemeral, @@ -508,7 +522,7 @@ pub(crate) mod tests { let conn = &store.conn().unwrap(); let inbox_id = "inbox_id"; - StoredIdentity::new(inbox_id.to_string(), rand_vec(), rand_vec()) + StoredIdentity::new(inbox_id.to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn) .unwrap(); @@ -516,8 +530,7 @@ pub(crate) mod tests { assert_eq!(fetched_identity.inbox_id, inbox_id); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn persistent_store() { let db_path = tmp_path(); { @@ -530,7 +543,7 @@ pub(crate) mod tests { let conn = &store.conn().unwrap(); let inbox_id = "inbox_id"; - StoredIdentity::new(inbox_id.to_string(), rand_vec(), rand_vec()) + StoredIdentity::new(inbox_id.to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn) .unwrap(); @@ -539,10 +552,8 @@ pub(crate) mod tests { } EncryptedMessageStore::remove_db_files(db_path) } - - // #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn releases_db_lock() { let db_path = tmp_path(); { @@ -555,7 +566,7 @@ pub(crate) mod tests { let conn = &store.conn().unwrap(); let inbox_id = "inbox_id"; - StoredIdentity::new(inbox_id.to_string(), rand_vec(), rand_vec()) + StoredIdentity::new(inbox_id.to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn) .unwrap(); @@ -574,8 +585,7 @@ pub(crate) mod tests { EncryptedMessageStore::remove_db_files(db_path) } - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - #[cfg(not(target_arch = "wasm32"))] + #[tokio::test] async fn mismatched_encryption_key() { let mut enc_key = [1u8; 32]; @@ -587,9 +597,13 @@ pub(crate) mod tests { .await .unwrap(); - StoredIdentity::new("dummy_address".to_string(), rand_vec(), rand_vec()) - .store(&store.conn().unwrap()) - .unwrap(); + StoredIdentity::new( + "dummy_address".to_string(), + rand_vec::<24>(), + rand_vec::<24>(), + ) + .store(&store.conn().unwrap()) + .unwrap(); } // Drop it enc_key[3] = 145; // Alter the enc_key @@ -604,8 +618,7 @@ pub(crate) mod tests { EncryptedMessageStore::remove_db_files(db_path) } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn encrypted_db_with_multiple_connections() { let db_path = tmp_path(); { @@ -618,7 +631,7 @@ pub(crate) mod tests { let conn1 = &store.conn().unwrap(); let inbox_id = "inbox_id"; - StoredIdentity::new(inbox_id.to_string(), rand_vec(), rand_vec()) + StoredIdentity::new(inbox_id.to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn1) .unwrap(); @@ -650,13 +663,13 @@ pub(crate) mod tests { .unwrap(); let barrier = Arc::new(Barrier::new(2)); - + let provider = XmtpOpenMlsProvider::new(store.conn().unwrap()); let store_pointer = store.clone(); let barrier_pointer = barrier.clone(); let handle = std::thread::spawn(move || { - store_pointer.transaction(|provider| { + store_pointer.transaction(&provider, |provider| { let conn1 = provider.conn_ref(); - StoredIdentity::new("correct".to_string(), rand_vec(), rand_vec()) + StoredIdentity::new("correct".to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn1) .unwrap(); // wait for second transaction to start @@ -668,20 +681,22 @@ pub(crate) mod tests { }); let store_pointer = store.clone(); + let provider = XmtpOpenMlsProvider::new(store.conn().unwrap()); let handle2 = std::thread::spawn(move || { barrier.wait(); - let result = store_pointer.transaction(|provider| -> Result<(), anyhow::Error> { - let connection = provider.conn_ref(); - let group = StoredGroup::new( - b"should not exist".to_vec(), - 0, - GroupMembershipState::Allowed, - "goodbye".to_string(), - None, - ); - group.store(connection)?; - Ok(()) - }); + let result = + store_pointer.transaction(&provider, |provider| -> Result<(), anyhow::Error> { + let connection = provider.conn_ref(); + let group = StoredGroup::new( + b"should not exist".to_vec(), + 0, + GroupMembershipState::Allowed, + "goodbye".to_string(), + None, + ); + group.store(connection)?; + Ok(()) + }); barrier.wait(); result }); @@ -717,12 +732,12 @@ pub(crate) mod tests { .unwrap(); let store_pointer = store.clone(); - + let provider = XmtpOpenMlsProvider::new(store_pointer.conn().unwrap()); let handle = crate::spawn(None, async move { store_pointer - .transaction_async(|provider| async move { + .transaction_async(&provider, |provider| async move { let conn1 = provider.conn_ref(); - StoredIdentity::new("crab".to_string(), rand_vec(), rand_vec()) + StoredIdentity::new("crab".to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn1) .unwrap(); diff --git a/xmtp_mls/src/storage/encrypted_store/native.rs b/xmtp_mls/src/storage/encrypted_store/native.rs index f951116b3..b254f820e 100644 --- a/xmtp_mls/src/storage/encrypted_store/native.rs +++ b/xmtp_mls/src/storage/encrypted_store/native.rs @@ -79,6 +79,14 @@ impl StorageOption { Ephemeral => SqliteConnection::establish(":memory:"), } } + + pub(super) fn path(&self) -> Option<&String> { + use StorageOption::*; + match self { + Persistent(path) => Some(path), + _ => None, + } + } } #[derive(Clone, Debug)] diff --git a/xmtp_mls/src/storage/encrypted_store/refresh_state.rs b/xmtp_mls/src/storage/encrypted_store/refresh_state.rs index 19b6b28cc..f5cf0ba33 100644 --- a/xmtp_mls/src/storage/encrypted_store/refresh_state.rs +++ b/xmtp_mls/src/storage/encrypted_store/refresh_state.rs @@ -90,13 +90,13 @@ impl DbConnection { } } - pub fn update_cursor( + pub fn update_cursor>( &self, - entity_id: &Vec, + entity_id: Id, entity_kind: EntityKind, cursor: i64, ) -> Result { - let state: Option = self.get_refresh_state(entity_id, entity_kind)?; + let state: Option = self.get_refresh_state(&entity_id, entity_kind)?; match state { Some(state) => { use super::schema::refresh_state::dsl; @@ -110,7 +110,7 @@ impl DbConnection { } None => Err(StorageError::NotFound(format!( "state for entity ID {} with kind {:?}", - hex::encode(entity_id), + hex::encode(entity_id.as_ref()), entity_kind ))), } diff --git a/xmtp_mls/src/storage/encrypted_store/schema.rs b/xmtp_mls/src/storage/encrypted_store/schema.rs index 5a6e3385b..c82818161 100644 --- a/xmtp_mls/src/storage/encrypted_store/schema.rs +++ b/xmtp_mls/src/storage/encrypted_store/schema.rs @@ -50,11 +50,11 @@ diesel::table! { created_at_ns -> BigInt, membership_state -> Integer, installations_last_checked -> BigInt, - conversation_type -> Integer, added_by_inbox_id -> Text, welcome_id -> Nullable, dm_inbox_id -> Nullable, rotated_at_ns -> BigInt, + conversation_type -> Integer, } } @@ -107,6 +107,13 @@ diesel::table! { } } +diesel::table! { + user_preferences (id) { + id -> Integer, + hmac_key -> Nullable, + } +} + diesel::table! { wallet_addresses (wallet_address) { inbox_id -> Text, @@ -129,5 +136,6 @@ diesel::allow_tables_to_appear_in_same_query!( openmls_key_store, openmls_key_value, refresh_state, + user_preferences, wallet_addresses, ); diff --git a/xmtp_mls/src/storage/encrypted_store/sqlcipher_connection.rs b/xmtp_mls/src/storage/encrypted_store/sqlcipher_connection.rs index 19b0e6145..fe9350b26 100644 --- a/xmtp_mls/src/storage/encrypted_store/sqlcipher_connection.rs +++ b/xmtp_mls/src/storage/encrypted_store/sqlcipher_connection.rs @@ -46,6 +46,8 @@ impl EncryptedConnection { /// Creates a file for the salt and stores it pub fn new(key: EncryptionKey, opts: &StorageOption) -> Result { use super::StorageOption::*; + Self::check_for_sqlcipher(opts)?; + let salt = match opts { Ephemeral => None, Persistent(ref db_path) => { @@ -56,6 +58,13 @@ impl EncryptedConnection { match (salt_path.try_exists()?, db_pathbuf.try_exists()?) { // db and salt exist (true, true) => { + tracing::debug!( + salt = %salt_path.display(), + db = %db_pathbuf.display(), + "salt and database exist, db=[{}], salt=[{}]", + db_pathbuf.display(), + salt_path.display(), + ); let file = File::open(salt_path)?; salt = ::from_hex( file.bytes().take(32).collect::, _>>()?, @@ -63,18 +72,31 @@ impl EncryptedConnection { } // the db exists and needs to be migrated (false, true) => { - tracing::debug!("migrating sqlcipher db to plaintext header."); + tracing::debug!( + "migrating sqlcipher db=[{}] to plaintext header with salt=[{}]", + db_pathbuf.display(), + salt_path.display() + ); Self::migrate(db_path, key, &mut salt)?; } // the db doesn't exist yet and needs to be created (false, false) => { - tracing::debug!("creating new sqlcipher db"); + tracing::debug!( + "creating new sqlcipher db=[{}] with salt=[{}]", + db_pathbuf.display(), + salt_path.display() + ); Self::create(db_path, key, &mut salt)?; } // the db doesn't exist but the salt does // This generally doesn't make sense & shouldn't happen. // Create a new database and delete the salt file. (true, false) => { + tracing::debug!( + "database [{}] does not exist, but the salt [{}] does, re-creating", + db_pathbuf.display(), + salt_path.display(), + ); std::fs::remove_file(salt_path)?; Self::create(db_path, key, &mut salt)?; } @@ -199,6 +221,19 @@ impl EncryptedConnection { ) } } + + fn check_for_sqlcipher(opts: &StorageOption) -> Result<(), StorageError> { + if let Some(path) = opts.path() { + let exists = std::path::Path::new(path).exists(); + tracing::debug!("db @ [{}] exists? [{}]", path, exists); + } + let conn = &mut opts.conn()?; + let cipher_version = sql_query("PRAGMA cipher_version").load::(conn)?; + if cipher_version.is_empty() { + return Err(StorageError::SqlCipherNotLoaded); + } + Ok(()) + } } impl super::native::ValidatedConnection for EncryptedConnection { @@ -269,9 +304,10 @@ fn pragma_plaintext_header() -> impl Display { #[cfg(test)] mod tests { - use crate::{storage::EncryptedMessageStore, utils::test::tmp_path}; + use crate::storage::EncryptedMessageStore; use diesel_migrations::MigrationHarness; use std::fs::File; + use xmtp_common::tmp_path; use super::*; const SQLITE3_PLAINTEXT_HEADER: &str = "SQLite format 3\0"; diff --git a/xmtp_mls/src/storage/encrypted_store/user_preferences.rs b/xmtp_mls/src/storage/encrypted_store/user_preferences.rs new file mode 100644 index 000000000..98170d52f --- /dev/null +++ b/xmtp_mls/src/storage/encrypted_store/user_preferences.rs @@ -0,0 +1,122 @@ +use crate::{ + groups::device_sync::preference_sync::UserPreferenceUpdate, storage::StorageError, + subscriptions::LocalEvents, Store, +}; + +use super::{ + schema::user_preferences::{self, dsl}, + DbConnection, +}; +use diesel::prelude::*; +use rand::{rngs::OsRng, RngCore}; +use tokio::sync::broadcast::Sender; + +#[derive(Identifiable, Queryable, AsChangeset, Debug, Clone, PartialEq, Eq, Default)] +#[diesel(table_name = user_preferences)] +#[diesel(primary_key(id))] +pub struct StoredUserPreferences { + /// Primary key - latest key is the "current" preference + pub id: i32, + /// Randomly generated hmac key root + pub hmac_key: Option>, +} + +#[derive(Insertable)] +#[diesel(table_name = user_preferences)] +pub struct NewStoredUserPreferences<'a> { + hmac_key: Option<&'a Vec>, +} + +impl<'a> From<&'a StoredUserPreferences> for NewStoredUserPreferences<'a> { + fn from(value: &'a StoredUserPreferences) -> Self { + Self { + hmac_key: value.hmac_key.as_ref(), + } + } +} + +impl Store for StoredUserPreferences { + fn store(&self, conn: &DbConnection) -> Result<(), StorageError> { + conn.raw_query(|conn| { + diesel::update(dsl::user_preferences) + .set(self) + .execute(conn) + })?; + + Ok(()) + } +} + +impl StoredUserPreferences { + pub fn load(conn: &DbConnection) -> Result { + let query = dsl::user_preferences.order(dsl::id.desc()).limit(1); + let mut result = conn.raw_query(|conn| query.load::(conn))?; + + Ok(result.pop().unwrap_or_default()) + } + + pub fn new_hmac_key( + conn: &DbConnection, + local_events: &Sender>, + ) -> Result, StorageError> { + let mut preferences = Self::load(conn)?; + + let mut hmac_key = vec![0; 32]; + OsRng.fill_bytes(&mut hmac_key); + preferences.hmac_key = Some(hmac_key.clone()); + + // Sync the new key to other devices + let _ = local_events.send(LocalEvents::OutgoingPreferenceUpdates(vec![ + UserPreferenceUpdate::HmacKeyUpdate { + key: hmac_key.clone(), + }, + ])); + + let to_insert: NewStoredUserPreferences = (&preferences).into(); + conn.raw_query(|conn| { + diesel::insert_into(dsl::user_preferences) + .values(to_insert) + .execute(conn) + })?; + + Ok(hmac_key) + } +} + +#[cfg(test)] +mod tests { + use xmtp_cryptography::utils::generate_local_wallet; + + use crate::builder::ClientBuilder; + #[cfg(target_arch = "wasm32")] + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + + use super::*; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + async fn test_insert_and_update_preferences() { + let wallet = generate_local_wallet(); + let client = ClientBuilder::new_test_client(&wallet).await; + + let conn = client.store().conn().unwrap(); + + // loads and stores a default + let pref = StoredUserPreferences::load(&conn).unwrap(); + // by default, there is no key + assert!(pref.hmac_key.is_none()); + + // set an hmac key + let hmac_key = StoredUserPreferences::new_hmac_key(&conn, &client.local_events).unwrap(); + let pref = StoredUserPreferences::load(&conn).unwrap(); + // Make sure it saved + assert_eq!(hmac_key, pref.hmac_key.unwrap()); + + // check that there are two preferences stored + let query = dsl::user_preferences.order(dsl::id.desc()); + let result = conn + .raw_query(|conn| query.load::(conn)) + .unwrap(); + assert_eq!(result.len(), 1); + } +} diff --git a/xmtp_mls/src/storage/encrypted_store/wasm.rs b/xmtp_mls/src/storage/encrypted_store/wasm.rs index 2d551c8a9..7cbc05fc0 100644 --- a/xmtp_mls/src/storage/encrypted_store/wasm.rs +++ b/xmtp_mls/src/storage/encrypted_store/wasm.rs @@ -50,13 +50,11 @@ impl XmtpDb for WasmDb { Ok(()) } - #[allow(unreachable_code)] fn release_connection(&self) -> Result<(), StorageError> { - unimplemented!(); + Ok(()) } - #[allow(unreachable_code)] fn reconnect(&self) -> Result<(), StorageError> { - unimplemented!(); + Ok(()) } } diff --git a/xmtp_mls/src/storage/errors.rs b/xmtp_mls/src/storage/errors.rs index f109b0248..de25850ab 100644 --- a/xmtp_mls/src/storage/errors.rs +++ b/xmtp_mls/src/storage/errors.rs @@ -3,9 +3,11 @@ use std::sync::PoisonError; use diesel::result::DatabaseErrorKind; use thiserror::Error; -use crate::{groups::intents::IntentError, retry::RetryableError, retryable}; - use super::sql_key_store; +use crate::groups::intents::IntentError; +use xmtp_common::{retryable, RetryableError}; + +pub struct Mls; #[derive(Debug, Error)] pub enum StorageError { @@ -66,7 +68,7 @@ impl From> for StorageError { } } -impl RetryableError for diesel::result::Error { +impl RetryableError for diesel::result::Error { fn is_retryable(&self) -> bool { use diesel::result::Error::*; use DatabaseErrorKind::*; @@ -101,7 +103,7 @@ impl RetryableError for StorageError { } // OpenMLS KeyStore errors -impl RetryableError for openmls::group::AddMembersError { +impl RetryableError for openmls::group::AddMembersError { fn is_retryable(&self) -> bool { match self { Self::CreateCommitError(commit) => retryable!(commit), @@ -112,7 +114,7 @@ impl RetryableError for openmls::group::AddMembersError { +impl RetryableError for openmls::group::CreateCommitError { fn is_retryable(&self) -> bool { match self { Self::KeyStoreError(storage) => retryable!(storage), @@ -122,7 +124,9 @@ impl RetryableError for openmls::group::CreateCommitError { +impl RetryableError + for openmls::treesync::LeafNodeUpdateError +{ fn is_retryable(&self) -> bool { match self { Self::Storage(storage) => retryable!(storage), @@ -131,13 +135,13 @@ impl RetryableError for openmls::treesync::LeafNodeUpdateError for openmls::key_packages::errors::KeyPackageNewError { fn is_retryable(&self) -> bool { matches!(self, Self::StorageError) } } -impl RetryableError for openmls::group::RemoveMembersError { +impl RetryableError for openmls::group::RemoveMembersError { fn is_retryable(&self) -> bool { match self { Self::CreateCommitError(commit) => retryable!(commit), @@ -148,7 +152,7 @@ impl RetryableError for openmls::group::RemoveMembersError { +impl RetryableError for openmls::group::NewGroupError { fn is_retryable(&self) -> bool { match self { Self::StorageError(storage) => retryable!(storage), @@ -157,7 +161,7 @@ impl RetryableError for openmls::group::NewGroupError for openmls::group::UpdateGroupMembershipError { fn is_retryable(&self) -> bool { @@ -170,13 +174,13 @@ impl RetryableError } } -impl RetryableError for openmls::prelude::MlsGroupStateError { +impl RetryableError for openmls::prelude::MlsGroupStateError { fn is_retryable(&self) -> bool { false } } -impl RetryableError +impl RetryableError for openmls::prelude::CreateGroupContextExtProposalError { fn is_retryable(&self) -> bool { @@ -188,7 +192,7 @@ impl RetryableError } } -impl RetryableError for openmls::group::SelfUpdateError { +impl RetryableError for openmls::group::SelfUpdateError { fn is_retryable(&self) -> bool { match self { Self::CreateCommitError(commit) => retryable!(commit), @@ -199,7 +203,7 @@ impl RetryableError for openmls::group::SelfUpdateError for openmls::prelude::CreationFromExternalError { fn is_retryable(&self) -> bool { @@ -210,7 +214,7 @@ impl RetryableError } } -impl RetryableError for openmls::prelude::WelcomeError { +impl RetryableError for openmls::prelude::WelcomeError { fn is_retryable(&self) -> bool { match self { Self::PublicGroupError(creation_err) => retryable!(creation_err), @@ -220,7 +224,7 @@ impl RetryableError for openmls::prelude::WelcomeError { +impl RetryableError for openmls::group::MergeCommitError { fn is_retryable(&self) -> bool { match self { Self::StorageError(storage) => retryable!(storage), @@ -229,7 +233,9 @@ impl RetryableError for openmls::group::MergeCommitError { +impl RetryableError + for openmls::group::MergePendingCommitError +{ fn is_retryable(&self) -> bool { match self { Self::MlsGroupStateError(err) => retryable!(err), @@ -238,7 +244,7 @@ impl RetryableError for openmls::group::MergePendingCommitError for openmls::prelude::ProcessMessageError { fn is_retryable(&self) -> bool { match self { Self::GroupStateError(err) => retryable!(err), diff --git a/xmtp_mls/src/storage/mod.rs b/xmtp_mls/src/storage/mod.rs index c1ecc7ed6..d3c525897 100644 --- a/xmtp_mls/src/storage/mod.rs +++ b/xmtp_mls/src/storage/mod.rs @@ -13,3 +13,95 @@ pub async fn init_sqlite() { } #[cfg(not(target_arch = "wasm32"))] pub async fn init_sqlite() {} + +#[cfg(any(test, feature = "test-utils"))] +pub mod test_util { + #![allow(clippy::unwrap_used)] + use super::*; + use diesel::{connection::LoadConnection, deserialize::FromSqlRow, sql_query, RunQueryDsl}; + impl DbConnection { + /// Create a new table and register triggers for tracking column updates + pub fn register_triggers(&self) { + tracing::info!("Registering triggers"); + let queries = vec![ + r#" + CREATE TABLE test_metadata ( + intents_created INT DEFAULT 0, + intents_published INT DEFAULT 0, + intents_deleted INT DEFAULT 0, + rowid integer PRIMARY KEY CHECK (rowid = 1) -- There can only be one meta + ); + "#, + r#"CREATE TRIGGER intents_created_tracking AFTER INSERT on group_intents + BEGIN + UPDATE test_metadata SET intents_created = intents_created + 1; + END;"#, + r#"CREATE TRIGGER intents_published_tracking AFTER UPDATE OF state ON group_intents + FOR EACH ROW + WHEN NEW.state = 2 AND OLD.state !=2 + BEGIN + UPDATE test_metadata SET intents_published = intents_published + 1; + END;"#, + r#"CREATE TRIGGER intents_deleted_tracking AFTER DELETE ON group_intents + FOR EACH ROW + BEGIN + UPDATE test_metadata SET intents_deleted = intents_deleted + 1; + END;"#, + r#"INSERT INTO test_metadata ( + intents_created, + intents_deleted, + intents_published + ) VALUES (0, 0,0);"#, + ]; + + for query in queries { + let query = diesel::sql_query(query); + let _ = self.raw_query(|conn| query.execute(conn)).unwrap(); + } + } + + pub fn intents_published(&self) -> i32 { + self.raw_query(|conn| { + let mut row = conn + .load(sql_query( + "SELECT intents_published FROM test_metadata WHERE rowid = 1", + )) + .unwrap(); + let row = row.next().unwrap().unwrap(); + Ok::<_, StorageError>( + >::build_from_row(&row) + .unwrap(), + ) + }) + .unwrap() + } + + pub fn intents_deleted(&self) -> i32 { + self.raw_query(|conn| { + let mut row = conn + .load(sql_query("SELECT intents_deleted FROM test_metadata")) + .unwrap(); + let row = row.next().unwrap().unwrap(); + Ok::<_, StorageError>( + >::build_from_row(&row) + .unwrap(), + ) + }) + .unwrap() + } + + pub fn intents_created(&self) -> i32 { + self.raw_query(|conn| { + let mut row = conn + .load(sql_query("SELECT intents_created FROM test_metadata")) + .unwrap(); + let row = row.next().unwrap().unwrap(); + Ok::<_, StorageError>( + >::build_from_row(&row) + .unwrap(), + ) + }) + .unwrap() + } + } +} diff --git a/xmtp_mls/src/storage/sql_key_store.rs b/xmtp_mls/src/storage/sql_key_store.rs index ae9d2d797..1ad7aa674 100644 --- a/xmtp_mls/src/storage/sql_key_store.rs +++ b/xmtp_mls/src/storage/sql_key_store.rs @@ -1,4 +1,4 @@ -use crate::{retry::RetryableError, retryable}; +use xmtp_common::{retryable, RetryableError}; use super::encrypted_store::db_connection::DbConnectionPrivate; use bincode; @@ -1041,9 +1041,9 @@ pub(crate) mod tests { use crate::{ configuration::CIPHERSUITE, storage::{sql_key_store::SqlKeyStoreError, EncryptedMessageStore, StorageOption}, - utils::test::tmp_path, xmtp_openmls_provider::XmtpOpenMlsProvider, }; + use xmtp_common::tmp_path; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] diff --git a/xmtp_mls/src/stream_handles.rs b/xmtp_mls/src/stream_handles.rs index 869b3dbc7..a42af78e0 100644 --- a/xmtp_mls/src/stream_handles.rs +++ b/xmtp_mls/src/stream_handles.rs @@ -1,6 +1,6 @@ //! Consistent Stream behavior between WebAssembly and Native utilizing `tokio::task::spawn` in native and //! `wasm_bindgen_futures::spawn` for web. -use futures::{Future, FutureExt}; +use futures::FutureExt; #[cfg(target_arch = "wasm32")] pub type GenericStreamHandle = dyn StreamHandle; @@ -72,6 +72,12 @@ pub use wasm::*; #[cfg(target_arch = "wasm32")] mod wasm { + use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + }; + use futures::future::Either; use super::*; @@ -150,32 +156,62 @@ mod wasm { F::Output: 'static, { let (res_tx, res_rx) = tokio::sync::oneshot::channel(); - let (closer_tx, mut closer_rx) = tokio::sync::mpsc::channel::<()>(1); + let (closer_tx, closer_rx) = tokio::sync::mpsc::channel::<()>(1); + let closer_handle = CloserHandle::new(closer_rx); let handle = WasmStreamHandle { result: res_rx, closer: closer_tx, ready, }; - + tracing::info!("Spawning local task on web executor"); wasm_bindgen_futures::spawn_local(async move { - let recv = closer_rx.recv(); - futures::pin_mut!(recv); + futures::pin_mut!(closer_handle); futures::pin_mut!(future); - let value = match futures::future::select(recv, future).await { - Either::Left((_, _)) => Err(StreamHandleError::StreamClosed), - Either::Right((v, _)) => Ok(v), + let value = match futures::future::select(closer_handle, future).await { + Either::Left((_, _)) => { + tracing::warn!("stream closed"); + Err(StreamHandleError::StreamClosed) + } + Either::Right((v, _)) => { + tracing::debug!("Future ended with value"); + Ok(v) + } }; let _ = res_tx.send(value); + tracing::info!("spawned local future closing"); }); handle } + + struct CloserHandle(tokio::sync::mpsc::Receiver<()>); + + impl CloserHandle { + fn new(receiver: tokio::sync::mpsc::Receiver<()>) -> Self { + Self(receiver) + } + } + + impl Future for CloserHandle { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.0.poll_recv(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Some(_)) => Poll::Ready(()), + // if the channel is closed, the task has detached and must + // be kept alive for the duration for the program + Poll::Ready(None) => Poll::Pending, + } + } + } } #[cfg(not(target_arch = "wasm32"))] mod native { use super::*; + use std::future::Future; use tokio::task::JoinHandle; pub struct TokioStreamHandle { diff --git a/xmtp_mls/src/subscriptions.rs b/xmtp_mls/src/subscriptions.rs index 90de8461f..d285050c2 100644 --- a/xmtp_mls/src/subscriptions.rs +++ b/xmtp_mls/src/subscriptions.rs @@ -6,23 +6,27 @@ use tokio::{ task::JoinHandle, }; use tokio_stream::wrappers::BroadcastStream; +use tracing::instrument; use xmtp_id::scw_verifier::SmartContractSignatureVerifier; use xmtp_proto::{api_client::XmtpMlsStreams, xmtp::mls::api::v1::WelcomeMessage}; use crate::{ client::{extract_welcome_message, ClientError}, - groups::{mls_sync::GroupMessageProcessingError, subscriptions, GroupError, MlsGroup}, - retry::{Retry, RetryableError}, - retry_async, retryable, + groups::{ + device_sync::preference_sync::UserPreferenceUpdate, group_metadata::GroupMetadata, + mls_sync::GroupMessageProcessingError, scoped_client::ScopedGroupClient as _, + subscriptions, GroupError, MlsGroup, + }, storage::{ consent_record::StoredConsentRecord, group::{ConversationType, GroupQueryArgs, StoredGroup}, group_message::StoredGroupMessage, StorageError, }, - Client, XmtpApi, + Client, XmtpApi, XmtpOpenMlsProvider, }; use thiserror::Error; +use xmtp_common::{retry_async, retryable, Retry, RetryableError}; #[derive(Debug, Error)] pub enum LocalEventError { @@ -51,8 +55,8 @@ pub enum LocalEvents { // a new group was created NewGroup(MlsGroup), SyncMessage(SyncMessage), - OutgoingConsentUpdates(Vec), - IncomingConsentUpdates(Vec), + OutgoingPreferenceUpdates(Vec), + IncomingPreferenceUpdate(Vec), } #[derive(Clone)] @@ -76,8 +80,8 @@ impl LocalEvents { match &self { SyncMessage(_) => Some(self), - OutgoingConsentUpdates(_) => Some(self), - IncomingConsentUpdates(_) => Some(self), + OutgoingPreferenceUpdates(_) => Some(self), + IncomingPreferenceUpdate(_) => Some(self), _ => None, } } @@ -86,8 +90,26 @@ impl LocalEvents { use LocalEvents::*; match self { - OutgoingConsentUpdates(cr) => Some(cr), - IncomingConsentUpdates(cr) => Some(cr), + OutgoingPreferenceUpdates(updates) => { + let updates = updates + .into_iter() + .filter_map(|pu| match pu { + UserPreferenceUpdate::ConsentUpdate(cr) => Some(cr), + _ => None, + }) + .collect(); + Some(updates) + } + IncomingPreferenceUpdate(updates) => { + let updates = updates + .into_iter() + .filter_map(|pu| match pu { + UserPreferenceUpdate::ConsentUpdate(cr) => Some(cr), + _ => None, + }) + .collect(); + Some(updates) + } _ => None, } } @@ -104,9 +126,10 @@ impl StreamMessages for broadcast::Receiver> where C: Clone + Send + Sync + 'static, { + #[instrument(level = "trace", skip_all)] fn stream_sync_messages(self) -> impl Stream, SubscribeError>> { BroadcastStream::new(self).filter_map(|event| async { - crate::optify!(event, "Missed message due to event queue lag") + xmtp_common::optify!(event, "Missed message due to event queue lag") .and_then(LocalEvents::sync_filter) .map(Result::Ok) }) @@ -116,7 +139,7 @@ where self, ) -> impl Stream, SubscribeError>> { BroadcastStream::new(self).filter_map(|event| async { - crate::optify!(event, "Missed message due to event queue lag") + xmtp_common::optify!(event, "Missed message due to event queue lag") .and_then(LocalEvents::consent_filter) .map(Result::Ok) }) @@ -202,6 +225,7 @@ where { async fn process_streamed_welcome( &self, + provider: &XmtpOpenMlsProvider, welcome: WelcomeMessage, ) -> Result, ClientError> { let welcome_v1 = extract_welcome_message(welcome)?; @@ -215,10 +239,10 @@ where let welcome_v1 = &welcome_v1; self.context .store() - .transaction_async(|provider| async move { + .transaction_async(provider, |provider| async move { MlsGroup::create_from_encrypted_welcome( Arc::new(self.clone()), - &provider, + provider, welcome_v1.hpke_public_key.as_slice(), &welcome_v1.data, welcome_v1.id as i64, @@ -230,11 +254,12 @@ where ); if let Some(err) = creation_result.as_ref().err() { - let conn = self.context.store().conn()?; + let conn = provider.conn_ref(); let result = conn.find_group_by_welcome_id(welcome_v1.id as i64); match result { Ok(Some(group)) => { tracing::info!( + inbox_id = self.inbox_id(), group_id = hex::encode(&group.id), welcome_id = ?group.welcome_id, "Loading existing group for welcome_id: {:?}", @@ -254,67 +279,81 @@ where &self, envelope_bytes: Vec, ) -> Result, ClientError> { + let provider = self.mls_provider()?; let envelope = WelcomeMessage::decode(envelope_bytes.as_slice()) .map_err(|e| ClientError::Generic(e.to_string()))?; - let welcome = self.process_streamed_welcome(envelope).await?; + let welcome = self.process_streamed_welcome(&provider, envelope).await?; Ok(welcome) } - pub async fn stream_conversations( - &self, + #[tracing::instrument(level = "debug", skip_all)] + pub async fn stream_conversations<'a>( + &'a self, conversation_type: Option, - ) -> Result, SubscribeError>> + '_, ClientError> + ) -> Result, SubscribeError>> + 'a, ClientError> where ApiClient: XmtpMlsStreams, { - let event_queue = tokio_stream::wrappers::BroadcastStream::new( - self.local_events.subscribe(), - ) - .filter_map(|event| async { - crate::optify!(event, "Missed messages due to event queue lag") - .and_then(LocalEvents::group_filter) - .map(Result::Ok) - }); - - // Helper function for filtering Dm groups - let filter_group = move |group: Result, ClientError>| { - let conversation_type = &conversation_type; - // take care of any possible errors - let result = || -> Result<_, _> { - let group = group?; - let provider = group.client.context().mls_provider()?; - let metadata = group.metadata(&provider)?; - Ok((metadata, group)) - }; - let filtered = result().map(|(metadata, group)| { - conversation_type - .map_or(true, |ct| ct == metadata.conversation_type) - .then_some(group) - }); - futures::future::ready(filtered.transpose()) - }; - let installation_key = self.installation_public_key(); let id_cursor = 0; - tracing::info!("Setting up conversation stream"); + tracing::info!(inbox_id = self.inbox_id(), "Setting up conversation stream"); let subscription = self .api_client - .subscribe_welcome_messages(installation_key, Some(id_cursor)) - .await?; + .subscribe_welcome_messages(installation_key.as_ref(), Some(id_cursor)) + .await? + .map(WelcomeOrGroup::::Welcome); + + let event_queue = + tokio_stream::wrappers::BroadcastStream::new(self.local_events.subscribe()) + .filter_map(|event| async { + xmtp_common::optify!(event, "Missed messages due to event queue lag") + .and_then(LocalEvents::group_filter) + .map(Result::Ok) + }) + .map(WelcomeOrGroup::::Group); + + let stream = futures::stream::select(event_queue, subscription); + let stream = stream.filter_map(move |group_or_welcome| async move { + tracing::info!( + inbox_id = self.inbox_id(), + installation_id = %self.installation_id(), + "Received conversation streaming payload" + ); + let filtered = self.process_streamed_convo(group_or_welcome).await; + let filtered = filtered.map(|(metadata, group)| { + conversation_type + .map_or(true, |ct| ct == metadata.conversation_type) + .then_some(group) + }); + filtered.transpose() + }); - let stream = subscription - .map(|welcome| async { - tracing::info!("Received conversation streaming payload"); - self.process_streamed_welcome(welcome?).await - }) - .filter_map(|v| async { Some(v.await) }); + Ok(stream) + } - Ok(futures::stream::select(stream, event_queue).filter_map(filter_group)) + async fn process_streamed_convo( + &self, + welcome_or_group: WelcomeOrGroup, + ) -> Result<(GroupMetadata, MlsGroup>), SubscribeError> { + let provider = self.mls_provider()?; + let group = match welcome_or_group { + WelcomeOrGroup::Welcome(welcome) => { + self.process_streamed_welcome(&provider, welcome?).await? + } + WelcomeOrGroup::Group(group) => group?, + }; + let metadata = group.metadata(provider)?; + Ok((metadata, group)) } } +enum WelcomeOrGroup { + Group(Result>, SubscribeError>), + Welcome(Result), +} + impl Client where ApiClient: XmtpApi + XmtpMlsStreams + Send + Sync + 'static, @@ -340,21 +379,30 @@ where }) } + #[tracing::instrument(level = "debug", skip_all)] pub async fn stream_all_messages( &self, conversation_type: Option, ) -> Result> + '_, ClientError> { - let conn = self.store().conn()?; - self.sync_welcomes(&conn).await?; - - let mut group_id_to_info = self - .store() - .conn()? - .find_groups(GroupQueryArgs::default().maybe_conversation_type(conversation_type))? - .into_iter() - .map(Into::into) - .collect::, MessagesStreamInfo>>(); + tracing::debug!( + inbox_id = self.inbox_id(), + conversation_type = ?conversation_type, + "stream all messages" + ); + let mut group_id_to_info = async { + let provider = self.mls_provider()?; + self.sync_welcomes(&provider).await?; + + let group_id_to_info = provider + .conn_ref() + .find_groups(GroupQueryArgs::default().maybe_conversation_type(conversation_type))? + .into_iter() + .map(Into::into) + .collect::, MessagesStreamInfo>>(); + Ok::<_, ClientError>(group_id_to_info) + } + .await?; let stream = async_stream::stream! { let messages_stream = subscriptions::stream_messages( @@ -364,7 +412,6 @@ where .await?; futures::pin_mut!(messages_stream); - tracing::info!("Setting up conversation stream in stream_all_messages"); let convo_stream = self.stream_conversations(conversation_type).await?; futures::pin_mut!(convo_stream); @@ -497,10 +544,11 @@ pub(crate) mod tests { atomic::{AtomicU64, Ordering}, Arc, }; + use wasm_bindgen_test::wasm_bindgen_test; use xmtp_cryptography::utils::generate_local_wallet; + use xmtp_id::InboxOwner; - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_stream_welcomes() { let alice = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bob = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -535,12 +583,9 @@ pub(crate) mod tests { assert_eq!(bob_received_groups.group_id, group_id); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_stream_messages() { + xmtp_common::logger(); let alice = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bob = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -548,13 +593,12 @@ pub(crate) mod tests { .create_group(None, GroupMetadataOptions::default()) .unwrap(); - // let mut bob_stream = bob.stream_conversations().await.unwrap()warning: unused implementer of `futures::Future` that must be used; alice_group .add_members_by_inbox_id(&[bob.inbox_id()]) .await .unwrap(); let bob_group = bob - .sync_welcomes(&bob.store().conn().unwrap()) + .sync_welcomes(&bob.mls_provider().unwrap()) .await .unwrap(); let bob_group = bob_group.first().unwrap(); @@ -570,26 +614,25 @@ pub(crate) mod tests { notify_ptr.notify_one(); } }); - let mut stream = tokio_stream::wrappers::UnboundedReceiverStream::new(rx); + let stream = tokio_stream::wrappers::UnboundedReceiverStream::new(rx); + // let stream = alice_group.stream().await.unwrap(); + futures::pin_mut!(stream); bob_group.send_message(b"hello").await.unwrap(); - notify.wait_for_delivery().await.unwrap(); + tracing::debug!("Bob Sent Message!, waiting for delivery"); + // notify.wait_for_delivery().await.unwrap(); let message = stream.next().await.unwrap().unwrap(); assert_eq!(message.decrypted_message_bytes, b"hello"); bob_group.send_message(b"hello2").await.unwrap(); - notify.wait_for_delivery().await.unwrap(); + // notify.wait_for_delivery().await.unwrap(); let message = stream.next().await.unwrap().unwrap(); assert_eq!(message.decrypted_message_bytes, b"hello2"); // assert_eq!(bob_received_groups.group_id, alice_bob_group.group_id); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_stream_all_messages_unchanging_group_list() { let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bo = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -610,7 +653,7 @@ pub(crate) mod tests { .add_members_by_inbox_id(&[caro.inbox_id()]) .await .unwrap(); - crate::sleep(core::time::Duration::from_millis(100)).await; + xmtp_common::time::sleep(core::time::Duration::from_millis(100)).await; let messages: Arc>> = Arc::new(Mutex::new(Vec::new())); let messages_clone = messages.clone(); @@ -646,15 +689,12 @@ pub(crate) mod tests { assert_eq!(messages[3].decrypted_message_bytes, b"fourth"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_stream_all_messages_changing_group_list() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bo = ClientBuilder::new_test_client(&generate_local_wallet()).await; - let caro = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); + let caro_wallet = generate_local_wallet(); + let caro = Arc::new(ClientBuilder::new_test_client(&caro_wallet).await); let alix_group = alix .create_group(None, GroupMetadataOptions::default()) @@ -678,27 +718,21 @@ pub(crate) mod tests { ); handle.wait_for_ready().await; - alix_group.send_message("first".as_bytes()).await.unwrap(); + alix_group.send_message(b"first").await.unwrap(); delivery .wait_for_delivery() .await .expect("timed out waiting for `first`"); - let bo_group = bo - .create_group(None, GroupMetadataOptions::default()) - .unwrap(); - bo_group - .add_members_by_inbox_id(&[caro.inbox_id()]) - .await - .unwrap(); + let bo_group = bo.create_dm(caro_wallet.get_address()).await.unwrap(); - bo_group.send_message("second".as_bytes()).await.unwrap(); + bo_group.send_message(b"second").await.unwrap(); delivery .wait_for_delivery() .await .expect("timed out waiting for `second`"); - alix_group.send_message("third".as_bytes()).await.unwrap(); + alix_group.send_message(b"third").await.unwrap(); delivery .wait_for_delivery() .await @@ -712,13 +746,13 @@ pub(crate) mod tests { .await .unwrap(); - alix_group.send_message("fourth".as_bytes()).await.unwrap(); + alix_group.send_message(b"fourth").await.unwrap(); delivery .wait_for_delivery() .await .expect("timed out waiting for `fourth`"); - alix_group_2.send_message("fifth".as_bytes()).await.unwrap(); + alix_group_2.send_message(b"fifth").await.unwrap(); delivery .wait_for_delivery() .await @@ -738,18 +772,15 @@ pub(crate) mod tests { .send_message("should not show up".as_bytes()) .await .unwrap(); - crate::sleep(core::time::Duration::from_millis(100)).await; + xmtp_common::time::sleep(core::time::Duration::from_millis(100)).await; let messages = messages.lock(); + assert_eq!(messages.len(), 5); } #[ignore] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread"))] async fn test_stream_all_messages_does_not_lose_messages() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let caro = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -782,7 +813,7 @@ pub(crate) mod tests { crate::spawn(None, async move { for _ in 0..50 { alix_group_pointer.send_message(b"spam").await.unwrap(); - crate::sleep(core::time::Duration::from_micros(200)).await; + xmtp_common::time::sleep(core::time::Duration::from_micros(200)).await; } }); @@ -814,8 +845,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread"))] async fn test_self_group_creation() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bo = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -863,7 +893,7 @@ pub(crate) mod tests { } // Verify syncing welcomes while streaming causes no issues - alix.sync_welcomes(&alix.store().conn().unwrap()) + alix.sync_welcomes(&alix.mls_provider().unwrap()) .await .unwrap(); let find_groups_results = alix.find_groups(GroupQueryArgs::default()).unwrap(); @@ -876,8 +906,7 @@ pub(crate) mod tests { closer.end(); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread"))] async fn test_dm_streaming() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bo = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -898,7 +927,7 @@ pub(crate) mod tests { }, ); - alix.create_dm_by_inbox_id(bo.inbox_id().to_string()) + alix.create_dm_by_inbox_id(&alix.mls_provider().unwrap(), bo.inbox_id().to_string()) .await .unwrap(); @@ -948,7 +977,7 @@ pub(crate) mod tests { let result = notify.wait_for_delivery().await; assert!(result.is_err(), "Stream unexpectedly received a Group"); - alix.create_dm_by_inbox_id(bo.inbox_id().to_string()) + alix.create_dm_by_inbox_id(&alix.mls_provider().unwrap(), bo.inbox_id().to_string()) .await .unwrap(); notify.wait_for_delivery().await.unwrap(); @@ -971,7 +1000,7 @@ pub(crate) mod tests { notify_pointer.notify_one(); }); - alix.create_dm_by_inbox_id(bo.inbox_id().to_string()) + alix.create_dm_by_inbox_id(&alix.mls_provider().unwrap(), bo.inbox_id().to_string()) .await .unwrap(); notify.wait_for_delivery().await.unwrap(); @@ -981,7 +1010,7 @@ pub(crate) mod tests { } let dm = bo - .create_dm_by_inbox_id(alix.inbox_id().to_string()) + .create_dm_by_inbox_id(&bo.mls_provider().unwrap(), alix.inbox_id().to_string()) .await .unwrap(); dm.add_members_by_inbox_id(&[alix.inbox_id()]) @@ -1010,8 +1039,7 @@ pub(crate) mod tests { closer.end(); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread"))] async fn test_dm_stream_all_messages() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bo = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -1025,7 +1053,7 @@ pub(crate) mod tests { .unwrap(); let alix_dm = alix - .create_dm_by_inbox_id(bo.inbox_id().to_string()) + .create_dm_by_inbox_id(&alix.mls_provider().unwrap(), bo.inbox_id().to_string()) .await .unwrap(); diff --git a/xmtp_mls/src/types.rs b/xmtp_mls/src/types.rs index 0a73a89b1..5a97a0ca3 100644 --- a/xmtp_mls/src/types.rs +++ b/xmtp_mls/src/types.rs @@ -1,2 +1,86 @@ pub type Address = String; -pub type InstallationId = String; + +use std::fmt; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct InstallationId([u8; 32]); + +impl fmt::Display for InstallationId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + +impl std::ops::Deref for InstallationId { + type Target = [u8; 32]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for InstallationId { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From for Vec { + fn from(value: InstallationId) -> Self { + value.0.to_vec() + } +} + +impl From<[u8; 32]> for InstallationId { + fn from(value: [u8; 32]) -> Self { + InstallationId(value) + } +} + +impl PartialEq> for InstallationId { + fn eq(&self, other: &Vec) -> bool { + self.0.eq(&other[..]) + } +} + +impl PartialEq for Vec { + fn eq(&self, other: &InstallationId) -> bool { + other.0.eq(&self[..]) + } +} + +impl PartialEq<&Vec> for InstallationId { + fn eq(&self, other: &&Vec) -> bool { + self.0.eq(&other[..]) + } +} + +impl PartialEq for &Vec { + fn eq(&self, other: &InstallationId) -> bool { + other.0.eq(&self[..]) + } +} + +impl PartialEq<[u8]> for InstallationId { + fn eq(&self, other: &[u8]) -> bool { + self.0.eq(other) + } +} + +impl PartialEq for [u8] { + fn eq(&self, other: &InstallationId) -> bool { + other.0.eq(self) + } +} + +impl PartialEq<[u8; 32]> for InstallationId { + fn eq(&self, other: &[u8; 32]) -> bool { + self.0.eq(other) + } +} + +impl PartialEq for [u8; 32] { + fn eq(&self, other: &InstallationId) -> bool { + other.0.eq(&self[..]) + } +} diff --git a/xmtp_mls/src/utils/bench/clients.rs b/xmtp_mls/src/utils/bench/clients.rs new file mode 100644 index 000000000..90eaaf666 --- /dev/null +++ b/xmtp_mls/src/utils/bench/clients.rs @@ -0,0 +1,72 @@ +use crate::utils::test::{TestClient as TestApiClient, HISTORY_SYNC_URL}; +use crate::{client::Client, identity::IdentityStrategy}; +use ethers::signers::LocalWallet; +use xmtp_id::{ + associations::{ + builder::SignatureRequest, + generate_inbox_id, + unverified::{UnverifiedRecoverableEcdsaSignature, UnverifiedSignature}, + }, + InboxOwner, +}; +use xmtp_proto::api_client::XmtpTestClient; + +pub type BenchClient = Client; + +/// Create a new, yet-unregistered client +pub async fn new_unregistered_client(history_sync: bool) -> (BenchClient, LocalWallet) { + let _ = fdlimit::raise_fd_limit(); + + let nonce = 1; + let wallet = xmtp_cryptography::utils::generate_local_wallet(); + let inbox_id = generate_inbox_id(&wallet.get_address(), &nonce).unwrap(); + + let dev = std::env::var("DEV_GRPC"); + let is_dev_network = matches!(dev, Ok(d) if d == "true" || d == "1"); + + let api_client = if is_dev_network { + tracing::info!("Using Dev GRPC"); + ::create_dev().await + } else { + tracing::info!("Using Local GRPC"); + ::create_local().await + }; + + let client = BenchClient::builder(IdentityStrategy::new( + inbox_id, + wallet.get_address(), + nonce, + None, + )); + + let mut client = client.temp_store().await.api_client(api_client); + if history_sync { + client = client.history_sync_url(HISTORY_SYNC_URL); + } + let client = client.build().await.unwrap(); + + (client, wallet) +} + +/// Add ECDSA Signature to a client +pub async fn ecdsa_signature(client: &BenchClient, owner: impl InboxOwner) -> SignatureRequest { + let mut signature_request = client.context().signature_request().unwrap(); + let signature_text = signature_request.signature_text(); + let unverified_signature = UnverifiedSignature::RecoverableEcdsa( + UnverifiedRecoverableEcdsaSignature::new(owner.sign(&signature_text).unwrap().into()), + ); + signature_request + .add_signature(unverified_signature, client.scw_verifier()) + .await + .unwrap(); + + signature_request +} + +/// Create a new registered client with an EOA +pub async fn new_client(history_sync: bool) -> BenchClient { + let (client, wallet) = new_unregistered_client(history_sync).await; + let signature_request = ecdsa_signature(&client, wallet).await; + client.register_identity(signature_request).await.unwrap(); + client +} diff --git a/xmtp_mls/src/utils/bench.rs b/xmtp_mls/src/utils/bench/identity_gen.rs similarity index 59% rename from xmtp_mls/src/utils/bench.rs rename to xmtp_mls/src/utils/bench/identity_gen.rs index 781e61bf8..8c1b42489 100644 --- a/xmtp_mls/src/utils/bench.rs +++ b/xmtp_mls/src/utils/bench/identity_gen.rs @@ -1,92 +1,12 @@ -//! Utilities for xmtp_mls benchmarks -//! Utilities mostly include pre-generating identities in order to save time when writing/testing -//! benchmarks. -#![allow(clippy::unwrap_used)] +//! Identity Generation -use crate::{builder::ClientBuilder, Client}; +use crate::builder::ClientBuilder; use indicatif::{ProgressBar, ProgressStyle}; -use once_cell::sync::OnceCell; use serde::{Deserialize, Serialize}; -use std::sync::Once; -use thiserror::Error; -use tracing::{Metadata, Subscriber}; -use tracing_flame::{FlameLayer, FlushGuard}; -use tracing_subscriber::{ - layer::{Context, Filter, Layer, SubscriberExt}, - registry::LookupSpan, - util::SubscriberInitExt, - EnvFilter, -}; use xmtp_cryptography::utils::generate_local_wallet; use xmtp_id::InboxOwner; -use super::test::TestClient; - -pub const BENCH_ROOT_SPAN: &str = "xmtp-trace-bench"; - -/// Re-export of functions in private modules for benchmarks -pub mod re_export { - pub use crate::hpke::encrypt_welcome; -} - -#[derive(Debug, Error)] -pub enum BenchError { - #[error(transparent)] - Serde(#[from] serde_json::Error), - #[error(transparent)] - Io(#[from] std::io::Error), -} - -static INIT: Once = Once::new(); - -static LOGGER: OnceCell>> = OnceCell::new(); - -/// initializes logging for benchmarks -/// - FMT logging is enabled by passing the normal `RUST_LOG` environment variable options. -/// - Generate a flamegraph from tracing data by passing `XMTP_FLAMEGRAPH=trace` -pub fn init_logging() { - INIT.call_once(|| { - let (flame_layer, guard) = FlameLayer::with_file("./tracing.folded").unwrap(); - let flame_layer = flame_layer - .with_threads_collapsed(true) - .with_module_path(true); - // .with_empty_samples(false); - - tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer().with_filter(EnvFilter::from_default_env())) - .with( - flame_layer - .with_filter(BenchFilter) - .with_filter(EnvFilter::from_env("XMTP_FLAMEGRAPH")), - ) - .init(); - - LOGGER.set(guard).unwrap(); - }) -} - -/// Filters for only spans where the root span name is "bench" -pub struct BenchFilter; - -impl Filter for BenchFilter -where - S: Subscriber + for<'lookup> LookupSpan<'lookup> + std::fmt::Debug, - for<'lookup> >::Data: std::fmt::Debug, -{ - fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool { - if meta.name() == BENCH_ROOT_SPAN { - return true; - } - if let Some(id) = cx.current_span().id() { - if let Some(s) = cx.span_scope(id) { - if let Some(s) = s.from_root().take(1).collect::>().first() { - return s.name() == BENCH_ROOT_SPAN; - } - } - } - false - } -} +use super::{BenchClient, BenchError}; pub fn file_path(is_dev_network: bool) -> String { if is_dev_network { @@ -176,9 +96,10 @@ async fn create_identities(n: usize, is_dev_network: bool) -> Vec { /// node still has those identities. pub async fn create_identities_if_dont_exist( identities: usize, - client: &Client, + client: &BenchClient, is_dev_network: bool, ) -> Vec { + let _ = fdlimit::raise_fd_limit(); match load_identities(is_dev_network) { Ok(identities) => { tracing::info!( diff --git a/xmtp_mls/src/utils/bench/mod.rs b/xmtp_mls/src/utils/bench/mod.rs new file mode 100644 index 000000000..5466c6dd9 --- /dev/null +++ b/xmtp_mls/src/utils/bench/mod.rs @@ -0,0 +1,99 @@ +//! Utilities for xmtp_mls benchmarks +//! Utilities mostly include pre-generating identities in order to save time when writing/testing +//! benchmarks. +#![allow(clippy::unwrap_used)] + +mod identity_gen; +pub use identity_gen::*; +pub mod clients; +pub use clients::*; + +use once_cell::sync::OnceCell; +use std::sync::Once; +use thiserror::Error; +use tracing::{Metadata, Subscriber}; +use tracing_flame::{FlameLayer, FlushGuard}; +use tracing_subscriber::{ + layer::{Context, Filter, Layer, SubscriberExt}, + registry::LookupSpan, + util::SubscriberInitExt, + EnvFilter, +}; + +pub const BENCH_ROOT_SPAN: &str = "xmtp-trace-bench"; + +/// Re-export of functions in private modules for benchmarks +pub mod re_export { + pub use crate::hpke::encrypt_welcome; +} + +#[derive(Debug, Error)] +pub enum BenchError { + #[error(transparent)] + Serde(#[from] serde_json::Error), + #[error(transparent)] + Io(#[from] std::io::Error), +} + +static INIT: Once = Once::new(); + +static LOGGER: OnceCell>> = OnceCell::new(); + +/// initializes logging for benchmarks +/// - FMT logging is enabled by passing the normal `RUST_LOG` environment variable options. +/// - Generate a flamegraph from tracing data by passing `XMTP_FLAMEGRAPH=trace` +pub fn init_logging() { + INIT.call_once(|| { + let (flame_layer, guard) = FlameLayer::with_file("./tracing.folded").unwrap(); + let flame_layer = flame_layer + .with_threads_collapsed(true) + .with_module_path(true); + // .with_empty_samples(false); + + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer().with_filter(EnvFilter::from_default_env())) + .with( + flame_layer + .with_filter(BenchFilter) + .with_filter(EnvFilter::from_env("XMTP_FLAMEGRAPH")), + ) + .init(); + + LOGGER.set(guard).unwrap(); + }) +} + +/// criterion `batch_iter` surrounds the closure in a `Runtime.block_on` despite being a sync +/// function, even in the async 'to_async` setup. Therefore we do this (only _slightly_) hacky +/// workaround to allow us to async setup some groups. +pub fn bench_async_setup(fun: F) -> T +where + F: Fn() -> Fut, + Fut: futures::future::Future, +{ + use tokio::runtime::Handle; + tokio::task::block_in_place(move || Handle::current().block_on(async move { fun().await })) +} + +/// Filters for only spans where the root span name is "bench" +pub struct BenchFilter; + +impl Filter for BenchFilter +where + S: Subscriber + for<'lookup> LookupSpan<'lookup> + std::fmt::Debug, + for<'lookup> >::Data: std::fmt::Debug, +{ + fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool { + if meta.name() == BENCH_ROOT_SPAN { + return true; + } + if let Some(id) = cx.current_span().id() { + if let Some(s) = cx.span_scope(id) { + if let Some(s) = s.from_root().take(1).collect::>().first() { + return s.name() == BENCH_ROOT_SPAN; + } + } + } + false + } +} diff --git a/xmtp_mls/src/utils/mod.rs b/xmtp_mls/src/utils/mod.rs index 19872e390..357410b65 100644 --- a/xmtp_mls/src/utils/mod.rs +++ b/xmtp_mls/src/utils/mod.rs @@ -8,16 +8,11 @@ pub mod hash { } pub mod time { - use wasm_timer::{SystemTime, UNIX_EPOCH}; + const SECS_IN_30_DAYS: i64 = 60 * 60 * 24 * 30; - pub const NS_IN_SEC: i64 = 1_000_000_000; - - pub fn now_ns() -> i64 { - let now = SystemTime::now(); - - now.duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_nanos() as i64 + /// Current hmac epoch. HMAC keys change every 30 days + pub fn hmac_epoch() -> i64 { + xmtp_common::time::now_secs() / SECS_IN_30_DAYS } } @@ -42,34 +37,3 @@ pub mod id { hex::encode(group_id) } } - -#[cfg(any( - all(target_arch = "wasm32", feature = "test-utils"), - all(test, target_arch = "wasm32") -))] -pub mod wasm { - use tokio::sync::OnceCell; - static INIT: OnceCell<()> = OnceCell::const_new(); - - /// can be used to debug wasm tests - /// normal tracing logs are output to the browser console - pub async fn init() { - use web_sys::console; - - INIT.get_or_init(|| async { - console::log_1(&"INIT".into()); - let config = tracing_wasm::WASMLayerConfigBuilder::default() - .set_console_config(tracing_wasm::ConsoleConfig::ReportWithoutConsoleColor) - .build(); - tracing_wasm::set_as_global_default_with_config(config); - console_error_panic_hook::set_once(); - diesel_wasm_sqlite::init_sqlite().await; - }) - .await; - } -} - -#[cfg(not(target_arch = "wasm32"))] -pub mod wasm { - pub async fn init() {} -} diff --git a/xmtp_mls/src/utils/test/mod.rs b/xmtp_mls/src/utils/test/mod.rs index 1616556ea..ff44452b2 100755 --- a/xmtp_mls/src/utils/test/mod.rs +++ b/xmtp_mls/src/utils/test/mod.rs @@ -1,11 +1,7 @@ #![allow(clippy::unwrap_used)] -use rand::{ - distributions::{Alphanumeric, DistString}, - Rng, RngCore, -}; use std::sync::Arc; -use tokio::{sync::Notify, time::error::Elapsed}; +use tokio::sync::Notify; use xmtp_id::{ associations::{ generate_inbox_id, @@ -19,18 +15,18 @@ use xmtp_proto::api_client::XmtpTestClient; use crate::{ builder::ClientBuilder, identity::IdentityStrategy, - storage::{EncryptedMessageStore, StorageOption}, - types::Address, + storage::{DbConnection, EncryptedMessageStore, StorageOption}, Client, InboxOwner, XmtpApi, }; -#[cfg(not(target_arch = "wasm32"))] -pub mod traced_test; -#[cfg(not(target_arch = "wasm32"))] -pub use traced_test::traced_test; - pub type FullXmtpClient = Client; +// TODO: Dev-Versions of URL +const HISTORY_SERVER_HOST: &str = "localhost"; +const HISTORY_SERVER_PORT: u16 = 5558; +pub const HISTORY_SYNC_URL: &str = + const_format::concatcp!("http://", HISTORY_SERVER_HOST, ":", HISTORY_SERVER_PORT); + #[cfg(not(any(feature = "http-api", target_arch = "wasm32")))] pub type TestClient = xmtp_api_grpc::grpc_api_helper::Client; @@ -39,40 +35,9 @@ use xmtp_api_http::XmtpHttpApiClient; #[cfg(any(feature = "http-api", target_arch = "wasm32"))] pub type TestClient = XmtpHttpApiClient; -pub fn rand_string() -> String { - Alphanumeric.sample_string(&mut rand::thread_rng(), 24) -} - -pub fn rand_account_address() -> Address { - Alphanumeric.sample_string(&mut rand::thread_rng(), 42) -} - -pub fn rand_vec() -> Vec { - rand::thread_rng().gen::<[u8; 24]>().to_vec() -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn tmp_path() -> String { - let db_name = rand_string(); - format!("{}/{}.db3", std::env::temp_dir().to_str().unwrap(), db_name) -} - -#[cfg(target_arch = "wasm32")] -pub fn tmp_path() -> String { - let db_name = rand_string(); - format!("{}/{}.db3", "test_db", db_name) -} - -pub fn rand_time() -> i64 { - let mut rng = rand::thread_rng(); - rng.gen_range(0..1_000_000_000) -} - impl EncryptedMessageStore { pub fn generate_enc_key() -> [u8; 32] { - let mut key = [0u8; 32]; - xmtp_cryptography::utils::rng().fill_bytes(&mut key[..]); - key + xmtp_common::rand_array::<32>() } #[cfg(not(target_arch = "wasm32"))] @@ -91,7 +56,7 @@ impl EncryptedMessageStore { impl ClientBuilder { pub async fn temp_store(self) -> Self { - let tmpdb = tmp_path(); + let tmpdb = xmtp_common::tmp_path(); self.store( EncryptedMessageStore::new( StorageOption::Persistent(tmpdb), @@ -102,6 +67,7 @@ impl ClientBuilder { ) } } + impl ClientBuilder { pub async fn new_test_client(owner: &impl InboxOwner) -> FullXmtpClient { let api_client = ::create_local().await; @@ -170,12 +136,12 @@ impl ClientBuilder { async fn inner_build(owner: impl InboxOwner, api_client: A) -> Client where - A: XmtpApi + 'static, + A: XmtpApi + 'static + Send + Sync, { let nonce = 1; let inbox_id = generate_inbox_id(&owner.get_address(), &nonce).unwrap(); - let client = Client::::builder(IdentityStrategy::CreateIfNotFound( + let client = Client::::builder(IdentityStrategy::new( inbox_id, owner.get_address(), nonce, @@ -189,7 +155,8 @@ where .build() .await .unwrap(); - + let conn = client.store().conn().unwrap(); + conn.register_triggers(); register_client(&client, owner).await; client @@ -208,7 +175,7 @@ where let nonce = 1; let inbox_id = generate_inbox_id(&owner.get_address(), &nonce).unwrap(); - let mut builder = Client::::builder(IdentityStrategy::CreateIfNotFound( + let mut builder = Client::::builder(IdentityStrategy::new( inbox_id, owner.get_address(), nonce, @@ -224,14 +191,10 @@ where } let client = builder.build_with_verifier().await.unwrap(); - + let conn = client.store().conn().unwrap(); + conn.register_triggers(); register_client(&client, owner).await; - if client.history_sync_url.is_some() { - let provider = client.mls_provider().unwrap(); - client.start_sync_worker(&provider).await.unwrap(); - } - client } @@ -251,8 +214,8 @@ impl Delivery { } } - pub async fn wait_for_delivery(&self) -> Result<(), Elapsed> { - tokio::time::timeout(self.timeout, async { self.notify.notified().await }).await + pub async fn wait_for_delivery(&self) -> Result<(), xmtp_common::time::Expired> { + xmtp_common::time::timeout(self.timeout, async { self.notify.notified().await }).await } pub fn notify_one(&self) { @@ -291,3 +254,13 @@ pub async fn register_client( client.register_identity(signature_request).await.unwrap(); } + +/// wait for a minimum amount of intents to be published +/// TODO: Should wrap with a timeout +pub async fn wait_for_min_intents(conn: &DbConnection, n: usize) { + let mut published = conn.intents_published() as usize; + while published < n { + xmtp_common::yield_().await; + published = conn.intents_published() as usize; + } +} diff --git a/xmtp_mls/src/xmtp_openmls_provider.rs b/xmtp_mls/src/xmtp_openmls_provider.rs index cfe695fd6..a5597ee31 100644 --- a/xmtp_mls/src/xmtp_openmls_provider.rs +++ b/xmtp_mls/src/xmtp_openmls_provider.rs @@ -19,7 +19,11 @@ impl XmtpOpenMlsProviderPrivate { } } - pub(crate) fn conn_ref(&self) -> &DbConnectionPrivate { + pub fn new_crypto() -> RustCrypto { + RustCrypto::default() + } + + pub fn conn_ref(&self) -> &DbConnectionPrivate { self.key_store.conn_ref() } } @@ -45,7 +49,7 @@ where } } -impl<'a, C> OpenMlsProvider for &'a XmtpOpenMlsProviderPrivate +impl OpenMlsProvider for &XmtpOpenMlsProviderPrivate where C: diesel::Connection + diesel::connection::LoadConnection, { diff --git a/xmtp_proto/Cargo.toml b/xmtp_proto/Cargo.toml index bca40bd06..9e5ddf30f 100644 --- a/xmtp_proto/Cargo.toml +++ b/xmtp_proto/Cargo.toml @@ -6,7 +6,6 @@ license.workspace = true [dependencies] futures = { workspace = true } -openmls = { workspace = true, optional = true } pbjson-types.workspace = true pbjson.workspace = true prost = { workspace = true, features = ["prost-derive"] } @@ -14,17 +13,23 @@ serde = { workspace = true } async-trait = "0.1" hex.workspace = true openmls_rust_crypto = { workspace = true, optional = true } +tracing.workspace = true +xmtp_common.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tonic = { workspace = true } +tonic = { workspace = true, features = ["codegen", "server", "channel", "prost"] } +openmls = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +openmls = { workspace = true, features = ["js"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test.workspace = true [features] -convert = ["openmls", "openmls_rust_crypto", "proto_full"] +convert = ["openmls_rust_crypto", "proto_full"] default = [] -test-utils = [] +test-utils = ["xmtp_common/test-utils"] # @@protoc_deletion_point(features) # This section is automatically generated by protoc-gen-prost-crate. diff --git a/xmtp_proto/src/api_client.rs b/xmtp_proto/src/api_client.rs index d531bb867..75cd72fbb 100644 --- a/xmtp_proto/src/api_client.rs +++ b/xmtp_proto/src/api_client.rs @@ -128,12 +128,6 @@ where } } -/// Global Marker trait for WebAssembly -#[cfg(target_arch = "wasm32")] -pub trait Wasm {} -#[cfg(target_arch = "wasm32")] -impl Wasm for T {} - // Wasm futures don't have `Send` or `Sync` bounds. #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] @@ -311,11 +305,13 @@ impl XmtpMlsStreams for Box where T: XmtpMlsStreams + Sync + ?Sized, { - type GroupMessageStream<'a> = ::GroupMessageStream<'a> + type GroupMessageStream<'a> + = ::GroupMessageStream<'a> where Self: 'a; - type WelcomeMessageStream<'a> = ::WelcomeMessageStream<'a> + type WelcomeMessageStream<'a> + = ::WelcomeMessageStream<'a> where Self: 'a; diff --git a/xmtp_proto/src/convert.rs b/xmtp_proto/src/convert.rs index fe6f2a1e8..7e265f27c 100644 --- a/xmtp_proto/src/convert.rs +++ b/xmtp_proto/src/convert.rs @@ -6,7 +6,6 @@ use crate::xmtp::mls::api::v1::{ }; use crate::xmtp::xmtpv4::envelopes::client_envelope::Payload; use crate::xmtp::xmtpv4::envelopes::{AuthenticatedData, ClientEnvelope}; -use crate::xmtp::xmtpv4::payer_api::PublishClientEnvelopesRequest; use crate::v4_utils::{ build_identity_topic_from_hex_encoded, build_welcome_message_topic, get_group_message_topic, @@ -34,18 +33,16 @@ mod inbox_id { } } -impl TryFrom for PublishClientEnvelopesRequest { +impl TryFrom for ClientEnvelope { type Error = Error; fn try_from(req: UploadKeyPackageRequest) -> Result { if let Some(key_package) = req.key_package.as_ref() { - Ok(PublishClientEnvelopesRequest { - envelopes: vec![ClientEnvelope { - aad: Some(AuthenticatedData::with_topic(get_key_package_topic( - key_package, - )?)), - payload: Some(Payload::UploadKeyPackage(req)), - }], + Ok(ClientEnvelope { + aad: Some(AuthenticatedData::with_topic(get_key_package_topic( + key_package, + )?)), + payload: Some(Payload::UploadKeyPackage(req)), }) } else { Err(Error::new(InternalError(MissingPayloadError))) @@ -53,18 +50,16 @@ impl TryFrom for PublishClientEnvelopesRequest { } } -impl TryFrom for PublishClientEnvelopesRequest { +impl TryFrom for ClientEnvelope { type Error = Error; fn try_from(req: PublishIdentityUpdateRequest) -> Result { if let Some(identity_update) = req.identity_update { - Ok(PublishClientEnvelopesRequest { - envelopes: vec![ClientEnvelope { - aad: Some(AuthenticatedData::with_topic( - build_identity_topic_from_hex_encoded(&identity_update.inbox_id)?, - )), - payload: Some(Payload::IdentityUpdate(identity_update)), - }], + Ok(ClientEnvelope { + aad: Some(AuthenticatedData::with_topic( + build_identity_topic_from_hex_encoded(&identity_update.inbox_id)?, + )), + payload: Some(Payload::IdentityUpdate(identity_update)), }) } else { Err(Error::new(InternalError(MissingPayloadError))) @@ -72,18 +67,16 @@ impl TryFrom for PublishClientEnvelopesRequest { } } -impl TryFrom for PublishClientEnvelopesRequest { +impl TryFrom for ClientEnvelope { type Error = crate::Error; fn try_from(req: GroupMessageInput) -> Result { if let Some(GroupMessageInputVersion::V1(ref version)) = req.version { - Ok(PublishClientEnvelopesRequest { - envelopes: vec![ClientEnvelope { - aad: Some(AuthenticatedData::with_topic(get_group_message_topic( - version.data.clone(), - )?)), - payload: Some(Payload::GroupMessage(req)), - }], + Ok(ClientEnvelope { + aad: Some(AuthenticatedData::with_topic(get_group_message_topic( + version.data.clone(), + )?)), + payload: Some(Payload::GroupMessage(req)), }) } else { Err(Error::new(InternalError(MissingPayloadError))) @@ -91,18 +84,16 @@ impl TryFrom for PublishClientEnvelopesRequest { } } -impl TryFrom for PublishClientEnvelopesRequest { +impl TryFrom for ClientEnvelope { type Error = crate::Error; fn try_from(req: WelcomeMessageInput) -> Result { if let Some(WelcomeMessageVersion::V1(ref version)) = req.version { - Ok(PublishClientEnvelopesRequest { - envelopes: vec![ClientEnvelope { - aad: Some(AuthenticatedData::with_topic(build_welcome_message_topic( - version.installation_key.as_slice(), - ))), - payload: Some(Payload::WelcomeMessage(req)), - }], + Ok(ClientEnvelope { + aad: Some(AuthenticatedData::with_topic(build_welcome_message_topic( + version.installation_key.as_slice(), + ))), + payload: Some(Payload::WelcomeMessage(req)), }) } else { Err(Error::new(InternalError(MissingPayloadError))) diff --git a/xmtp_proto/src/error.rs b/xmtp_proto/src/error.rs index d8109b9b8..8a574c785 100644 --- a/xmtp_proto/src/error.rs +++ b/xmtp_proto/src/error.rs @@ -35,6 +35,13 @@ pub struct Error { source: Option, } +// network errors should generally be retryable, unless there's a bug in our code +impl xmtp_common::RetryableError for Error { + fn is_retryable(&self) -> bool { + true + } +} + impl Error { pub fn new(kind: ErrorKind) -> Self { Self { kind, source: None } diff --git a/xmtp_proto/src/gen/xmtp.message_contents.rs b/xmtp_proto/src/gen/xmtp.message_contents.rs index b4ec8ba5f..9b79214ca 100644 --- a/xmtp_proto/src/gen/xmtp.message_contents.rs +++ b/xmtp_proto/src/gen/xmtp.message_contents.rs @@ -791,16 +791,27 @@ pub struct FrameActionBody { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameAction { + #[deprecated] #[prost(message, optional, tag="1")] pub signature: ::core::option::Option, /// The SignedPublicKeyBundle of the signer, used to link the XMTP signature /// with a blockchain account through a chain of signatures. + #[deprecated] #[prost(message, optional, tag="2")] pub signed_public_key_bundle: ::core::option::Option, /// Serialized FrameActionBody message, so that the signature verification can /// happen on a byte-perfect representation of the message #[prost(bytes="vec", tag="3")] pub action_body: ::prost::alloc::vec::Vec, + /// The installation signature + #[prost(bytes="vec", tag="4")] + pub installation_signature: ::prost::alloc::vec::Vec, + /// The public installation id used to sign. + #[prost(bytes="vec", tag="5")] + pub installation_id: ::prost::alloc::vec::Vec, + /// The inbox id of the installation used to sign. + #[prost(string, tag="6")] + pub inbox_id: ::prost::alloc::string::String, } // Message V1 @@ -2608,7 +2619,7 @@ pub const FILE_DESCRIPTOR_SET: &[u8] = &[ 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x0c, 0x04, 0x09, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0c, 0x0a, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0c, 0x0f, 0x10, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, - 0x0a, 0xd5, 0x15, 0x0a, 0x1d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x0a, 0xb3, 0x19, 0x0a, 0x1d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x21, 0x6d, 0x65, 0x73, 0x73, 0x61, @@ -2637,127 +2648,137 @@ pub const FILE_DESCRIPTOR_SET: &[u8] = &[ 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xd5, 0x01, 0x0a, 0x0b, 0x46, 0x72, 0x61, 0x6d, - 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xd8, 0x02, 0x0a, 0x0b, 0x46, 0x72, 0x61, 0x6d, + 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x09, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x65, 0x0a, 0x18, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x75, 0x6e, - 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x78, 0x6d, 0x74, 0x70, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, - 0x79, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x15, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x64, 0x79, 0x42, - 0xc7, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x0b, 0x46, - 0x72, 0x61, 0x6d, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x58, - 0xaa, 0x02, 0x14, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x14, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, - 0x20, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0xea, 0x02, 0x15, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xd4, 0x0e, 0x0a, 0x06, 0x12, 0x04, - 0x01, 0x00, 0x2e, 0x01, 0x0a, 0x47, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x3d, - 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x6b, 0x65, 0x79, - 0x20, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x08, 0x0a, - 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x1e, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x00, 0x12, 0x03, 0x05, - 0x00, 0x2b, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x01, 0x12, 0x03, 0x06, 0x00, 0x2a, 0x0a, 0x08, 0x0a, - 0x01, 0x08, 0x12, 0x03, 0x08, 0x00, 0x43, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x08, - 0x00, 0x43, 0x0a, 0x83, 0x01, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x0d, 0x00, 0x22, 0x01, 0x1a, - 0x77, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x74, 0x68, - 0x61, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, - 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x73, - 0x69, 0x64, 0x65, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x60, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x60, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, - 0x03, 0x0d, 0x08, 0x17, 0x0a, 0x56, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x10, 0x02, - 0x17, 0x1a, 0x49, 0x20, 0x54, 0x68, 0x65, 0x20, 0x55, 0x52, 0x4c, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x61, - 0x73, 0x20, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x0a, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x62, - 0x65, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x72, 0x6f, 0x6d, - 0x20, 0x60, 0x70, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x60, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x10, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, - 0x02, 0x00, 0x01, 0x12, 0x03, 0x10, 0x09, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, - 0x03, 0x12, 0x03, 0x10, 0x15, 0x16, 0x0a, 0x34, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, - 0x12, 0x02, 0x19, 0x1a, 0x27, 0x20, 0x54, 0x68, 0x65, 0x20, 0x31, 0x2d, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x65, 0x64, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, - 0x77, 0x61, 0x73, 0x20, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x12, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, - 0x02, 0x01, 0x01, 0x12, 0x03, 0x12, 0x08, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, - 0x03, 0x12, 0x03, 0x12, 0x17, 0x18, 0x0a, 0x45, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, - 0x14, 0x02, 0x2b, 0x1a, 0x38, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, - 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x20, 0x69, 0x6e, 0x20, - 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x73, 0x69, 0x6e, - 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x0a, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x14, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x14, 0x09, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, - 0x02, 0x03, 0x12, 0x03, 0x14, 0x15, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x08, - 0x12, 0x03, 0x14, 0x17, 0x2a, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x00, 0x02, 0x02, 0x08, 0x03, 0x12, - 0x03, 0x14, 0x18, 0x29, 0x0a, 0x8a, 0x01, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x17, - 0x02, 0x2c, 0x1a, 0x7d, 0x20, 0x41, 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x6e, - 0x6f, 0x74, 0x20, 0x74, 0x69, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, 0x79, 0x74, 0x68, - 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x6e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x2e, 0x20, 0x57, 0x69, 0x6c, 0x6c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6d, 0x61, - 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x6f, 0x72, - 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x05, 0x12, 0x03, 0x17, 0x02, 0x08, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x17, 0x09, 0x27, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x00, 0x02, 0x03, 0x03, 0x12, 0x03, 0x17, 0x2a, 0x2b, 0x0a, 0x1d, 0x0a, 0x04, 0x04, - 0x00, 0x02, 0x04, 0x12, 0x03, 0x19, 0x02, 0x1c, 0x1a, 0x10, 0x20, 0x55, 0x6e, 0x69, 0x78, 0x20, - 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, - 0x02, 0x04, 0x05, 0x12, 0x03, 0x19, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x04, - 0x01, 0x12, 0x03, 0x19, 0x09, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x04, 0x03, 0x12, - 0x03, 0x19, 0x1a, 0x1b, 0x0a, 0x31, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x05, 0x12, 0x03, 0x1b, 0x02, - 0x18, 0x1a, 0x24, 0x20, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x66, - 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, - 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x05, 0x05, - 0x12, 0x03, 0x1b, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x05, 0x01, 0x12, 0x03, - 0x1b, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x05, 0x03, 0x12, 0x03, 0x1b, 0x16, - 0x17, 0x0a, 0x65, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x06, 0x12, 0x03, 0x1d, 0x02, 0x13, 0x1a, 0x58, - 0x20, 0x41, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, - 0x28, 0x66, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x76, 0x69, 0x61, - 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, - 0x29, 0x29, 0x2e, 0x20, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x20, 0x34, 0x30, 0x39, 0x36, - 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x06, - 0x05, 0x12, 0x03, 0x1d, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x06, 0x01, 0x12, - 0x03, 0x1d, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x06, 0x03, 0x12, 0x03, 0x1d, - 0x11, 0x12, 0x0a, 0x22, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x07, 0x12, 0x03, 0x1f, 0x02, 0x15, 0x1a, - 0x15, 0x20, 0x41, 0x20, 0x30, 0x78, 0x20, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x20, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x07, 0x05, 0x12, - 0x03, 0x1f, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x07, 0x01, 0x12, 0x03, 0x1f, - 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x07, 0x03, 0x12, 0x03, 0x1f, 0x13, 0x14, - 0x0a, 0x28, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x08, 0x12, 0x03, 0x21, 0x02, 0x1c, 0x1a, 0x1b, 0x20, - 0x41, 0x20, 0x68, 0x61, 0x73, 0x68, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, - 0x02, 0x08, 0x05, 0x12, 0x03, 0x21, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x08, - 0x01, 0x12, 0x03, 0x21, 0x09, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x08, 0x03, 0x12, - 0x03, 0x21, 0x1a, 0x1b, 0x0a, 0x79, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x26, 0x00, 0x2e, 0x01, - 0x1a, 0x6d, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x20, 0x70, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, - 0x65, 0x20, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x60, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x60, 0x20, 0x69, 0x6e, 0x20, - 0x74, 0x68, 0x65, 0x0a, 0x20, 0x60, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x61, - 0x74, 0x61, 0x60, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x0a, 0x0a, - 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x26, 0x08, 0x13, 0x0a, 0x0b, 0x0a, 0x04, 0x04, - 0x01, 0x02, 0x00, 0x12, 0x03, 0x27, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, - 0x06, 0x12, 0x03, 0x27, 0x02, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, - 0x03, 0x27, 0x0c, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x27, - 0x18, 0x19, 0x0a, 0x92, 0x01, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x2a, 0x02, 0x35, + 0x74, 0x73, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x69, 0x0a, 0x18, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, + 0x5f, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, + 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x15, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x35, 0x0a, 0x16, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x6e, 0x62, 0x6f, 0x78, + 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x78, + 0x49, 0x64, 0x42, 0xc7, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, + 0x42, 0x0b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, + 0x58, 0x4d, 0x58, 0xaa, 0x02, 0x14, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x14, 0x58, 0x6d, 0x74, + 0x70, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x73, 0xe2, 0x02, 0x20, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x15, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xaf, 0x11, 0x0a, + 0x06, 0x12, 0x04, 0x01, 0x00, 0x34, 0x01, 0x0a, 0x47, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, + 0x12, 0x1a, 0x3d, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, 0x73, + 0x20, 0x61, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x75, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x6b, 0x65, 0x79, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x0a, + 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x1e, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x00, + 0x12, 0x03, 0x05, 0x00, 0x2b, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x01, 0x12, 0x03, 0x06, 0x00, 0x2a, + 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x08, 0x00, 0x43, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, + 0x12, 0x03, 0x08, 0x00, 0x43, 0x0a, 0x83, 0x01, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x0d, 0x00, + 0x22, 0x01, 0x1a, 0x77, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, + 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x60, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x60, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, + 0x00, 0x01, 0x12, 0x03, 0x0d, 0x08, 0x17, 0x0a, 0x56, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, + 0x03, 0x10, 0x02, 0x17, 0x1a, 0x49, 0x20, 0x54, 0x68, 0x65, 0x20, 0x55, 0x52, 0x4c, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, + 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x0a, 0x20, 0x4d, 0x61, + 0x79, 0x20, 0x62, 0x65, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x66, + 0x72, 0x6f, 0x6d, 0x20, 0x60, 0x70, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x60, 0x0a, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x10, 0x02, 0x08, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x10, 0x09, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x10, 0x15, 0x16, 0x0a, 0x34, 0x0a, 0x04, 0x04, 0x00, 0x02, + 0x01, 0x12, 0x03, 0x12, 0x02, 0x19, 0x1a, 0x27, 0x20, 0x54, 0x68, 0x65, 0x20, 0x31, 0x2d, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x65, 0x64, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x0a, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x12, 0x02, 0x07, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x12, 0x08, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x12, 0x17, 0x18, 0x0a, 0x45, 0x0a, 0x04, 0x04, 0x00, 0x02, + 0x02, 0x12, 0x03, 0x14, 0x02, 0x2b, 0x1a, 0x38, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x20, + 0x69, 0x6e, 0x20, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x20, + 0x73, 0x69, 0x6e, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x0a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x14, 0x02, 0x08, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x14, 0x09, 0x12, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x14, 0x15, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, + 0x02, 0x02, 0x08, 0x12, 0x03, 0x14, 0x17, 0x2a, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x00, 0x02, 0x02, + 0x08, 0x03, 0x12, 0x03, 0x14, 0x18, 0x29, 0x0a, 0x8a, 0x01, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x03, + 0x12, 0x03, 0x17, 0x02, 0x2c, 0x1a, 0x7d, 0x20, 0x41, 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, + 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x74, 0x69, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, + 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x6e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x20, 0x57, 0x69, 0x6c, 0x6c, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, + 0x20, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x05, 0x12, 0x03, 0x17, + 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x17, 0x09, 0x27, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x03, 0x12, 0x03, 0x17, 0x2a, 0x2b, 0x0a, 0x1d, + 0x0a, 0x04, 0x04, 0x00, 0x02, 0x04, 0x12, 0x03, 0x19, 0x02, 0x1c, 0x1a, 0x10, 0x20, 0x55, 0x6e, + 0x69, 0x78, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x04, 0x05, 0x12, 0x03, 0x19, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x04, 0x01, 0x12, 0x03, 0x19, 0x09, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x04, 0x03, 0x12, 0x03, 0x19, 0x1a, 0x1b, 0x0a, 0x31, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x05, 0x12, + 0x03, 0x1b, 0x02, 0x18, 0x1a, 0x24, 0x20, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, + 0x02, 0x05, 0x05, 0x12, 0x03, 0x1b, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x05, + 0x01, 0x12, 0x03, 0x1b, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x05, 0x03, 0x12, + 0x03, 0x1b, 0x16, 0x17, 0x0a, 0x65, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x06, 0x12, 0x03, 0x1d, 0x02, + 0x13, 0x1a, 0x58, 0x20, 0x41, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x73, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x20, 0x28, 0x66, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, + 0x76, 0x69, 0x61, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, + 0x66, 0x79, 0x28, 0x29, 0x29, 0x2e, 0x20, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x20, 0x34, + 0x30, 0x39, 0x36, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x06, 0x05, 0x12, 0x03, 0x1d, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x06, 0x01, 0x12, 0x03, 0x1d, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x06, 0x03, + 0x12, 0x03, 0x1d, 0x11, 0x12, 0x0a, 0x22, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x07, 0x12, 0x03, 0x1f, + 0x02, 0x15, 0x1a, 0x15, 0x20, 0x41, 0x20, 0x30, 0x78, 0x20, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x07, 0x05, 0x12, 0x03, 0x1f, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x07, 0x01, + 0x12, 0x03, 0x1f, 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x07, 0x03, 0x12, 0x03, + 0x1f, 0x13, 0x14, 0x0a, 0x28, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x08, 0x12, 0x03, 0x21, 0x02, 0x1c, + 0x1a, 0x1b, 0x20, 0x41, 0x20, 0x68, 0x61, 0x73, 0x68, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, + 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x08, 0x05, 0x12, 0x03, 0x21, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x08, 0x01, 0x12, 0x03, 0x21, 0x09, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x08, 0x03, 0x12, 0x03, 0x21, 0x1a, 0x1b, 0x0a, 0x79, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x26, + 0x00, 0x34, 0x01, 0x1a, 0x6d, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x20, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x60, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x60, 0x20, + 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x60, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, + 0x5f, 0x64, 0x61, 0x74, 0x61, 0x60, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x26, 0x08, 0x13, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x27, 0x02, 0x2e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x27, 0x02, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x00, 0x01, 0x12, 0x03, 0x27, 0x0c, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, + 0x12, 0x03, 0x27, 0x18, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x08, 0x12, 0x03, + 0x27, 0x1a, 0x2d, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x01, 0x02, 0x00, 0x08, 0x03, 0x12, 0x03, 0x27, + 0x1b, 0x2c, 0x0a, 0x92, 0x01, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x2a, 0x02, 0x49, 0x1a, 0x84, 0x01, 0x20, 0x54, 0x68, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x2c, 0x20, 0x75, 0x73, 0x65, @@ -2769,325 +2790,345 @@ pub const FILE_DESCRIPTOR_SET: &[u8] = &[ 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x06, 0x12, 0x03, 0x2a, 0x02, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x2a, 0x18, 0x30, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x2a, 0x33, - 0x34, 0x0a, 0x92, 0x01, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x03, 0x2d, 0x02, 0x18, 0x1a, - 0x84, 0x01, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x46, 0x72, - 0x61, 0x6d, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x64, 0x79, 0x20, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x73, 0x6f, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x61, 0x6e, 0x0a, 0x20, 0x68, - 0x61, 0x70, 0x70, 0x65, 0x6e, 0x20, 0x6f, 0x6e, 0x20, 0x61, 0x20, 0x62, 0x79, 0x74, 0x65, 0x2d, - 0x70, 0x65, 0x72, 0x66, 0x65, 0x63, 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x05, 0x12, - 0x03, 0x2d, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x2d, - 0x08, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x2d, 0x16, 0x17, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0xb5, 0x20, 0x0a, 0x1e, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x78, 0x6d, 0x74, - 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0x1a, 0x21, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, - 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb5, 0x01, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x31, 0x12, 0x3e, 0x0a, 0x06, 0x73, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x6d, - 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42, 0x75, 0x6e, - 0x64, 0x6c, 0x65, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x44, 0x0a, 0x09, 0x72, - 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, - 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, - 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, - 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, - 0x71, 0x0a, 0x09, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x56, 0x31, 0x12, 0x21, 0x0a, 0x0c, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, - 0x41, 0x0a, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x69, 0x70, 0x68, - 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, - 0x78, 0x74, 0x22, 0x46, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x56, 0x32, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x4e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x22, 0xdd, 0x01, 0x0a, 0x09, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x56, 0x32, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x63, - 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x21, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, - 0x78, 0x74, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x12, 0x24, - 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x48, 0x6d, 0x61, - 0x63, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0b, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x5f, 0x70, - 0x75, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x0a, 0x73, 0x68, 0x6f, - 0x75, 0x6c, 0x64, 0x50, 0x75, 0x73, 0x68, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, - 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x5f, 0x70, 0x75, 0x73, 0x68, 0x22, 0x7c, 0x0a, 0x07, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x02, 0x76, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x56, 0x31, 0x48, 0x00, 0x52, 0x02, 0x76, 0x31, 0x12, 0x32, 0x0a, 0x02, 0x76, 0x32, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x56, 0x32, 0x48, 0x00, 0x52, 0x02, 0x76, 0x32, 0x42, 0x09, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xed, 0x02, 0x0a, 0x0e, 0x44, 0x65, 0x63, - 0x6f, 0x64, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x30, 0x0a, 0x11, 0x72, - 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x10, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, - 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, - 0x07, 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, - 0x73, 0x65, 0x6e, 0x74, 0x4e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x50, 0x0a, 0x0c, 0x63, - 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2c, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, - 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, - 0x0c, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, - 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x74, - 0x65, 0x73, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, - 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0xc8, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, - 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x0c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, - 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x58, 0xaa, 0x02, 0x14, 0x58, 0x6d, 0x74, - 0x70, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0xca, 0x02, 0x14, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x20, 0x58, 0x6d, 0x74, 0x70, 0x5c, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x15, 0x58, 0x6d, - 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x73, 0x4a, 0xf2, 0x14, 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, 0x4d, 0x01, 0x0a, 0x4a, - 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x40, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x73, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, - 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, - 0x03, 0x03, 0x00, 0x1e, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x00, 0x12, 0x03, 0x05, 0x00, 0x2b, 0x0a, - 0x09, 0x0a, 0x02, 0x03, 0x01, 0x12, 0x03, 0x06, 0x00, 0x37, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x02, - 0x12, 0x03, 0x07, 0x00, 0x2b, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x09, 0x00, 0x43, 0x0a, - 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x09, 0x00, 0x43, 0x0a, 0x8c, 0x01, 0x0a, 0x02, 0x04, - 0x00, 0x12, 0x04, 0x10, 0x00, 0x14, 0x01, 0x1a, 0x72, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6e, 0x63, 0x6f, - 0x64, 0x65, 0x64, 0x20, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x20, 0x61, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, - 0x61, 0x6c, 0x73, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x64, 0x0a, 0x20, 0x61, 0x73, 0x20, 0x61, 0x73, - 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, - 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x32, 0x0c, 0x20, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x56, 0x31, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, - 0x12, 0x03, 0x10, 0x08, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x11, - 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x11, 0x02, 0x11, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x11, 0x12, 0x18, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x11, 0x1b, 0x1c, 0x0a, 0x0b, 0x0a, 0x04, - 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x12, 0x02, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, - 0x01, 0x06, 0x12, 0x03, 0x12, 0x02, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, - 0x12, 0x03, 0x12, 0x12, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, - 0x12, 0x1e, 0x1f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x13, 0x02, 0x17, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x13, 0x02, 0x08, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x13, 0x09, 0x12, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x13, 0x15, 0x16, 0x0a, 0x37, 0x0a, 0x02, 0x04, 0x01, - 0x12, 0x04, 0x17, 0x00, 0x1c, 0x01, 0x1a, 0x2b, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x20, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x65, 0x6c, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x17, 0x08, 0x11, 0x0a, - 0x33, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x19, 0x02, 0x19, 0x1a, 0x26, 0x20, 0x65, - 0x6e, 0x63, 0x61, 0x70, 0x73, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, 0x65, 0x6e, 0x63, 0x6f, - 0x64, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x56, 0x31, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x19, - 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x19, 0x08, 0x14, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x19, 0x17, 0x18, 0x0a, 0x47, - 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x1b, 0x02, 0x1c, 0x1a, 0x3a, 0x20, 0x43, 0x69, - 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, - 0x20, 0x4d, 0x55, 0x53, 0x54, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x65, 0x6e, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x06, - 0x12, 0x03, 0x1b, 0x02, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, - 0x1b, 0x0d, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x1b, 0x1a, - 0x1b, 0x0a, 0x88, 0x02, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x24, 0x00, 0x29, 0x01, 0x1a, 0xed, - 0x01, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x20, 0x63, 0x61, 0x72, 0x72, 0x69, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, - 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, - 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x0a, 0x20, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x68, - 0x6f, 0x77, 0x65, 0x76, 0x65, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, - 0x65, 0x64, 0x20, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x41, 0x45, 0x41, 0x44, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x0a, 0x20, 0x74, 0x68, - 0x75, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x61, 0x6d, - 0x70, 0x65, 0x72, 0x20, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x2e, 0x0a, 0x32, 0x0c, - 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x56, 0x32, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, - 0x04, 0x02, 0x01, 0x12, 0x03, 0x24, 0x08, 0x17, 0x0a, 0x35, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x00, - 0x12, 0x03, 0x26, 0x02, 0x18, 0x1a, 0x28, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x73, - 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x0a, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x05, 0x12, 0x03, 0x26, 0x02, 0x08, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x26, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x26, 0x16, 0x17, 0x0a, 0x2f, 0x0a, 0x04, 0x04, 0x02, 0x02, - 0x01, 0x12, 0x03, 0x28, 0x02, 0x13, 0x1a, 0x22, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, - 0x69, 0x63, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x62, - 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x20, 0x74, 0x6f, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, - 0x02, 0x01, 0x05, 0x12, 0x03, 0x28, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, - 0x01, 0x12, 0x03, 0x28, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x03, 0x12, - 0x03, 0x28, 0x11, 0x12, 0x0a, 0x4d, 0x0a, 0x02, 0x04, 0x03, 0x12, 0x04, 0x2c, 0x00, 0x37, 0x01, - 0x1a, 0x41, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, - 0x6e, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, - 0x64, 0x2e, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x03, 0x01, 0x12, 0x03, 0x2c, 0x08, 0x11, 0x0a, - 0x33, 0x0a, 0x04, 0x04, 0x03, 0x02, 0x00, 0x12, 0x03, 0x2e, 0x02, 0x19, 0x1a, 0x26, 0x20, 0x65, - 0x6e, 0x63, 0x61, 0x70, 0x73, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, 0x65, 0x6e, 0x63, 0x6f, - 0x64, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x56, 0x32, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x05, 0x12, 0x03, 0x2e, - 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x01, 0x12, 0x03, 0x2e, 0x08, 0x14, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x03, 0x12, 0x03, 0x2e, 0x17, 0x18, 0x0a, 0x46, - 0x0a, 0x04, 0x04, 0x03, 0x02, 0x01, 0x12, 0x03, 0x30, 0x02, 0x1c, 0x1a, 0x39, 0x20, 0x43, 0x69, - 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, - 0x20, 0x4d, 0x55, 0x53, 0x54, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x65, 0x6e, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x01, 0x06, 0x12, - 0x03, 0x30, 0x02, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x12, 0x03, 0x30, - 0x0d, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x01, 0x03, 0x12, 0x03, 0x30, 0x1a, 0x1b, - 0x0a, 0x5c, 0x0a, 0x04, 0x04, 0x03, 0x02, 0x02, 0x12, 0x03, 0x33, 0x02, 0x21, 0x1a, 0x4f, 0x20, - 0x48, 0x4d, 0x41, 0x43, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x2c, 0x20, - 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x4d, 0x41, 0x43, 0x20, 0x6b, 0x65, - 0x79, 0x20, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x0a, 0x20, 0x6b, 0x65, 0x79, 0x0a, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, 0x04, 0x12, 0x03, 0x33, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x03, 0x02, 0x02, 0x05, 0x12, 0x03, 0x33, 0x0b, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, - 0x02, 0x02, 0x01, 0x12, 0x03, 0x33, 0x11, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, - 0x03, 0x12, 0x03, 0x33, 0x1f, 0x20, 0x0a, 0x5f, 0x0a, 0x04, 0x04, 0x03, 0x02, 0x03, 0x12, 0x03, - 0x36, 0x02, 0x20, 0x1a, 0x52, 0x20, 0x46, 0x6c, 0x61, 0x67, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x77, 0x68, 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, - 0x20, 0x62, 0x65, 0x20, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, - 0x61, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x03, 0x04, - 0x12, 0x03, 0x36, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x03, 0x05, 0x12, 0x03, - 0x36, 0x0b, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x03, 0x01, 0x12, 0x03, 0x36, 0x10, - 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x03, 0x03, 0x12, 0x03, 0x36, 0x1e, 0x1f, 0x0a, - 0x1f, 0x0a, 0x02, 0x04, 0x04, 0x12, 0x04, 0x3a, 0x00, 0x3f, 0x01, 0x1a, 0x13, 0x20, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x0a, - 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x04, 0x01, 0x12, 0x03, 0x3a, 0x08, 0x0f, 0x0a, 0x0c, 0x0a, 0x04, - 0x04, 0x04, 0x08, 0x00, 0x12, 0x04, 0x3b, 0x02, 0x3e, 0x03, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, - 0x08, 0x00, 0x01, 0x12, 0x03, 0x3b, 0x08, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x00, - 0x12, 0x03, 0x3c, 0x04, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x06, 0x12, 0x03, - 0x3c, 0x04, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x01, 0x12, 0x03, 0x3c, 0x0e, - 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x03, 0x12, 0x03, 0x3c, 0x13, 0x14, 0x0a, - 0x0b, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x01, 0x12, 0x03, 0x3d, 0x04, 0x15, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x04, 0x02, 0x01, 0x06, 0x12, 0x03, 0x3d, 0x04, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, - 0x02, 0x01, 0x01, 0x12, 0x03, 0x3d, 0x0e, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, - 0x03, 0x12, 0x03, 0x3d, 0x13, 0x14, 0x0a, 0xae, 0x01, 0x0a, 0x02, 0x04, 0x05, 0x12, 0x04, 0x44, - 0x00, 0x4d, 0x01, 0x1a, 0xa1, 0x01, 0x20, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x73, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x0a, 0x20, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, - 0x6f, 0x74, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x0a, 0x20, 0x6d, - 0x61, 0x79, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x05, 0x01, 0x12, 0x03, - 0x44, 0x08, 0x16, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x00, 0x12, 0x03, 0x45, 0x02, 0x10, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x05, 0x12, 0x03, 0x45, 0x02, 0x08, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x01, 0x12, 0x03, 0x45, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x05, 0x02, 0x00, 0x03, 0x12, 0x03, 0x45, 0x0e, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, - 0x02, 0x01, 0x12, 0x03, 0x46, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x05, - 0x12, 0x03, 0x46, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x01, 0x12, 0x03, - 0x46, 0x09, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x03, 0x12, 0x03, 0x46, 0x1b, - 0x1c, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x02, 0x12, 0x03, 0x47, 0x02, 0x1c, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, 0x05, 0x12, 0x03, 0x47, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x05, 0x02, 0x02, 0x01, 0x12, 0x03, 0x47, 0x09, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, - 0x02, 0x02, 0x03, 0x12, 0x03, 0x47, 0x1a, 0x1b, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x03, - 0x12, 0x03, 0x48, 0x02, 0x28, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x03, 0x04, 0x12, 0x03, - 0x48, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x03, 0x05, 0x12, 0x03, 0x48, 0x0b, - 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x03, 0x01, 0x12, 0x03, 0x48, 0x12, 0x23, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x03, 0x03, 0x12, 0x03, 0x48, 0x26, 0x27, 0x0a, 0x0b, 0x0a, - 0x04, 0x04, 0x05, 0x02, 0x04, 0x12, 0x03, 0x49, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, - 0x02, 0x04, 0x05, 0x12, 0x03, 0x49, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x04, - 0x01, 0x12, 0x03, 0x49, 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x04, 0x03, 0x12, - 0x03, 0x49, 0x13, 0x14, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x05, 0x12, 0x03, 0x4a, 0x02, - 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x05, 0x05, 0x12, 0x03, 0x4a, 0x02, 0x08, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x05, 0x01, 0x12, 0x03, 0x4a, 0x09, 0x16, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x05, 0x02, 0x05, 0x03, 0x12, 0x03, 0x4a, 0x19, 0x1a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, - 0x05, 0x02, 0x06, 0x12, 0x03, 0x4b, 0x02, 0x29, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x06, - 0x06, 0x12, 0x03, 0x4b, 0x02, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x06, 0x01, 0x12, - 0x03, 0x4b, 0x18, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x06, 0x03, 0x12, 0x03, 0x4b, - 0x27, 0x28, 0x0a, 0x2a, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x07, 0x12, 0x03, 0x4c, 0x02, 0x1a, 0x22, - 0x1d, 0x20, 0x65, 0x6e, 0x63, 0x61, 0x70, 0x73, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, 0x45, - 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x05, 0x02, 0x07, 0x05, 0x12, 0x03, 0x4c, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x05, 0x02, 0x07, 0x01, 0x12, 0x03, 0x4c, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, - 0x02, 0x07, 0x03, 0x12, 0x03, 0x4c, 0x18, 0x19, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, - 0x0a, 0xe0, 0x05, 0x0a, 0x25, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x78, 0x6d, 0x74, 0x70, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x1a, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x73, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0x69, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3e, - 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0xce, - 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x12, 0x53, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, - 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, + 0x34, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x08, 0x12, 0x03, 0x2a, 0x35, 0x48, 0x0a, + 0x0d, 0x0a, 0x06, 0x04, 0x01, 0x02, 0x01, 0x08, 0x03, 0x12, 0x03, 0x2a, 0x36, 0x47, 0x0a, 0x92, + 0x01, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x03, 0x2d, 0x02, 0x18, 0x1a, 0x84, 0x01, 0x20, + 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x64, 0x79, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x2c, 0x20, 0x73, 0x6f, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x61, 0x6e, 0x0a, 0x20, 0x68, 0x61, 0x70, 0x70, + 0x65, 0x6e, 0x20, 0x6f, 0x6e, 0x20, 0x61, 0x20, 0x62, 0x79, 0x74, 0x65, 0x2d, 0x70, 0x65, 0x72, + 0x66, 0x65, 0x63, 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x05, 0x12, 0x03, 0x2d, 0x02, + 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x2d, 0x08, 0x13, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x2d, 0x16, 0x17, 0x0a, 0x29, 0x0a, + 0x04, 0x04, 0x01, 0x02, 0x03, 0x12, 0x03, 0x2f, 0x02, 0x23, 0x1a, 0x1c, 0x20, 0x54, 0x68, 0x65, + 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, + 0x05, 0x12, 0x03, 0x2f, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x01, 0x12, + 0x03, 0x2f, 0x08, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x03, 0x12, 0x03, 0x2f, + 0x21, 0x22, 0x0a, 0x37, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x04, 0x12, 0x03, 0x31, 0x02, 0x1c, 0x1a, + 0x2a, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x64, 0x20, 0x75, 0x73, 0x65, + 0x64, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x04, 0x05, 0x12, 0x03, 0x31, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x04, 0x01, 0x12, 0x03, 0x31, 0x08, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x03, + 0x12, 0x03, 0x31, 0x1a, 0x1b, 0x0a, 0x3d, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x05, 0x12, 0x03, 0x33, + 0x02, 0x16, 0x1a, 0x30, 0x20, 0x54, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x20, 0x69, + 0x64, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x69, + 0x67, 0x6e, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x05, 0x12, 0x03, 0x33, + 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x01, 0x12, 0x03, 0x33, 0x09, 0x11, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x03, 0x12, 0x03, 0x33, 0x14, 0x15, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0xb5, 0x20, 0x0a, 0x1e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, - 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x58, 0xaa, 0x02, 0x14, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x14, - 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x20, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x15, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, - 0xb9, 0x02, 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, 0x0e, 0x01, 0x0a, 0x43, 0x0a, 0x01, 0x0c, 0x12, - 0x03, 0x01, 0x00, 0x12, 0x1a, 0x39, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x73, 0x74, - 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x67, 0x6e, - 0x65, 0x64, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x73, 0x0a, 0x0a, - 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x1e, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x00, 0x12, - 0x03, 0x05, 0x00, 0x2a, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x07, 0x00, 0x43, 0x0a, 0x09, - 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x07, 0x00, 0x43, 0x0a, 0x46, 0x0a, 0x02, 0x04, 0x00, 0x12, - 0x04, 0x0b, 0x00, 0x0e, 0x01, 0x1a, 0x3a, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x61, - 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, - 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, - 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x0b, 0x08, 0x15, 0x0a, 0x0b, 0x0a, - 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0c, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, - 0x02, 0x00, 0x05, 0x12, 0x03, 0x0c, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, - 0x01, 0x12, 0x03, 0x0c, 0x08, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, - 0x03, 0x0c, 0x12, 0x13, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x0d, 0x02, - 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x06, 0x12, 0x03, 0x0d, 0x02, 0x0b, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x0d, 0x0c, 0x15, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x0d, 0x18, 0x19, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x1a, 0x21, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0x2f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x21, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb5, 0x01, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x31, 0x12, 0x3e, 0x0a, 0x06, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x6d, 0x74, 0x70, + 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x73, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x44, 0x0a, 0x09, 0x72, 0x65, 0x63, + 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, + 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, + 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x71, 0x0a, + 0x09, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x56, 0x31, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, + 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x74, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, + 0x22, 0x46, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x56, 0x32, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6e, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x4e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x22, 0xdd, 0x01, 0x0a, 0x09, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x56, 0x32, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x69, 0x70, + 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, + 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x12, 0x24, 0x0a, 0x0b, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x48, 0x6d, 0x61, 0x63, 0x88, + 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0b, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x5f, 0x70, 0x75, 0x73, + 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x0a, 0x73, 0x68, 0x6f, 0x75, 0x6c, + 0x64, 0x50, 0x75, 0x73, 0x68, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, 0x68, 0x6f, + 0x75, 0x6c, 0x64, 0x5f, 0x70, 0x75, 0x73, 0x68, 0x22, 0x7c, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x02, 0x76, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x56, + 0x31, 0x48, 0x00, 0x52, 0x02, 0x76, 0x31, 0x12, 0x32, 0x0a, 0x02, 0x76, 0x32, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x56, 0x32, 0x48, 0x00, 0x52, 0x02, 0x76, 0x32, 0x42, 0x09, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xed, 0x02, 0x0a, 0x0e, 0x44, 0x65, 0x63, 0x6f, 0x64, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x30, 0x0a, 0x11, 0x72, 0x65, 0x63, + 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x10, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, + 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x07, 0x73, + 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x73, 0x65, + 0x6e, 0x74, 0x4e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x50, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, + 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2c, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0c, 0x63, + 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, + 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0xc8, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x78, + 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x73, 0x42, 0x0c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, + 0x6f, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x58, 0xaa, 0x02, 0x14, 0x58, 0x6d, 0x74, 0x70, 0x2e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, + 0x02, 0x14, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x20, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, + 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x15, 0x58, 0x6d, 0x74, 0x70, + 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x73, 0x4a, 0xf2, 0x14, 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, 0x4d, 0x01, 0x0a, 0x4a, 0x0a, 0x01, + 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x40, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x73, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x72, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, + 0x00, 0x1e, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x00, 0x12, 0x03, 0x05, 0x00, 0x2b, 0x0a, 0x09, 0x0a, + 0x02, 0x03, 0x01, 0x12, 0x03, 0x06, 0x00, 0x37, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x02, 0x12, 0x03, + 0x07, 0x00, 0x2b, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x09, 0x00, 0x43, 0x0a, 0x09, 0x0a, + 0x02, 0x08, 0x0b, 0x12, 0x03, 0x09, 0x00, 0x43, 0x0a, 0x8c, 0x01, 0x0a, 0x02, 0x04, 0x00, 0x12, + 0x04, 0x10, 0x00, 0x14, 0x01, 0x1a, 0x72, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, + 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, + 0x64, 0x20, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x20, 0x61, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x61, 0x6c, + 0x73, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x64, 0x0a, 0x20, 0x61, 0x73, 0x20, 0x61, 0x73, 0x73, 0x6f, + 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x65, 0x6e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x32, 0x0c, 0x20, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x20, 0x56, 0x31, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, + 0x10, 0x08, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x11, 0x02, 0x1d, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x11, 0x02, 0x11, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x11, 0x12, 0x18, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x11, 0x1b, 0x1c, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, + 0x02, 0x01, 0x12, 0x03, 0x12, 0x02, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x06, + 0x12, 0x03, 0x12, 0x02, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, + 0x12, 0x12, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x12, 0x1e, + 0x1f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x13, 0x02, 0x17, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x13, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x13, 0x09, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, + 0x02, 0x02, 0x03, 0x12, 0x03, 0x13, 0x15, 0x16, 0x0a, 0x37, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, + 0x17, 0x00, 0x1c, 0x01, 0x1a, 0x2b, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x69, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x20, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x17, 0x08, 0x11, 0x0a, 0x33, 0x0a, + 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x19, 0x02, 0x19, 0x1a, 0x26, 0x20, 0x65, 0x6e, 0x63, + 0x61, 0x70, 0x73, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, + 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, + 0x31, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x19, 0x02, 0x07, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x19, 0x08, 0x14, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x19, 0x17, 0x18, 0x0a, 0x47, 0x0a, 0x04, + 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x1b, 0x02, 0x1c, 0x1a, 0x3a, 0x20, 0x43, 0x69, 0x70, 0x68, + 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x4d, + 0x55, 0x53, 0x54, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x65, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x06, 0x12, 0x03, + 0x1b, 0x02, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x1b, 0x0d, + 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x1b, 0x1a, 0x1b, 0x0a, + 0x88, 0x02, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x24, 0x00, 0x29, 0x01, 0x1a, 0xed, 0x01, 0x20, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x63, + 0x61, 0x72, 0x72, 0x69, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x0a, 0x20, 0x6f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x68, 0x6f, 0x77, + 0x65, 0x76, 0x65, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, + 0x20, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x41, 0x45, + 0x41, 0x44, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x75, 0x73, + 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x0a, 0x20, 0x74, 0x68, 0x75, 0x73, + 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x61, 0x6d, 0x70, 0x65, + 0x72, 0x20, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x2e, 0x0a, 0x32, 0x0c, 0x20, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x56, 0x32, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x02, + 0x01, 0x12, 0x03, 0x24, 0x08, 0x17, 0x0a, 0x35, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x00, 0x12, 0x03, + 0x26, 0x02, 0x18, 0x1a, 0x28, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x73, 0x70, 0x65, + 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x02, 0x02, 0x00, 0x05, 0x12, 0x03, 0x26, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x26, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, + 0x00, 0x03, 0x12, 0x03, 0x26, 0x16, 0x17, 0x0a, 0x2f, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, + 0x03, 0x28, 0x02, 0x13, 0x1a, 0x22, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x62, 0x65, 0x6c, + 0x6f, 0x6e, 0x67, 0x73, 0x20, 0x74, 0x6f, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, + 0x05, 0x12, 0x03, 0x28, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x01, 0x12, + 0x03, 0x28, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x28, + 0x11, 0x12, 0x0a, 0x4d, 0x0a, 0x02, 0x04, 0x03, 0x12, 0x04, 0x2c, 0x00, 0x37, 0x01, 0x1a, 0x41, + 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2e, + 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x03, 0x01, 0x12, 0x03, 0x2c, 0x08, 0x11, 0x0a, 0x33, 0x0a, + 0x04, 0x04, 0x03, 0x02, 0x00, 0x12, 0x03, 0x2e, 0x02, 0x19, 0x1a, 0x26, 0x20, 0x65, 0x6e, 0x63, + 0x61, 0x70, 0x73, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, + 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, + 0x32, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x05, 0x12, 0x03, 0x2e, 0x02, 0x07, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x01, 0x12, 0x03, 0x2e, 0x08, 0x14, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x03, 0x12, 0x03, 0x2e, 0x17, 0x18, 0x0a, 0x46, 0x0a, 0x04, + 0x04, 0x03, 0x02, 0x01, 0x12, 0x03, 0x30, 0x02, 0x1c, 0x1a, 0x39, 0x20, 0x43, 0x69, 0x70, 0x68, + 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x4d, + 0x55, 0x53, 0x54, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x65, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x01, 0x06, 0x12, 0x03, 0x30, + 0x02, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x12, 0x03, 0x30, 0x0d, 0x17, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x01, 0x03, 0x12, 0x03, 0x30, 0x1a, 0x1b, 0x0a, 0x5c, + 0x0a, 0x04, 0x04, 0x03, 0x02, 0x02, 0x12, 0x03, 0x33, 0x02, 0x21, 0x1a, 0x4f, 0x20, 0x48, 0x4d, + 0x41, 0x43, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x2c, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x4d, 0x41, 0x43, 0x20, 0x6b, 0x65, 0x79, 0x20, + 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x0a, 0x20, 0x6b, 0x65, 0x79, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x03, 0x02, 0x02, 0x04, 0x12, 0x03, 0x33, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, + 0x02, 0x02, 0x05, 0x12, 0x03, 0x33, 0x0b, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, + 0x01, 0x12, 0x03, 0x33, 0x11, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, 0x03, 0x12, + 0x03, 0x33, 0x1f, 0x20, 0x0a, 0x5f, 0x0a, 0x04, 0x04, 0x03, 0x02, 0x03, 0x12, 0x03, 0x36, 0x02, + 0x20, 0x1a, 0x52, 0x20, 0x46, 0x6c, 0x61, 0x67, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x20, 0x77, 0x68, 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, + 0x65, 0x20, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x03, 0x04, 0x12, 0x03, + 0x36, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x03, 0x05, 0x12, 0x03, 0x36, 0x0b, + 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x03, 0x01, 0x12, 0x03, 0x36, 0x10, 0x1b, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x03, 0x03, 0x12, 0x03, 0x36, 0x1e, 0x1f, 0x0a, 0x1f, 0x0a, + 0x02, 0x04, 0x04, 0x12, 0x04, 0x3a, 0x00, 0x3f, 0x01, 0x1a, 0x13, 0x20, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x0a, 0x0a, 0x0a, + 0x0a, 0x03, 0x04, 0x04, 0x01, 0x12, 0x03, 0x3a, 0x08, 0x0f, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x04, + 0x08, 0x00, 0x12, 0x04, 0x3b, 0x02, 0x3e, 0x03, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x08, 0x00, + 0x01, 0x12, 0x03, 0x3b, 0x08, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x00, 0x12, 0x03, + 0x3c, 0x04, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x06, 0x12, 0x03, 0x3c, 0x04, + 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x01, 0x12, 0x03, 0x3c, 0x0e, 0x10, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x03, 0x12, 0x03, 0x3c, 0x13, 0x14, 0x0a, 0x0b, 0x0a, + 0x04, 0x04, 0x04, 0x02, 0x01, 0x12, 0x03, 0x3d, 0x04, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, + 0x02, 0x01, 0x06, 0x12, 0x03, 0x3d, 0x04, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, + 0x01, 0x12, 0x03, 0x3d, 0x0e, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, 0x03, 0x12, + 0x03, 0x3d, 0x13, 0x14, 0x0a, 0xae, 0x01, 0x0a, 0x02, 0x04, 0x05, 0x12, 0x04, 0x44, 0x00, 0x4d, + 0x01, 0x1a, 0xa1, 0x01, 0x20, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x0a, 0x20, + 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x0a, 0x20, 0x6d, 0x61, 0x79, + 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x05, 0x01, 0x12, 0x03, 0x44, 0x08, + 0x16, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x00, 0x12, 0x03, 0x45, 0x02, 0x10, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x05, 0x12, 0x03, 0x45, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x05, 0x02, 0x00, 0x01, 0x12, 0x03, 0x45, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, + 0x02, 0x00, 0x03, 0x12, 0x03, 0x45, 0x0e, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x01, + 0x12, 0x03, 0x46, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x05, 0x12, 0x03, + 0x46, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x01, 0x12, 0x03, 0x46, 0x09, + 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x03, 0x12, 0x03, 0x46, 0x1b, 0x1c, 0x0a, + 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x02, 0x12, 0x03, 0x47, 0x02, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x05, 0x02, 0x02, 0x05, 0x12, 0x03, 0x47, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, + 0x02, 0x02, 0x01, 0x12, 0x03, 0x47, 0x09, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, + 0x03, 0x12, 0x03, 0x47, 0x1a, 0x1b, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x03, 0x12, 0x03, + 0x48, 0x02, 0x28, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x03, 0x04, 0x12, 0x03, 0x48, 0x02, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x03, 0x05, 0x12, 0x03, 0x48, 0x0b, 0x11, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x03, 0x01, 0x12, 0x03, 0x48, 0x12, 0x23, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x05, 0x02, 0x03, 0x03, 0x12, 0x03, 0x48, 0x26, 0x27, 0x0a, 0x0b, 0x0a, 0x04, 0x04, + 0x05, 0x02, 0x04, 0x12, 0x03, 0x49, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x04, + 0x05, 0x12, 0x03, 0x49, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x04, 0x01, 0x12, + 0x03, 0x49, 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x04, 0x03, 0x12, 0x03, 0x49, + 0x13, 0x14, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x05, 0x12, 0x03, 0x4a, 0x02, 0x1b, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x05, 0x05, 0x12, 0x03, 0x4a, 0x02, 0x08, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x05, 0x02, 0x05, 0x01, 0x12, 0x03, 0x4a, 0x09, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x05, 0x02, 0x05, 0x03, 0x12, 0x03, 0x4a, 0x19, 0x1a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, + 0x06, 0x12, 0x03, 0x4b, 0x02, 0x29, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x06, 0x06, 0x12, + 0x03, 0x4b, 0x02, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x06, 0x01, 0x12, 0x03, 0x4b, + 0x18, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x06, 0x03, 0x12, 0x03, 0x4b, 0x27, 0x28, + 0x0a, 0x2a, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x07, 0x12, 0x03, 0x4c, 0x02, 0x1a, 0x22, 0x1d, 0x20, + 0x65, 0x6e, 0x63, 0x61, 0x70, 0x73, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, 0x45, 0x6e, 0x63, + 0x6f, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x05, 0x02, 0x07, 0x05, 0x12, 0x03, 0x4c, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, + 0x02, 0x07, 0x01, 0x12, 0x03, 0x4c, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x07, + 0x03, 0x12, 0x03, 0x4c, 0x18, 0x19, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0xe0, + 0x05, 0x0a, 0x25, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x1a, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x73, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x69, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3e, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0xce, 0x01, 0x0a, + 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x12, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, + 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, + 0x03, 0x58, 0x4d, 0x58, 0xaa, 0x02, 0x14, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x14, 0x58, 0x6d, + 0x74, 0x70, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0xe2, 0x02, 0x20, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x15, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xb9, 0x02, + 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, 0x0e, 0x01, 0x0a, 0x43, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, + 0x00, 0x12, 0x1a, 0x39, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, + 0x73, 0x20, 0x61, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x73, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x75, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x73, 0x0a, 0x0a, 0x08, 0x0a, + 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x1e, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x00, 0x12, 0x03, 0x05, + 0x00, 0x2a, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x07, 0x00, 0x43, 0x0a, 0x09, 0x0a, 0x02, + 0x08, 0x0b, 0x12, 0x03, 0x07, 0x00, 0x43, 0x0a, 0x46, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x0b, + 0x00, 0x0e, 0x01, 0x1a, 0x3a, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6c, + 0x6f, 0x61, 0x64, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x0a, 0x0a, + 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x0b, 0x08, 0x15, 0x0a, 0x0b, 0x0a, 0x04, 0x04, + 0x00, 0x02, 0x00, 0x12, 0x03, 0x0c, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, + 0x05, 0x12, 0x03, 0x0c, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, + 0x03, 0x0c, 0x08, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0c, + 0x12, 0x13, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x0d, 0x02, 0x1a, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x06, 0x12, 0x03, 0x0d, 0x02, 0x0b, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x0d, 0x0c, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x0d, 0x18, 0x19, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, ]; include!("xmtp.message_contents.serde.rs"); // @@protoc_insertion_point(module) \ No newline at end of file diff --git a/xmtp_proto/src/gen/xmtp.message_contents.serde.rs b/xmtp_proto/src/gen/xmtp.message_contents.serde.rs index 279816e33..cdda606ee 100644 --- a/xmtp_proto/src/gen/xmtp.message_contents.serde.rs +++ b/xmtp_proto/src/gen/xmtp.message_contents.serde.rs @@ -2032,6 +2032,15 @@ impl serde::Serialize for FrameAction { if !self.action_body.is_empty() { len += 1; } + if !self.installation_signature.is_empty() { + len += 1; + } + if !self.installation_id.is_empty() { + len += 1; + } + if !self.inbox_id.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("xmtp.message_contents.FrameAction", len)?; if let Some(v) = self.signature.as_ref() { struct_ser.serialize_field("signature", v)?; @@ -2044,6 +2053,19 @@ impl serde::Serialize for FrameAction { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("actionBody", pbjson::private::base64::encode(&self.action_body).as_str())?; } + if !self.installation_signature.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("installationSignature", pbjson::private::base64::encode(&self.installation_signature).as_str())?; + } + if !self.installation_id.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("installationId", pbjson::private::base64::encode(&self.installation_id).as_str())?; + } + if !self.inbox_id.is_empty() { + struct_ser.serialize_field("inboxId", &self.inbox_id)?; + } struct_ser.end() } } @@ -2059,6 +2081,12 @@ impl<'de> serde::Deserialize<'de> for FrameAction { "signedPublicKeyBundle", "action_body", "actionBody", + "installation_signature", + "installationSignature", + "installation_id", + "installationId", + "inbox_id", + "inboxId", ]; #[allow(clippy::enum_variant_names)] @@ -2066,6 +2094,9 @@ impl<'de> serde::Deserialize<'de> for FrameAction { Signature, SignedPublicKeyBundle, ActionBody, + InstallationSignature, + InstallationId, + InboxId, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -2090,6 +2121,9 @@ impl<'de> serde::Deserialize<'de> for FrameAction { "signature" => Ok(GeneratedField::Signature), "signedPublicKeyBundle" | "signed_public_key_bundle" => Ok(GeneratedField::SignedPublicKeyBundle), "actionBody" | "action_body" => Ok(GeneratedField::ActionBody), + "installationSignature" | "installation_signature" => Ok(GeneratedField::InstallationSignature), + "installationId" | "installation_id" => Ok(GeneratedField::InstallationId), + "inboxId" | "inbox_id" => Ok(GeneratedField::InboxId), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -2112,6 +2146,9 @@ impl<'de> serde::Deserialize<'de> for FrameAction { let mut signature__ = None; let mut signed_public_key_bundle__ = None; let mut action_body__ = None; + let mut installation_signature__ = None; + let mut installation_id__ = None; + let mut inbox_id__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Signature => { @@ -2134,12 +2171,37 @@ impl<'de> serde::Deserialize<'de> for FrameAction { Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) ; } + GeneratedField::InstallationSignature => { + if installation_signature__.is_some() { + return Err(serde::de::Error::duplicate_field("installationSignature")); + } + installation_signature__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::InstallationId => { + if installation_id__.is_some() { + return Err(serde::de::Error::duplicate_field("installationId")); + } + installation_id__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::InboxId => { + if inbox_id__.is_some() { + return Err(serde::de::Error::duplicate_field("inboxId")); + } + inbox_id__ = Some(map_.next_value()?); + } } } Ok(FrameAction { signature: signature__, signed_public_key_bundle: signed_public_key_bundle__, action_body: action_body__.unwrap_or_default(), + installation_signature: installation_signature__.unwrap_or_default(), + installation_id: installation_id__.unwrap_or_default(), + inbox_id: inbox_id__.unwrap_or_default(), }) } } diff --git a/xmtp_proto/src/gen/xmtp.mls.message_contents.rs b/xmtp_proto/src/gen/xmtp.mls.message_contents.rs index 3a270b954..1193804ab 100644 --- a/xmtp_proto/src/gen/xmtp.mls.message_contents.rs +++ b/xmtp_proto/src/gen/xmtp.mls.message_contents.rs @@ -89,9 +89,9 @@ pub mod plaintext_envelope { /// Some other authorized installation sends a reply with a link to payload #[prost(message, tag="4")] DeviceSyncReply(super::super::DeviceSyncReply), - /// A streamed cosnent update + /// A serialized user preference update #[prost(message, tag="5")] - ConsentUpdate(super::super::ConsentUpdate), + UserPreferenceUpdate(super::super::UserPreferenceUpdate), } } /// Selector which declares which version of the EncodedContent this @@ -155,16 +155,11 @@ pub mod device_sync_key_type { Aes256Gcm(::prost::alloc::vec::Vec), } } -/// A streamed consent update #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct ConsentUpdate { - #[prost(enumeration="ConsentEntityType", tag="1")] - pub entity_type: i32, - #[prost(enumeration="ConsentState", tag="2")] - pub state: i32, - #[prost(string, tag="3")] - pub entity: ::prost::alloc::string::String, +pub struct UserPreferenceUpdate { + #[prost(bytes="vec", repeated, tag="1")] + pub contents: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, } /// Recognized compression algorithms /// protolint:disable ENUM_FIELD_NAMES_ZERO_VALUE_END_WITH @@ -224,67 +219,6 @@ impl DeviceSyncKind { } } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum ConsentEntityType { - Unspecified = 0, - ConversationId = 1, - InboxId = 2, - Address = 3, -} -impl ConsentEntityType { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - ConsentEntityType::Unspecified => "CONSENT_ENTITY_TYPE_UNSPECIFIED", - ConsentEntityType::ConversationId => "CONSENT_ENTITY_TYPE_CONVERSATION_ID", - ConsentEntityType::InboxId => "CONSENT_ENTITY_TYPE_INBOX_ID", - ConsentEntityType::Address => "CONSENT_ENTITY_TYPE_ADDRESS", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "CONSENT_ENTITY_TYPE_UNSPECIFIED" => Some(Self::Unspecified), - "CONSENT_ENTITY_TYPE_CONVERSATION_ID" => Some(Self::ConversationId), - "CONSENT_ENTITY_TYPE_INBOX_ID" => Some(Self::InboxId), - "CONSENT_ENTITY_TYPE_ADDRESS" => Some(Self::Address), - _ => None, - } - } -} -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum ConsentState { - Unspecified = 0, - Allowed = 1, - Denied = 2, -} -impl ConsentState { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - ConsentState::Unspecified => "CONSENT_STATE_UNSPECIFIED", - ConsentState::Allowed => "CONSENT_STATE_ALLOWED", - ConsentState::Denied => "CONSENT_STATE_DENIED", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "CONSENT_STATE_UNSPECIFIED" => Some(Self::Unspecified), - "CONSENT_STATE_ALLOWED" => Some(Self::Allowed), - "CONSENT_STATE_DENIED" => Some(Self::Denied), - _ => None, - } - } -} /// Contains a mapping of `inbox_id` -> `sequence_id` for all members of a group. /// Designed to be stored in the group context extension of the MLS group #[allow(clippy::derive_partial_eq_without_eq)] @@ -682,7 +616,7 @@ pub mod group_updated { } /// Encoded file descriptor set for the `xmtp.mls.message_contents` package pub const FILE_DESCRIPTOR_SET: &[u8] = &[ - 0x0a, 0x8b, 0x3b, 0x0a, 0x22, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x0a, 0xbc, 0x34, 0x0a, 0x22, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, @@ -720,7 +654,7 @@ pub const FILE_DESCRIPTOR_SET: &[u8] = &[ 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x42, 0x0e, 0x0a, 0x0c, - 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xd6, 0x04, 0x0a, + 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xec, 0x04, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x02, 0x76, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, @@ -735,7 +669,7 @@ pub const FILE_DESCRIPTOR_SET: &[u8] = &[ 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4b, 0x65, - 0x79, 0x1a, 0xe6, 0x02, 0x0a, 0x02, 0x56, 0x32, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x64, 0x65, 0x6d, + 0x79, 0x1a, 0xfc, 0x02, 0x0a, 0x02, 0x56, 0x32, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, @@ -751,276 +685,253 @@ pub const FILE_DESCRIPTOR_SET: &[u8] = &[ 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, - 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x51, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x65, - 0x6e, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x28, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, - 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x6f, 0x6e, - 0x73, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x69, - 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, - 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, - 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xf9, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x0e, 0x65, 0x6e, 0x63, - 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2c, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4b, 0x65, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x21, - 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, - 0x73, 0x12, 0x3d, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, - 0x22, 0x3c, 0x0a, 0x11, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4b, 0x65, - 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x65, 0x73, 0x5f, 0x32, 0x35, 0x36, - 0x5f, 0x67, 0x63, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x61, 0x65, - 0x73, 0x32, 0x35, 0x36, 0x47, 0x63, 0x6d, 0x42, 0x05, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xb5, - 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x12, 0x4d, 0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, + 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x67, 0x0a, 0x16, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, + 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x14, 0x75, 0x73, 0x65, 0x72, + 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x42, 0x0e, 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x42, 0x09, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x11, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x70, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x6b, + 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, + 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, + 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xf9, 0x01, 0x0a, 0x0f, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, + 0x53, 0x0a, 0x0e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, + 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4b, 0x65, + 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x3d, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x3d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, - 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x65, - 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2a, 0x3c, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, - 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x46, 0x4c, 0x41, 0x54, 0x45, 0x10, 0x00, 0x12, 0x14, - 0x0a, 0x10, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x47, 0x5a, - 0x49, 0x50, 0x10, 0x01, 0x2a, 0x76, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, - 0x6e, 0x63, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x1c, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, - 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x24, 0x0a, 0x20, 0x44, 0x45, 0x56, 0x49, - 0x43, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, 0x45, 0x53, - 0x53, 0x41, 0x47, 0x45, 0x5f, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, 0x12, 0x1c, - 0x0a, 0x18, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x4b, 0x49, - 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x2a, 0xa4, 0x01, 0x0a, - 0x11, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x45, 0x4e, - 0x54, 0x49, 0x54, 0x59, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x27, 0x0a, 0x23, 0x43, 0x4f, 0x4e, 0x53, 0x45, - 0x4e, 0x54, 0x5f, 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, - 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x44, 0x10, 0x01, - 0x12, 0x20, 0x0a, 0x1c, 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x45, 0x4e, 0x54, 0x49, - 0x54, 0x59, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x42, 0x4f, 0x58, 0x5f, 0x49, 0x44, - 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x45, 0x4e, - 0x54, 0x49, 0x54, 0x59, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, - 0x53, 0x10, 0x03, 0x2a, 0x62, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x53, - 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x54, - 0x41, 0x54, 0x45, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x45, 0x44, 0x10, 0x01, 0x12, 0x18, 0x0a, - 0x14, 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, - 0x45, 0x4e, 0x49, 0x45, 0x44, 0x10, 0x02, 0x42, 0xe1, 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, - 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, - 0x4d, 0xaa, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, - 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, - 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, - 0x1a, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x6c, 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0x98, 0x27, 0x0a, 0x07, - 0x12, 0x05, 0x02, 0x00, 0x88, 0x01, 0x01, 0x0a, 0x7c, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x02, 0x00, - 0x12, 0x1a, 0x72, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x73, 0x74, 0x72, - 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x0a, 0x20, 0x43, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x20, - 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x56, 0x32, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x73, 0x6f, 0x20, - 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x72, 0x65, 0x74, 0x69, 0x72, 0x65, 0x20, 0x61, 0x6c, - 0x6c, 0x20, 0x56, 0x32, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x04, 0x00, 0x22, 0x0a, - 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x06, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, - 0x03, 0x06, 0x00, 0x47, 0x0a, 0x58, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x0a, 0x00, 0x0f, 0x01, - 0x1a, 0x4c, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x49, 0x64, - 0x20, 0x69, 0x73, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, - 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, - 0x69, 0x6e, 0x20, 0x61, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x0a, 0x0a, 0x0a, - 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x08, 0x15, 0x0a, 0x34, 0x0a, 0x04, 0x04, 0x00, - 0x02, 0x00, 0x12, 0x03, 0x0b, 0x02, 0x1a, 0x22, 0x27, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x67, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x0a, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x0b, 0x02, 0x08, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0b, 0x09, 0x15, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0b, 0x18, 0x19, 0x0a, 0x1e, 0x0a, 0x04, 0x04, 0x00, - 0x02, 0x01, 0x12, 0x03, 0x0c, 0x02, 0x15, 0x22, 0x11, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x69, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, - 0x02, 0x01, 0x05, 0x12, 0x03, 0x0c, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, - 0x01, 0x12, 0x03, 0x0c, 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, - 0x03, 0x0c, 0x13, 0x14, 0x0a, 0x28, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x0d, 0x02, - 0x1b, 0x22, 0x1b, 0x20, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x0a, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x0d, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x0d, 0x09, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, - 0x02, 0x02, 0x03, 0x12, 0x03, 0x0d, 0x19, 0x1a, 0x0a, 0x28, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x03, - 0x12, 0x03, 0x0e, 0x02, 0x1b, 0x22, 0x1b, 0x20, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x20, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, - 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x05, 0x12, 0x03, 0x0e, 0x02, 0x08, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x0e, 0x09, 0x16, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x03, 0x12, 0x03, 0x0e, 0x19, 0x1a, 0x0a, 0x67, 0x0a, 0x02, - 0x05, 0x00, 0x12, 0x04, 0x13, 0x00, 0x16, 0x01, 0x1a, 0x5b, 0x20, 0x52, 0x65, 0x63, 0x6f, 0x67, - 0x6e, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x73, 0x0a, 0x20, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x6c, 0x69, 0x6e, 0x74, 0x3a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x20, - 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x53, - 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x5f, 0x45, 0x4e, 0x44, 0x5f, - 0x57, 0x49, 0x54, 0x48, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x05, 0x00, 0x01, 0x12, 0x03, 0x13, 0x05, - 0x10, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x00, 0x12, 0x03, 0x14, 0x02, 0x1a, 0x0a, 0x0c, - 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x14, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, - 0x05, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x14, 0x18, 0x19, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, - 0x02, 0x01, 0x12, 0x03, 0x15, 0x02, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x01, 0x01, - 0x12, 0x03, 0x15, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, - 0x15, 0x15, 0x16, 0x0a, 0xa3, 0x01, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x1b, 0x00, 0x29, 0x01, - 0x1a, 0x96, 0x01, 0x20, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, - 0x20, 0x69, 0x74, 0x73, 0x20, 0x74, 0x79, 0x70, 0x65, 0x0a, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x20, 0x64, - 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x65, 0x73, - 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, - 0x12, 0x03, 0x1b, 0x08, 0x16, 0x0a, 0x65, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x1e, - 0x02, 0x19, 0x1a, 0x58, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x79, 0x70, - 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x75, 0x73, 0x65, - 0x64, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x0a, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x20, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, - 0x67, 0x20, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x72, 0x79, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x1e, 0x02, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, - 0x02, 0x00, 0x01, 0x12, 0x03, 0x1e, 0x10, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, - 0x03, 0x12, 0x03, 0x1e, 0x17, 0x18, 0x0a, 0x54, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, - 0x20, 0x02, 0x25, 0x1a, 0x47, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x65, - 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, - 0x72, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x63, - 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x01, 0x02, 0x01, 0x06, 0x12, 0x03, 0x20, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, - 0x02, 0x01, 0x01, 0x12, 0x03, 0x20, 0x16, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, - 0x03, 0x12, 0x03, 0x20, 0x23, 0x24, 0x0a, 0x84, 0x01, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, - 0x03, 0x23, 0x02, 0x1f, 0x1a, 0x77, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, - 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, - 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x63, 0x61, 0x73, 0x65, 0x0a, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, - 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x01, 0x02, 0x02, 0x04, 0x12, 0x03, 0x23, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x01, 0x02, 0x02, 0x05, 0x12, 0x03, 0x23, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, - 0x02, 0x01, 0x12, 0x03, 0x23, 0x12, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, - 0x12, 0x03, 0x23, 0x1d, 0x1e, 0x0a, 0x6e, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x03, 0x12, 0x03, 0x26, - 0x02, 0x27, 0x1a, 0x61, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x63, 0x6f, - 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, - 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, - 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, - 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x62, - 0x79, 0x74, 0x65, 0x73, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x04, 0x12, 0x03, - 0x26, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x06, 0x12, 0x03, 0x26, 0x0b, - 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x01, 0x12, 0x03, 0x26, 0x17, 0x22, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x03, 0x12, 0x03, 0x26, 0x25, 0x26, 0x0a, 0x25, 0x0a, - 0x04, 0x04, 0x01, 0x02, 0x04, 0x12, 0x03, 0x28, 0x02, 0x14, 0x1a, 0x18, 0x20, 0x65, 0x6e, 0x63, - 0x6f, 0x64, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x74, 0x73, - 0x65, 0x6c, 0x66, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x05, 0x12, 0x03, 0x28, - 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x01, 0x12, 0x03, 0x28, 0x08, 0x0f, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x03, 0x12, 0x03, 0x28, 0x12, 0x13, 0x0a, 0x55, - 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x2c, 0x00, 0x4e, 0x01, 0x1a, 0x49, 0x20, 0x41, 0x20, 0x50, - 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, - 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x6d, 0x6f, 0x73, - 0x74, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x67, - 0x65, 0x74, 0x73, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, - 0x20, 0x4d, 0x4c, 0x53, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, 0x03, 0x2c, 0x08, - 0x19, 0x0a, 0x33, 0x0a, 0x04, 0x04, 0x02, 0x03, 0x00, 0x12, 0x04, 0x2e, 0x02, 0x34, 0x03, 0x1a, - 0x25, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x65, 0x6e, 0x76, - 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x00, 0x01, 0x12, - 0x03, 0x2e, 0x0a, 0x0c, 0x0a, 0x2e, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, - 0x30, 0x04, 0x16, 0x1a, 0x1f, 0x20, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, - 0x6f, 0x20, 0x62, 0x65, 0x20, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x05, 0x12, - 0x03, 0x30, 0x04, 0x09, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, - 0x03, 0x30, 0x0a, 0x11, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, - 0x03, 0x30, 0x14, 0x15, 0x0a, 0x8c, 0x01, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x00, 0x02, 0x01, 0x12, - 0x03, 0x33, 0x04, 0x1f, 0x1a, 0x7d, 0x20, 0x41, 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, - 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x65, 0x6e, 0x73, 0x75, 0x72, 0x65, - 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x64, - 0x75, 0x63, 0x65, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x68, 0x61, - 0x73, 0x68, 0x65, 0x73, 0x2e, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x62, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x2e, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, - 0x33, 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, - 0x33, 0x0b, 0x1a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, - 0x33, 0x1d, 0x1e, 0x0a, 0x33, 0x0a, 0x04, 0x04, 0x02, 0x03, 0x01, 0x12, 0x04, 0x37, 0x02, 0x46, - 0x03, 0x1a, 0x25, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x32, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x65, - 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x01, - 0x01, 0x12, 0x03, 0x37, 0x0a, 0x0c, 0x0a, 0x8c, 0x01, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, - 0x00, 0x12, 0x03, 0x3a, 0x04, 0x1f, 0x1a, 0x7d, 0x20, 0x41, 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, - 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, 0x61, 0x6e, - 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x65, 0x6e, 0x73, 0x75, - 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, - 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x0a, 0x20, 0x70, 0x72, - 0x6f, 0x64, 0x75, 0x63, 0x65, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x20, - 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x2e, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x62, 0x65, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x2e, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x05, - 0x12, 0x03, 0x3a, 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x01, - 0x12, 0x03, 0x3a, 0x0b, 0x1a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x03, - 0x12, 0x03, 0x3a, 0x1d, 0x1e, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x08, 0x00, 0x12, - 0x04, 0x3c, 0x04, 0x45, 0x05, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x08, 0x00, 0x01, - 0x12, 0x03, 0x3c, 0x0a, 0x16, 0x0a, 0x2e, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x12, - 0x03, 0x3e, 0x06, 0x18, 0x1a, 0x1f, 0x20, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, - 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x05, - 0x12, 0x03, 0x3e, 0x06, 0x0b, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x01, - 0x12, 0x03, 0x3e, 0x0c, 0x13, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x03, - 0x12, 0x03, 0x3e, 0x16, 0x17, 0x0a, 0x42, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x02, 0x12, - 0x03, 0x40, 0x06, 0x30, 0x1a, 0x33, 0x20, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, - 0x20, 0x73, 0x65, 0x6e, 0x64, 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, 0x73, 0x79, 0x6e, 0x63, + 0x73, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4b, 0x69, 0x6e, 0x64, + 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x3c, 0x0a, 0x11, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x79, 0x6e, 0x63, 0x4b, 0x65, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, + 0x65, 0x73, 0x5f, 0x32, 0x35, 0x36, 0x5f, 0x67, 0x63, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x48, 0x00, 0x52, 0x09, 0x61, 0x65, 0x73, 0x32, 0x35, 0x36, 0x47, 0x63, 0x6d, 0x42, 0x05, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x22, 0x32, 0x0a, 0x14, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x08, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2a, 0x3c, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, + 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x50, 0x52, + 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x46, 0x4c, 0x41, 0x54, 0x45, 0x10, 0x00, + 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, + 0x47, 0x5a, 0x49, 0x50, 0x10, 0x01, 0x2a, 0x76, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x79, 0x6e, 0x63, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x1c, 0x44, 0x45, 0x56, 0x49, + 0x43, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x24, 0x0a, 0x20, 0x44, 0x45, + 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, + 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, + 0x12, 0x1c, 0x0a, 0x18, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x5f, + 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x42, 0xe1, + 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, + 0x42, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, + 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x6c, + 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x4d, 0xaa, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x2e, + 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, + 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1a, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x6c, + 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0x4a, 0xc2, 0x23, 0x0a, 0x06, 0x12, 0x04, 0x02, 0x00, 0x78, 0x01, 0x0a, 0x7c, 0x0a, + 0x01, 0x0c, 0x12, 0x03, 0x02, 0x00, 0x12, 0x1a, 0x72, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, + 0x6e, 0x67, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x0a, 0x20, 0x43, + 0x6f, 0x70, 0x69, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x56, 0x32, 0x20, 0x63, 0x6f, + 0x64, 0x65, 0x20, 0x73, 0x6f, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x65, 0x20, 0x63, 0x61, + 0x6e, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x72, 0x65, 0x74, + 0x69, 0x72, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x56, 0x32, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, + 0x12, 0x03, 0x04, 0x00, 0x22, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x06, 0x00, 0x47, 0x0a, + 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x06, 0x00, 0x47, 0x0a, 0x58, 0x0a, 0x02, 0x04, 0x00, + 0x12, 0x04, 0x0a, 0x00, 0x0f, 0x01, 0x1a, 0x4c, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x49, 0x64, 0x20, 0x69, 0x73, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, + 0x6f, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x2e, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x08, 0x15, + 0x0a, 0x34, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0b, 0x02, 0x1a, 0x22, 0x27, 0x20, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x67, 0x6f, 0x76, 0x65, 0x72, 0x6e, + 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, + 0x03, 0x0b, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0b, + 0x09, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0b, 0x18, 0x19, + 0x0a, 0x1e, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x0c, 0x02, 0x15, 0x22, 0x11, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x0a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x0c, 0x02, 0x08, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x0c, 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x0c, 0x13, 0x14, 0x0a, 0x28, 0x0a, 0x04, 0x04, 0x00, + 0x02, 0x02, 0x12, 0x03, 0x0d, 0x02, 0x1b, 0x22, 0x1b, 0x20, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x20, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x0d, + 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x0d, 0x09, 0x16, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x0d, 0x19, 0x1a, 0x0a, 0x28, + 0x0a, 0x04, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x0e, 0x02, 0x1b, 0x22, 0x1b, 0x20, 0x6d, 0x69, + 0x6e, 0x6f, 0x72, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, + 0x05, 0x12, 0x03, 0x0e, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, + 0x03, 0x0e, 0x09, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x03, 0x12, 0x03, 0x0e, + 0x19, 0x1a, 0x0a, 0x67, 0x0a, 0x02, 0x05, 0x00, 0x12, 0x04, 0x13, 0x00, 0x16, 0x01, 0x1a, 0x5b, + 0x20, 0x52, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6d, 0x70, + 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, + 0x6d, 0x73, 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6c, 0x69, 0x6e, 0x74, 0x3a, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, + 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x56, 0x41, 0x4c, 0x55, + 0x45, 0x5f, 0x45, 0x4e, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x05, + 0x00, 0x01, 0x12, 0x03, 0x13, 0x05, 0x10, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x00, 0x12, + 0x03, 0x14, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x14, + 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x14, 0x18, 0x19, + 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x01, 0x12, 0x03, 0x15, 0x02, 0x17, 0x0a, 0x0c, 0x0a, + 0x05, 0x05, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x15, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x05, + 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x15, 0x15, 0x16, 0x0a, 0xa3, 0x01, 0x0a, 0x02, 0x04, 0x01, + 0x12, 0x04, 0x1b, 0x00, 0x29, 0x01, 0x1a, 0x96, 0x01, 0x20, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, + 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x74, 0x73, 0x20, 0x74, 0x79, 0x70, 0x65, 0x0a, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x20, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x72, + 0x72, 0x65, 0x63, 0x74, 0x20, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, + 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x1b, 0x08, 0x16, 0x0a, 0x65, 0x0a, 0x04, 0x04, + 0x01, 0x02, 0x00, 0x12, 0x03, 0x1e, 0x02, 0x19, 0x1a, 0x58, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x0a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x20, 0x64, + 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x72, + 0x79, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x1e, 0x02, 0x0f, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x1e, 0x10, 0x14, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x1e, 0x17, 0x18, 0x0a, 0x54, 0x0a, 0x04, + 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x20, 0x02, 0x25, 0x1a, 0x47, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x64, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x64, + 0x65, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x06, 0x12, 0x03, 0x20, 0x02, 0x15, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x20, 0x16, 0x20, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x20, 0x23, 0x24, 0x0a, 0x84, 0x01, 0x0a, + 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x03, 0x23, 0x02, 0x1f, 0x1a, 0x77, 0x20, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x20, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, + 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x63, 0x61, + 0x73, 0x65, 0x0a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x63, + 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x6f, 0x72, 0x20, + 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x04, 0x12, 0x03, 0x23, 0x02, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x05, 0x12, 0x03, 0x23, 0x0b, 0x11, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x23, 0x12, 0x1a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x23, 0x1d, 0x1e, 0x0a, 0x6e, 0x0a, 0x04, 0x04, + 0x01, 0x02, 0x03, 0x12, 0x03, 0x26, 0x02, 0x27, 0x1a, 0x61, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3b, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x20, 0x75, + 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x03, 0x04, 0x12, 0x03, 0x26, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x03, 0x06, 0x12, 0x03, 0x26, 0x0b, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x01, + 0x12, 0x03, 0x26, 0x17, 0x22, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x03, 0x12, 0x03, + 0x26, 0x25, 0x26, 0x0a, 0x25, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x04, 0x12, 0x03, 0x28, 0x02, 0x14, + 0x1a, 0x18, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x20, 0x69, 0x74, 0x73, 0x65, 0x6c, 0x66, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, + 0x02, 0x04, 0x05, 0x12, 0x03, 0x28, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, + 0x01, 0x12, 0x03, 0x28, 0x08, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x03, 0x12, + 0x03, 0x28, 0x12, 0x13, 0x0a, 0x55, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x2c, 0x00, 0x4e, 0x01, + 0x1a, 0x49, 0x20, 0x41, 0x20, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, + 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x6d, 0x6f, 0x73, 0x74, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x20, + 0x74, 0x68, 0x61, 0x74, 0x20, 0x67, 0x65, 0x74, 0x73, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x4d, 0x4c, 0x53, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, + 0x02, 0x01, 0x12, 0x03, 0x2c, 0x08, 0x19, 0x0a, 0x33, 0x0a, 0x04, 0x04, 0x02, 0x03, 0x00, 0x12, + 0x04, 0x2e, 0x02, 0x34, 0x03, 0x1a, 0x25, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, + 0x31, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x65, 0x64, 0x20, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x03, 0x00, 0x01, 0x12, 0x03, 0x2e, 0x0a, 0x0c, 0x0a, 0x2e, 0x0a, 0x06, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, 0x30, 0x04, 0x16, 0x1a, 0x1f, 0x20, 0x45, 0x78, 0x70, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x45, 0x6e, 0x63, 0x6f, 0x64, + 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x30, 0x04, 0x09, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x30, 0x0a, 0x11, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x30, 0x14, 0x15, 0x0a, 0x8c, 0x01, 0x0a, 0x06, 0x04, + 0x02, 0x03, 0x00, 0x02, 0x01, 0x12, 0x03, 0x33, 0x04, 0x1f, 0x1a, 0x7d, 0x20, 0x41, 0x20, 0x75, + 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, + 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, + 0x65, 0x6e, 0x73, 0x75, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x73, 0x61, 0x6d, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x63, 0x61, 0x6e, + 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x74, 0x20, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x2e, 0x20, 0x4d, 0x61, 0x79, 0x20, + 0x62, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x33, 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x33, 0x0b, 0x1a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x33, 0x1d, 0x1e, 0x0a, 0x33, 0x0a, 0x04, 0x04, 0x02, 0x03, + 0x01, 0x12, 0x04, 0x37, 0x02, 0x46, 0x03, 0x1a, 0x25, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x32, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x65, 0x64, 0x20, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x0a, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x02, 0x03, 0x01, 0x01, 0x12, 0x03, 0x37, 0x0a, 0x0c, 0x0a, 0x8c, 0x01, 0x0a, + 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x12, 0x03, 0x3a, 0x04, 0x1f, 0x1a, 0x7d, 0x20, 0x41, + 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, + 0x6f, 0x20, 0x65, 0x6e, 0x73, 0x75, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x63, + 0x61, 0x6e, 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x20, 0x64, 0x69, 0x66, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x2e, 0x20, 0x4d, 0x61, + 0x79, 0x20, 0x62, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x20, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x02, 0x03, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x3a, 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x02, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x3a, 0x0b, 0x1a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x02, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x3a, 0x1d, 0x1e, 0x0a, 0x0e, 0x0a, 0x06, 0x04, + 0x02, 0x03, 0x01, 0x08, 0x00, 0x12, 0x04, 0x3c, 0x04, 0x45, 0x05, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x02, 0x03, 0x01, 0x08, 0x00, 0x01, 0x12, 0x03, 0x3c, 0x0a, 0x16, 0x0a, 0x2e, 0x0a, 0x06, 0x04, + 0x02, 0x03, 0x01, 0x02, 0x01, 0x12, 0x03, 0x3e, 0x06, 0x18, 0x1a, 0x1f, 0x20, 0x45, 0x78, 0x70, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x45, 0x6e, 0x63, 0x6f, + 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x02, 0x03, 0x01, 0x02, 0x01, 0x05, 0x12, 0x03, 0x3e, 0x06, 0x0b, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x02, 0x03, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x3e, 0x0c, 0x13, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x02, 0x03, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x3e, 0x16, 0x17, 0x0a, 0x42, 0x0a, 0x06, 0x04, + 0x02, 0x03, 0x01, 0x02, 0x02, 0x12, 0x03, 0x40, 0x06, 0x30, 0x1a, 0x33, 0x20, 0x49, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x73, 0x20, 0x61, 0x20, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x20, 0x73, 0x79, 0x6e, 0x63, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x0a, 0x0a, + 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x02, 0x06, 0x12, 0x03, 0x40, 0x06, 0x17, 0x0a, + 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x40, 0x18, 0x2b, 0x0a, + 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x40, 0x2e, 0x2f, 0x0a, + 0x58, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x03, 0x12, 0x03, 0x42, 0x06, 0x2c, 0x1a, 0x49, + 0x20, 0x53, 0x6f, 0x6d, 0x65, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x70, 0x6c, + 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, - 0x01, 0x02, 0x02, 0x06, 0x12, 0x03, 0x40, 0x06, 0x17, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, - 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x40, 0x18, 0x2b, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, - 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x40, 0x2e, 0x2f, 0x0a, 0x58, 0x0a, 0x06, 0x04, 0x02, 0x03, - 0x01, 0x02, 0x03, 0x12, 0x03, 0x42, 0x06, 0x2c, 0x1a, 0x49, 0x20, 0x53, 0x6f, 0x6d, 0x65, 0x20, - 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, - 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x65, - 0x6e, 0x64, 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, - 0x20, 0x61, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, - 0x61, 0x64, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x03, 0x06, 0x12, 0x03, - 0x42, 0x06, 0x15, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x03, 0x01, 0x12, 0x03, - 0x42, 0x16, 0x27, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x03, 0x03, 0x12, 0x03, - 0x42, 0x2a, 0x2b, 0x0a, 0x2a, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x04, 0x12, 0x03, 0x44, - 0x06, 0x27, 0x1a, 0x1b, 0x20, 0x41, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x64, 0x20, - 0x63, 0x6f, 0x73, 0x6e, 0x65, 0x6e, 0x74, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x0a, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x04, 0x06, 0x12, 0x03, 0x44, 0x06, 0x13, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x04, 0x01, 0x12, 0x03, 0x44, 0x14, 0x22, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x04, 0x03, 0x12, 0x03, 0x44, 0x25, 0x26, 0x0a, + 0x01, 0x02, 0x03, 0x06, 0x12, 0x03, 0x42, 0x06, 0x15, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x01, 0x02, 0x03, 0x01, 0x12, 0x03, 0x42, 0x16, 0x27, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x01, 0x02, 0x03, 0x03, 0x12, 0x03, 0x42, 0x2a, 0x2b, 0x0a, 0x34, 0x0a, 0x06, 0x04, 0x02, 0x03, + 0x01, 0x02, 0x04, 0x12, 0x03, 0x44, 0x06, 0x36, 0x1a, 0x25, 0x20, 0x41, 0x20, 0x73, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x70, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x0a, 0x0a, + 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x04, 0x06, 0x12, 0x03, 0x44, 0x06, 0x1a, 0x0a, + 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x04, 0x01, 0x12, 0x03, 0x44, 0x1b, 0x31, 0x0a, + 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x04, 0x03, 0x12, 0x03, 0x44, 0x34, 0x35, 0x0a, 0x66, 0x0a, 0x04, 0x04, 0x02, 0x08, 0x00, 0x12, 0x04, 0x4a, 0x02, 0x4d, 0x03, 0x1a, 0x58, 0x20, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x65, 0x73, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x76, 0x65, @@ -1118,904 +1029,874 @@ pub const FILE_DESCRIPTOR_SET: &[u8] = &[ 0x72, 0x02, 0x22, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x01, 0x02, 0x12, 0x03, 0x72, 0x25, 0x26, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x01, 0x02, 0x02, 0x12, 0x03, 0x73, 0x02, 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x73, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, - 0x05, 0x01, 0x02, 0x02, 0x02, 0x12, 0x03, 0x73, 0x1d, 0x1e, 0x0a, 0x27, 0x0a, 0x02, 0x04, 0x06, - 0x12, 0x04, 0x77, 0x00, 0x7b, 0x01, 0x1a, 0x1b, 0x20, 0x41, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x06, 0x01, 0x12, 0x03, 0x77, 0x08, 0x15, 0x0a, - 0x0b, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x00, 0x12, 0x03, 0x78, 0x02, 0x24, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x06, 0x02, 0x00, 0x06, 0x12, 0x03, 0x78, 0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, - 0x02, 0x00, 0x01, 0x12, 0x03, 0x78, 0x14, 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x00, - 0x03, 0x12, 0x03, 0x78, 0x22, 0x23, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x01, 0x12, 0x03, - 0x79, 0x02, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x01, 0x06, 0x12, 0x03, 0x79, 0x02, - 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x01, 0x01, 0x12, 0x03, 0x79, 0x0f, 0x14, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x01, 0x03, 0x12, 0x03, 0x79, 0x17, 0x18, 0x0a, 0x0b, 0x0a, - 0x04, 0x04, 0x06, 0x02, 0x02, 0x12, 0x03, 0x7a, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, - 0x02, 0x02, 0x05, 0x12, 0x03, 0x7a, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x02, - 0x01, 0x12, 0x03, 0x7a, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x02, 0x03, 0x12, - 0x03, 0x7a, 0x12, 0x13, 0x0a, 0x0b, 0x0a, 0x02, 0x05, 0x02, 0x12, 0x05, 0x7d, 0x00, 0x82, 0x01, - 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x05, 0x02, 0x01, 0x12, 0x03, 0x7d, 0x05, 0x16, 0x0a, 0x0b, 0x0a, - 0x04, 0x05, 0x02, 0x02, 0x00, 0x12, 0x03, 0x7e, 0x02, 0x26, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x02, - 0x02, 0x00, 0x01, 0x12, 0x03, 0x7e, 0x02, 0x21, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x02, 0x02, 0x00, - 0x02, 0x12, 0x03, 0x7e, 0x24, 0x25, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x02, 0x02, 0x01, 0x12, 0x03, - 0x7f, 0x02, 0x2a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, 0x7f, 0x02, - 0x25, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x02, 0x02, 0x01, 0x02, 0x12, 0x03, 0x7f, 0x28, 0x29, 0x0a, - 0x0c, 0x0a, 0x04, 0x05, 0x02, 0x02, 0x02, 0x12, 0x04, 0x80, 0x01, 0x02, 0x23, 0x0a, 0x0d, 0x0a, - 0x05, 0x05, 0x02, 0x02, 0x02, 0x01, 0x12, 0x04, 0x80, 0x01, 0x02, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, - 0x05, 0x02, 0x02, 0x02, 0x02, 0x12, 0x04, 0x80, 0x01, 0x21, 0x22, 0x0a, 0x0c, 0x0a, 0x04, 0x05, - 0x02, 0x02, 0x03, 0x12, 0x04, 0x81, 0x01, 0x02, 0x22, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x02, 0x02, - 0x03, 0x01, 0x12, 0x04, 0x81, 0x01, 0x02, 0x1d, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x02, 0x02, 0x03, - 0x02, 0x12, 0x04, 0x81, 0x01, 0x20, 0x21, 0x0a, 0x0c, 0x0a, 0x02, 0x05, 0x03, 0x12, 0x06, 0x84, - 0x01, 0x00, 0x88, 0x01, 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x05, 0x03, 0x01, 0x12, 0x04, 0x84, 0x01, - 0x05, 0x11, 0x0a, 0x0c, 0x0a, 0x04, 0x05, 0x03, 0x02, 0x00, 0x12, 0x04, 0x85, 0x01, 0x02, 0x20, - 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x03, 0x02, 0x00, 0x01, 0x12, 0x04, 0x85, 0x01, 0x02, 0x1b, 0x0a, - 0x0d, 0x0a, 0x05, 0x05, 0x03, 0x02, 0x00, 0x02, 0x12, 0x04, 0x85, 0x01, 0x1e, 0x1f, 0x0a, 0x0c, - 0x0a, 0x04, 0x05, 0x03, 0x02, 0x01, 0x12, 0x04, 0x86, 0x01, 0x02, 0x1c, 0x0a, 0x0d, 0x0a, 0x05, - 0x05, 0x03, 0x02, 0x01, 0x01, 0x12, 0x04, 0x86, 0x01, 0x02, 0x17, 0x0a, 0x0d, 0x0a, 0x05, 0x05, - 0x03, 0x02, 0x01, 0x02, 0x12, 0x04, 0x86, 0x01, 0x1a, 0x1b, 0x0a, 0x0c, 0x0a, 0x04, 0x05, 0x03, - 0x02, 0x02, 0x12, 0x04, 0x87, 0x01, 0x02, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x03, 0x02, 0x02, - 0x01, 0x12, 0x04, 0x87, 0x01, 0x02, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x03, 0x02, 0x02, 0x02, - 0x12, 0x04, 0x87, 0x01, 0x19, 0x1a, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0x90, - 0x06, 0x0a, 0x2b, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, - 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x0f, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x12, 0x51, 0x0a, - 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, - 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, - 0x1a, 0x3a, 0x0a, 0x0c, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0xe9, 0x01, 0x0a, - 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x14, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, - 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x4d, 0xaa, 0x02, - 0x18, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, - 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1a, 0x58, 0x6d, - 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x6c, 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xae, 0x02, 0x0a, 0x06, 0x12, 0x04, 0x01, - 0x00, 0x0c, 0x01, 0x0a, 0x1c, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x12, 0x20, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, - 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x22, 0x0a, 0x08, 0x0a, 0x01, 0x08, - 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x05, 0x00, 0x47, - 0x0a, 0xa3, 0x01, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x0a, 0x00, 0x0c, 0x01, 0x1a, 0x96, 0x01, - 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x61, 0x20, 0x6d, 0x61, 0x70, 0x70, - 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x66, 0x20, 0x60, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, - 0x60, 0x20, 0x2d, 0x3e, 0x20, 0x60, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x60, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x0a, 0x20, - 0x44, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, - 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4d, 0x4c, 0x53, 0x20, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x0a, - 0x08, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0b, 0x02, 0x22, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x0b, 0x02, 0x15, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0b, 0x16, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0b, 0x20, 0x21, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, 0x0a, 0xe5, 0x0e, 0x0a, 0x29, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x05, 0x01, 0x02, 0x02, 0x02, 0x12, 0x03, 0x73, 0x1d, 0x1e, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x06, + 0x12, 0x04, 0x76, 0x00, 0x78, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x06, 0x01, 0x12, 0x03, 0x76, + 0x08, 0x1c, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x00, 0x12, 0x03, 0x77, 0x02, 0x1e, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x00, 0x04, 0x12, 0x03, 0x77, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x06, 0x02, 0x00, 0x05, 0x12, 0x03, 0x77, 0x0b, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x06, 0x02, 0x00, 0x01, 0x12, 0x03, 0x77, 0x11, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, + 0x00, 0x03, 0x12, 0x03, 0x77, 0x1c, 0x1d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, + 0x90, 0x06, 0x0a, 0x2b, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xa6, 0x02, 0x0a, 0x0f, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x56, 0x31, 0x12, 0x58, - 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, - 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x6f, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x28, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x6e, 0x62, 0x6f, - 0x78, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x48, 0x0a, 0x0a, 0x64, 0x6d, - 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x44, 0x6d, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x09, 0x64, 0x6d, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x73, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x64, 0x6d, 0x5f, 0x6d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x73, 0x22, 0x22, 0x0a, 0x05, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x12, 0x19, 0x0a, 0x08, - 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x22, 0x97, 0x01, 0x0a, 0x09, 0x44, 0x6d, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x44, 0x0a, 0x0d, 0x64, 0x6d, 0x5f, 0x6d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x5f, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, - 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x52, 0x0b, - 0x64, 0x6d, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x6e, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x64, - 0x6d, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x74, 0x77, 0x6f, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x49, - 0x6e, 0x62, 0x6f, 0x78, 0x52, 0x0b, 0x64, 0x6d, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x54, 0x77, - 0x6f, 0x2a, 0x88, 0x01, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, - 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4f, 0x4e, - 0x56, 0x45, 0x52, 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, - 0x52, 0x4f, 0x55, 0x50, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, - 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4d, 0x10, 0x02, - 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x42, 0xe7, 0x01, 0x0a, - 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x12, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, - 0x6f, 0x2f, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x4d, 0xaa, 0x02, 0x18, 0x58, - 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, - 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0xe2, 0x02, 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, - 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1a, 0x58, 0x6d, 0x74, 0x70, - 0x3a, 0x3a, 0x4d, 0x6c, 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xb8, 0x07, 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, 0x23, - 0x01, 0x0a, 0x24, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x1a, 0x20, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x20, 0x69, 0x6d, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, - 0x22, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, - 0x0b, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x2f, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x09, 0x00, - 0x10, 0x01, 0x1a, 0x23, 0x20, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, - 0x09, 0x08, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0a, 0x02, 0x29, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x0a, 0x02, 0x12, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x13, 0x24, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0a, 0x27, 0x28, 0x0a, 0x28, 0x0a, 0x04, 0x04, 0x00, - 0x02, 0x01, 0x12, 0x03, 0x0c, 0x02, 0x25, 0x1a, 0x1b, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x77, - 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x73, - 0x6f, 0x6f, 0x6e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x0c, - 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x0c, 0x09, 0x20, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x0c, 0x23, 0x24, 0x0a, 0x0b, - 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x0d, 0x02, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x0d, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, - 0x02, 0x01, 0x12, 0x03, 0x0d, 0x09, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x03, - 0x12, 0x03, 0x0d, 0x1c, 0x1d, 0x0a, 0x3e, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x0f, - 0x02, 0x24, 0x1a, 0x31, 0x20, 0x53, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x20, 0x62, 0x65, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x44, 0x4d, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x04, 0x12, 0x03, - 0x0f, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x06, 0x12, 0x03, 0x0f, 0x0b, - 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x0f, 0x15, 0x1f, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x03, 0x12, 0x03, 0x0f, 0x22, 0x23, 0x0a, 0x2e, 0x0a, - 0x02, 0x05, 0x00, 0x12, 0x04, 0x13, 0x00, 0x18, 0x01, 0x1a, 0x22, 0x20, 0x44, 0x65, 0x66, 0x69, - 0x6e, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, - 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x0a, 0x0a, 0x0a, - 0x03, 0x05, 0x00, 0x01, 0x12, 0x03, 0x13, 0x05, 0x15, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, - 0x00, 0x12, 0x03, 0x14, 0x02, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x01, 0x12, - 0x03, 0x14, 0x02, 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x14, - 0x22, 0x23, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x01, 0x12, 0x03, 0x15, 0x02, 0x1e, 0x0a, - 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x15, 0x02, 0x19, 0x0a, 0x0c, 0x0a, - 0x05, 0x05, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x15, 0x1c, 0x1d, 0x0a, 0x0b, 0x0a, 0x04, 0x05, - 0x00, 0x02, 0x02, 0x12, 0x03, 0x16, 0x02, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x02, - 0x01, 0x12, 0x03, 0x16, 0x02, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x02, 0x02, 0x12, - 0x03, 0x16, 0x19, 0x1a, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x03, 0x12, 0x03, 0x17, 0x02, - 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x17, 0x02, 0x18, 0x0a, - 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x17, 0x1b, 0x1c, 0x0a, 0x28, 0x0a, - 0x02, 0x04, 0x01, 0x12, 0x04, 0x1b, 0x00, 0x1d, 0x01, 0x1a, 0x1c, 0x20, 0x57, 0x72, 0x61, 0x70, - 0x70, 0x65, 0x72, 0x20, 0x61, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x61, 0x6e, 0x20, 0x49, 0x6e, - 0x62, 0x6f, 0x78, 0x20, 0x49, 0x64, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, - 0x1b, 0x08, 0x0d, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x1c, 0x02, 0x16, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x1c, 0x02, 0x08, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x1c, 0x09, 0x11, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x1c, 0x14, 0x15, 0x0a, 0x2b, 0x0a, 0x02, 0x04, 0x02, - 0x12, 0x04, 0x20, 0x00, 0x23, 0x01, 0x1a, 0x1f, 0x20, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x74, 0x65, - 0x72, 0x20, 0x68, 0x65, 0x72, 0x65, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, 0x03, - 0x20, 0x08, 0x11, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x00, 0x12, 0x03, 0x21, 0x02, 0x1a, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x06, 0x12, 0x03, 0x21, 0x02, 0x07, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x21, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x21, 0x18, 0x19, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, - 0x02, 0x01, 0x12, 0x03, 0x22, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x06, - 0x12, 0x03, 0x22, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, - 0x22, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x22, 0x18, - 0x19, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0x92, 0x0a, 0x0a, 0x31, 0x6d, 0x6c, - 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x19, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xcb, 0x02, 0x0a, 0x16, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x56, 0x31, 0x12, 0x61, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x78, 0x6d, 0x74, 0x70, - 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x75, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x56, 0x31, 0x2e, 0x41, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x61, 0x64, 0x6d, 0x69, - 0x6e, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, - 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, - 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x10, 0x73, - 0x75, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x52, 0x0e, 0x73, 0x75, 0x70, 0x65, 0x72, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x26, 0x0a, 0x07, 0x49, 0x6e, 0x62, 0x6f, - 0x78, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x73, - 0x42, 0xee, 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, + 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x0f, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x12, 0x51, + 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x37, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x2e, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0xe9, 0x01, + 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, + 0x14, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, + 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x4d, 0xaa, + 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, 0x6d, 0x74, + 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, + 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, + 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1a, 0x58, + 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x6c, 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xae, 0x02, 0x0a, 0x06, 0x12, 0x04, + 0x01, 0x00, 0x0c, 0x01, 0x0a, 0x1c, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x12, + 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, + 0x70, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x22, 0x0a, 0x08, 0x0a, 0x01, + 0x08, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x05, 0x00, + 0x47, 0x0a, 0xa3, 0x01, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x0a, 0x00, 0x0c, 0x01, 0x1a, 0x96, + 0x01, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x61, 0x20, 0x6d, 0x61, 0x70, + 0x70, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x66, 0x20, 0x60, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x69, + 0x64, 0x60, 0x20, 0x2d, 0x3e, 0x20, 0x60, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x60, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x0a, + 0x20, 0x44, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x20, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4d, 0x4c, 0x53, + 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, + 0x0a, 0x08, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0b, 0x02, 0x22, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x0b, 0x02, 0x15, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0b, 0x16, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0b, 0x20, 0x21, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, 0x0a, 0xe5, 0x0e, 0x0a, 0x29, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x19, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xa6, 0x02, 0x0a, 0x0f, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x56, 0x31, 0x12, + 0x58, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, + 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x6f, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x6e, 0x62, + 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x48, 0x0a, 0x0a, 0x64, + 0x6d, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x44, 0x6d, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x09, 0x64, 0x6d, 0x4d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x64, 0x6d, 0x5f, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x22, 0x22, 0x0a, 0x05, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x12, 0x19, 0x0a, + 0x08, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x22, 0x97, 0x01, 0x0a, 0x09, 0x44, 0x6d, 0x4d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x44, 0x0a, 0x0d, 0x64, 0x6d, 0x5f, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x5f, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, + 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x52, + 0x0b, 0x64, 0x6d, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x6e, 0x65, 0x12, 0x44, 0x0a, 0x0d, + 0x64, 0x6d, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x74, 0x77, 0x6f, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x52, 0x0b, 0x64, 0x6d, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x54, + 0x77, 0x6f, 0x2a, 0x88, 0x01, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4e, 0x56, 0x45, + 0x52, 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4f, + 0x4e, 0x56, 0x45, 0x52, 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4e, 0x56, 0x45, + 0x52, 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4d, 0x10, + 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x41, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x42, 0xe7, 0x01, + 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, + 0x12, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, + 0x67, 0x6f, 0x2f, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x4d, 0xaa, 0x02, 0x18, + 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x5c, + 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, + 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1a, 0x58, 0x6d, 0x74, + 0x70, 0x3a, 0x3a, 0x4d, 0x6c, 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xb8, 0x07, 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, + 0x23, 0x01, 0x0a, 0x24, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x1a, 0x20, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x20, 0x69, 0x6d, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, + 0x00, 0x22, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, + 0x08, 0x0b, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x2f, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x09, + 0x00, 0x10, 0x01, 0x1a, 0x23, 0x20, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, + 0x03, 0x09, 0x08, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0a, 0x02, + 0x29, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x0a, 0x02, 0x12, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x13, 0x24, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0a, 0x27, 0x28, 0x0a, 0x28, 0x0a, 0x04, 0x04, + 0x00, 0x02, 0x01, 0x12, 0x03, 0x0c, 0x02, 0x25, 0x1a, 0x1b, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, + 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, + 0x73, 0x6f, 0x6f, 0x6e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, + 0x0c, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x0c, 0x09, + 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x0c, 0x23, 0x24, 0x0a, + 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x0d, 0x02, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x0d, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, + 0x02, 0x02, 0x01, 0x12, 0x03, 0x0d, 0x09, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, + 0x03, 0x12, 0x03, 0x0d, 0x1c, 0x1d, 0x0a, 0x3e, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, + 0x0f, 0x02, 0x24, 0x1a, 0x31, 0x20, 0x53, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x6e, 0x6c, + 0x79, 0x20, 0x62, 0x65, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x44, 0x4d, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x04, 0x12, + 0x03, 0x0f, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x06, 0x12, 0x03, 0x0f, + 0x0b, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x0f, 0x15, 0x1f, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x03, 0x12, 0x03, 0x0f, 0x22, 0x23, 0x0a, 0x2e, + 0x0a, 0x02, 0x05, 0x00, 0x12, 0x04, 0x13, 0x00, 0x18, 0x01, 0x1a, 0x22, 0x20, 0x44, 0x65, 0x66, + 0x69, 0x6e, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x0a, 0x0a, + 0x0a, 0x03, 0x05, 0x00, 0x01, 0x12, 0x03, 0x13, 0x05, 0x15, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, + 0x02, 0x00, 0x12, 0x03, 0x14, 0x02, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x01, + 0x12, 0x03, 0x14, 0x02, 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, + 0x14, 0x22, 0x23, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x01, 0x12, 0x03, 0x15, 0x02, 0x1e, + 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x15, 0x02, 0x19, 0x0a, 0x0c, + 0x0a, 0x05, 0x05, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x15, 0x1c, 0x1d, 0x0a, 0x0b, 0x0a, 0x04, + 0x05, 0x00, 0x02, 0x02, 0x12, 0x03, 0x16, 0x02, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, + 0x02, 0x01, 0x12, 0x03, 0x16, 0x02, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x02, 0x02, + 0x12, 0x03, 0x16, 0x19, 0x1a, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x03, 0x12, 0x03, 0x17, + 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x17, 0x02, 0x18, + 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x17, 0x1b, 0x1c, 0x0a, 0x28, + 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x1b, 0x00, 0x1d, 0x01, 0x1a, 0x1c, 0x20, 0x57, 0x72, 0x61, + 0x70, 0x70, 0x65, 0x72, 0x20, 0x61, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x61, 0x6e, 0x20, 0x49, + 0x6e, 0x62, 0x6f, 0x78, 0x20, 0x49, 0x64, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, + 0x03, 0x1b, 0x08, 0x0d, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x1c, 0x02, + 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x1c, 0x02, 0x08, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x1c, 0x09, 0x11, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x1c, 0x14, 0x15, 0x0a, 0x2b, 0x0a, 0x02, 0x04, + 0x02, 0x12, 0x04, 0x20, 0x00, 0x23, 0x01, 0x1a, 0x1f, 0x20, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x74, + 0x65, 0x72, 0x20, 0x68, 0x65, 0x72, 0x65, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, + 0x03, 0x20, 0x08, 0x11, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x00, 0x12, 0x03, 0x21, 0x02, + 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x06, 0x12, 0x03, 0x21, 0x02, 0x07, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x21, 0x08, 0x15, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x21, 0x18, 0x19, 0x0a, 0x0b, 0x0a, 0x04, 0x04, + 0x02, 0x02, 0x01, 0x12, 0x03, 0x22, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, + 0x06, 0x12, 0x03, 0x22, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x01, 0x12, + 0x03, 0x22, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x22, + 0x18, 0x19, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0x92, 0x0a, 0x0a, 0x31, 0x6d, + 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x19, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xcb, 0x02, 0x0a, 0x16, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x56, 0x31, 0x12, 0x61, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x78, 0x6d, 0x74, + 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x75, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x56, 0x31, 0x2e, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x65, + 0x73, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x10, + 0x73, 0x75, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6c, 0x69, 0x73, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0x42, 0x19, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, - 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x6c, 0x73, - 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x4d, 0xaa, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, - 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x24, - 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1a, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x6c, 0x73, - 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x4a, 0xd2, 0x04, 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, 0x16, 0x01, 0x0a, 0x22, 0x0a, 0x01, - 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x18, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, - 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, - 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x22, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, - 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, - 0x30, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x0a, 0x00, 0x11, 0x01, 0x1a, 0x24, 0x20, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, + 0x74, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x52, 0x0e, 0x73, 0x75, 0x70, 0x65, + 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x26, 0x0a, 0x07, 0x49, 0x6e, 0x62, + 0x6f, 0x78, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, + 0x73, 0x42, 0xee, 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, + 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x42, 0x19, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, + 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x6c, + 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x4d, 0xaa, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x2e, + 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, + 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1a, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x6c, + 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0x4a, 0xd2, 0x04, 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, 0x16, 0x01, 0x0a, 0x22, 0x0a, + 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x18, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x08, 0x1e, 0x0a, 0x4a, 0x0a, - 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0c, 0x02, 0x25, 0x1a, 0x3d, 0x20, 0x4d, 0x61, 0x70, - 0x20, 0x74, 0x6f, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x76, 0x61, 0x72, 0x69, 0x6f, 0x75, - 0x73, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x73, 0x20, 0x28, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6e, 0x61, 0x6d, - 0x65, 0x2c, 0x20, 0x65, 0x74, 0x63, 0x2e, 0x29, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, - 0x00, 0x06, 0x12, 0x03, 0x0c, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, - 0x12, 0x03, 0x0c, 0x16, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, - 0x0c, 0x23, 0x24, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x0d, 0x02, 0x19, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x06, 0x12, 0x03, 0x0d, 0x02, 0x09, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x0d, 0x0a, 0x14, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x0d, 0x17, 0x18, 0x0a, 0x64, 0x0a, 0x04, 0x04, 0x00, - 0x02, 0x02, 0x12, 0x03, 0x10, 0x02, 0x1f, 0x1a, 0x57, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, - 0x72, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x20, 0x61, 0x73, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x20, 0x73, 0x75, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x0a, 0x20, 0x4f, 0x6e, - 0x6c, 0x79, 0x20, 0x73, 0x75, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x63, - 0x61, 0x6e, 0x20, 0x61, 0x64, 0x64, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x6f, 0x74, - 0x68, 0x65, 0x72, 0x20, 0x73, 0x75, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x0a, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x06, 0x12, 0x03, 0x10, 0x02, 0x09, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x10, 0x0a, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x10, 0x1d, 0x1e, 0x0a, 0x39, 0x0a, 0x02, 0x04, 0x01, - 0x12, 0x04, 0x14, 0x00, 0x16, 0x01, 0x1a, 0x2d, 0x20, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, - 0x20, 0x61, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, - 0x66, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x49, 0x6e, 0x62, 0x6f, 0x78, - 0x20, 0x49, 0x64, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x14, 0x08, - 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x15, 0x02, 0x20, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, 0x15, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x15, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, - 0x02, 0x00, 0x01, 0x12, 0x03, 0x15, 0x12, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, - 0x03, 0x12, 0x03, 0x15, 0x1e, 0x1f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0xfb, - 0x31, 0x0a, 0x2c, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x19, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x5d, 0x0a, 0x19, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x56, 0x31, 0x12, 0x40, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x6d, 0x74, 0x70, - 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x65, 0x74, 0x52, - 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x22, 0xdf, 0x05, 0x0a, 0x09, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x53, 0x65, 0x74, 0x12, 0x57, 0x0a, 0x11, 0x61, 0x64, 0x64, 0x5f, 0x6d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, - 0x0f, 0x61, 0x64, 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x12, 0x5d, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, - 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x12, 0x72, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, - 0x74, 0x0a, 0x16, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x3e, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x53, 0x65, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x14, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x5c, 0x0a, 0x10, 0x61, 0x64, 0x64, 0x5f, 0x61, 0x64, 0x6d, - 0x69, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x32, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x12, 0x62, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x22, 0x0a, 0x08, 0x0a, 0x01, 0x08, + 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x05, 0x00, 0x47, + 0x0a, 0x30, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x0a, 0x00, 0x11, 0x01, 0x1a, 0x24, 0x20, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x20, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x08, 0x1e, 0x0a, 0x4a, + 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0c, 0x02, 0x25, 0x1a, 0x3d, 0x20, 0x4d, 0x61, + 0x70, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x76, 0x61, 0x72, 0x69, 0x6f, + 0x75, 0x73, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x61, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x20, 0x28, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x2c, 0x20, 0x65, 0x74, 0x63, 0x2e, 0x29, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, + 0x02, 0x00, 0x06, 0x12, 0x03, 0x0c, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, + 0x01, 0x12, 0x03, 0x0c, 0x16, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, + 0x03, 0x0c, 0x23, 0x24, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x0d, 0x02, + 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x06, 0x12, 0x03, 0x0d, 0x02, 0x09, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x0d, 0x0a, 0x14, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x0d, 0x17, 0x18, 0x0a, 0x64, 0x0a, 0x04, 0x04, + 0x00, 0x02, 0x02, 0x12, 0x03, 0x10, 0x02, 0x1f, 0x1a, 0x57, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x6f, 0x72, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x20, 0x61, 0x73, 0x20, 0x6f, 0x6e, 0x6c, + 0x79, 0x20, 0x73, 0x75, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x0a, 0x20, 0x4f, + 0x6e, 0x6c, 0x79, 0x20, 0x73, 0x75, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x20, + 0x63, 0x61, 0x6e, 0x20, 0x61, 0x64, 0x64, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x6f, + 0x74, 0x68, 0x65, 0x72, 0x20, 0x73, 0x75, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x06, 0x12, 0x03, 0x10, 0x02, 0x09, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x10, 0x0a, 0x1a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x10, 0x1d, 0x1e, 0x0a, 0x39, 0x0a, 0x02, 0x04, + 0x01, 0x12, 0x04, 0x14, 0x00, 0x16, 0x01, 0x1a, 0x2d, 0x20, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, + 0x72, 0x20, 0x61, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, + 0x6f, 0x66, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x49, 0x6e, 0x62, 0x6f, + 0x78, 0x20, 0x49, 0x64, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x14, + 0x08, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x15, 0x02, 0x20, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, 0x15, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x15, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x15, 0x12, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x00, 0x03, 0x12, 0x03, 0x15, 0x1e, 0x1f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, + 0xfb, 0x31, 0x0a, 0x2c, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x19, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x5d, 0x0a, 0x19, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x56, 0x31, 0x12, 0x40, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x6d, 0x74, + 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x65, 0x74, + 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x22, 0xdf, 0x05, 0x0a, 0x09, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x65, 0x74, 0x12, 0x57, 0x0a, 0x11, 0x61, 0x64, 0x64, 0x5f, + 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x52, 0x0f, 0x61, 0x64, 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x12, 0x5d, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x6d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x12, 0x72, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x12, 0x74, 0x0a, 0x16, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x3e, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x53, 0x65, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x14, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x5c, 0x0a, 0x10, 0x61, 0x64, 0x64, 0x5f, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x6e, 0x0a, 0x19, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x6d, 0x74, - 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x17, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x1a, 0x72, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8a, 0x05, 0x0a, 0x10, - 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x12, 0x4c, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, - 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x42, 0x61, 0x73, 0x65, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x48, 0x00, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x5f, - 0x0a, 0x0d, 0x61, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x2e, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, - 0x00, 0x52, 0x0c, 0x61, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x5f, 0x0a, 0x0d, 0x61, 0x6e, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, - 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x2e, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x48, 0x00, 0x52, 0x0c, 0x61, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x1a, 0x57, 0x0a, 0x0c, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x47, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, - 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x1a, 0x57, 0x0a, 0x0c, 0x41, 0x6e, 0x79, - 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x08, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, + 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x12, 0x62, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x61, + 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x6e, 0x0a, 0x19, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, - 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, - 0x65, 0x73, 0x22, 0xab, 0x01, 0x0a, 0x0a, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, - 0x0a, 0x11, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, 0x4c, - 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, - 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x44, 0x45, 0x4e, 0x59, 0x10, 0x02, 0x12, 0x2d, 0x0a, 0x29, 0x42, - 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, - 0x5f, 0x49, 0x46, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x53, 0x55, 0x50, - 0x45, 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x42, 0x41, - 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, - 0x49, 0x46, 0x5f, 0x53, 0x55, 0x50, 0x45, 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x04, - 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xac, 0x05, 0x0a, 0x0e, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x52, 0x0a, 0x04, 0x62, - 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3c, 0x2e, 0x78, 0x6d, 0x74, 0x70, - 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x61, 0x73, - 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x48, 0x00, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, - 0x5d, 0x0a, 0x0d, 0x61, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, + 0x17, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x1a, 0x72, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x2e, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, - 0x52, 0x0c, 0x61, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5d, - 0x0a, 0x0d, 0x61, 0x6e, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x2e, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, - 0x0c, 0x61, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x55, 0x0a, - 0x0c, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, 0x0a, - 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x1a, 0x55, 0x0a, 0x0c, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, + 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8a, 0x05, 0x0a, + 0x10, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x12, 0x4c, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x36, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x42, 0x61, 0x73, + 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x48, 0x00, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, + 0x5f, 0x0a, 0x0d, 0x61, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, + 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x2e, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x00, 0x52, 0x0c, 0x61, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x5f, 0x0a, 0x0d, 0x61, 0x6e, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, + 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x1a, 0x57, 0x0a, 0x0c, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x47, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x1a, 0x57, 0x0a, 0x0c, 0x41, 0x6e, + 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x08, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, + 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x68, 0x69, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x69, 0x65, 0x73, 0x22, 0xab, 0x01, 0x0a, 0x0a, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, + 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x15, 0x0a, 0x11, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, + 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, + 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x44, 0x45, 0x4e, 0x59, 0x10, 0x02, 0x12, 0x2d, 0x0a, 0x29, + 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, + 0x57, 0x5f, 0x49, 0x46, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x53, 0x55, + 0x50, 0x45, 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x42, + 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, + 0x5f, 0x49, 0x46, 0x5f, 0x53, 0x55, 0x50, 0x45, 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, + 0x04, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xac, 0x05, 0x0a, 0x0e, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x52, 0x0a, 0x04, + 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3c, 0x2e, 0x78, 0x6d, 0x74, + 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x61, + 0x73, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x48, 0x00, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, + 0x12, 0x5d, 0x0a, 0x0d, 0x61, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, + 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x2e, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x00, 0x52, 0x0c, 0x61, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x5d, 0x0a, 0x0d, 0x61, 0x6e, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x22, 0xd1, 0x01, 0x0a, 0x12, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x12, 0x24, 0x0a, 0x20, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x42, - 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x4d, 0x45, 0x54, 0x41, - 0x44, 0x41, 0x54, 0x41, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, - 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x4d, 0x45, 0x54, 0x41, + 0x79, 0x2e, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, + 0x52, 0x0c, 0x61, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x55, + 0x0a, 0x0c, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, + 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x69, 0x65, 0x73, 0x1a, 0x55, 0x0a, 0x0c, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, + 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x22, 0xd1, 0x01, 0x0a, + 0x12, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x12, 0x24, 0x0a, 0x20, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x5f, + 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x4d, 0x45, 0x54, + 0x41, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, + 0x59, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x4d, 0x45, 0x54, + 0x41, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, + 0x59, 0x5f, 0x44, 0x45, 0x4e, 0x59, 0x10, 0x02, 0x12, 0x27, 0x0a, 0x23, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, - 0x5f, 0x44, 0x45, 0x4e, 0x59, 0x10, 0x02, 0x12, 0x27, 0x0a, 0x23, 0x4d, 0x45, 0x54, 0x41, 0x44, - 0x41, 0x54, 0x41, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, - 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x49, 0x46, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x03, - 0x12, 0x2d, 0x0a, 0x29, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x42, 0x41, 0x53, - 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x49, - 0x46, 0x5f, 0x53, 0x55, 0x50, 0x45, 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x04, 0x42, - 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xd4, 0x05, 0x0a, 0x17, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x12, 0x5e, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x48, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x48, 0x00, 0x52, 0x04, 0x62, - 0x61, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x0d, 0x61, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x78, 0x6d, 0x74, - 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, - 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0c, 0x61, - 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x66, 0x0a, 0x0d, 0x61, - 0x6e, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, + 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x49, 0x46, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, + 0x03, 0x12, 0x2d, 0x0a, 0x29, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x42, 0x41, + 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, + 0x49, 0x46, 0x5f, 0x53, 0x55, 0x50, 0x45, 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x04, + 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xd4, 0x05, 0x0a, 0x17, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x12, 0x5e, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x48, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x1a, 0x5e, 0x0a, 0x0c, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x1a, 0x5e, 0x0a, 0x0c, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x48, 0x00, 0x52, 0x04, + 0x62, 0x61, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x0d, 0x61, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x78, 0x6d, + 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, + 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0c, + 0x61, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x66, 0x0a, 0x0d, + 0x61, 0x6e, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x5e, 0x0a, 0x0c, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, + 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x1a, 0x5e, 0x0a, 0x0c, 0x41, 0x6e, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, + 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x27, + 0x0a, 0x23, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x42, 0x41, + 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x50, 0x45, 0x52, 0x4d, 0x49, + 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, + 0x43, 0x59, 0x5f, 0x44, 0x45, 0x4e, 0x59, 0x10, 0x01, 0x12, 0x2a, 0x0a, 0x26, 0x50, 0x45, 0x52, + 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, + 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x49, 0x46, 0x5f, 0x41, 0x44, + 0x4d, 0x49, 0x4e, 0x10, 0x02, 0x12, 0x30, 0x0a, 0x2c, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, + 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, + 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x49, 0x46, 0x5f, 0x53, 0x55, 0x50, 0x45, 0x52, 0x5f, + 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x03, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x42, + 0xea, 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x27, 0x0a, - 0x23, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x42, 0x41, 0x53, - 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, - 0x53, 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, - 0x59, 0x5f, 0x44, 0x45, 0x4e, 0x59, 0x10, 0x01, 0x12, 0x2a, 0x0a, 0x26, 0x50, 0x45, 0x52, 0x4d, - 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, - 0x49, 0x43, 0x59, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x49, 0x46, 0x5f, 0x41, 0x44, 0x4d, - 0x49, 0x4e, 0x10, 0x02, 0x12, 0x30, 0x0a, 0x2c, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, - 0x4f, 0x4e, 0x53, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, - 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x49, 0x46, 0x5f, 0x53, 0x55, 0x50, 0x45, 0x52, 0x5f, 0x41, - 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x03, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x42, 0xea, - 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, - 0x42, 0x15, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, - 0x4d, 0xaa, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, - 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, - 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, - 0x1a, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x6c, 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xe6, 0x18, 0x0a, 0x06, - 0x12, 0x04, 0x01, 0x00, 0x68, 0x01, 0x0a, 0x2e, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, - 0x1a, 0x24, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x22, - 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, - 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x30, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x09, 0x00, 0x0b, - 0x01, 0x1a, 0x24, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, - 0x09, 0x08, 0x21, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0a, 0x02, 0x19, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x0a, 0x02, 0x0b, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x0c, 0x14, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0a, 0x17, 0x18, 0x0a, 0x37, 0x0a, 0x02, 0x04, 0x01, - 0x12, 0x04, 0x0e, 0x00, 0x15, 0x01, 0x1a, 0x2b, 0x20, 0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x74, - 0x20, 0x6f, 0x66, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, - 0x74, 0x20, 0x67, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x0e, 0x08, 0x11, 0x0a, - 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x0f, 0x02, 0x29, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x0f, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, - 0x02, 0x00, 0x01, 0x12, 0x03, 0x0f, 0x13, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, - 0x03, 0x12, 0x03, 0x0f, 0x27, 0x28, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, - 0x10, 0x02, 0x2c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x06, 0x12, 0x03, 0x10, 0x02, - 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x10, 0x13, 0x27, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x10, 0x2a, 0x2b, 0x0a, 0x0b, 0x0a, - 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x03, 0x11, 0x02, 0x39, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, - 0x02, 0x02, 0x06, 0x12, 0x03, 0x11, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, - 0x01, 0x12, 0x03, 0x11, 0x1e, 0x34, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, 0x12, - 0x03, 0x11, 0x37, 0x38, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x03, 0x12, 0x03, 0x12, 0x02, - 0x2f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x06, 0x12, 0x03, 0x12, 0x02, 0x19, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x01, 0x12, 0x03, 0x12, 0x1a, 0x2a, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x01, 0x02, 0x03, 0x03, 0x12, 0x03, 0x12, 0x2d, 0x2e, 0x0a, 0x0b, 0x0a, 0x04, 0x04, - 0x01, 0x02, 0x04, 0x12, 0x03, 0x13, 0x02, 0x32, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, - 0x06, 0x12, 0x03, 0x13, 0x02, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x01, 0x12, - 0x03, 0x13, 0x1a, 0x2d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x03, 0x12, 0x03, 0x13, - 0x30, 0x31, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x05, 0x12, 0x03, 0x14, 0x02, 0x38, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x06, 0x12, 0x03, 0x14, 0x02, 0x19, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x01, 0x02, 0x05, 0x01, 0x12, 0x03, 0x14, 0x1a, 0x33, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x01, 0x02, 0x05, 0x03, 0x12, 0x03, 0x14, 0x36, 0x37, 0x0a, 0x4c, 0x0a, 0x02, 0x04, 0x02, 0x12, - 0x04, 0x18, 0x00, 0x31, 0x01, 0x1a, 0x40, 0x20, 0x41, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x67, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x73, 0x20, 0x61, 0x64, - 0x64, 0x69, 0x6e, 0x67, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, 0x03, - 0x18, 0x08, 0x18, 0x0a, 0x1b, 0x0a, 0x04, 0x04, 0x02, 0x04, 0x00, 0x12, 0x04, 0x1a, 0x02, 0x20, - 0x03, 0x1a, 0x0d, 0x20, 0x42, 0x61, 0x73, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x0a, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x04, 0x00, 0x01, 0x12, 0x03, 0x1a, 0x07, 0x11, 0x0a, 0x0d, - 0x0a, 0x06, 0x04, 0x02, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x1b, 0x04, 0x20, 0x0a, 0x0e, 0x0a, - 0x07, 0x04, 0x02, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x1b, 0x04, 0x1b, 0x0a, 0x0e, 0x0a, - 0x07, 0x04, 0x02, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x1b, 0x1e, 0x1f, 0x0a, 0x0d, 0x0a, - 0x06, 0x04, 0x02, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x1c, 0x04, 0x1a, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x02, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x1c, 0x04, 0x15, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x02, 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x1c, 0x18, 0x19, 0x0a, 0x0d, 0x0a, 0x06, - 0x04, 0x02, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x1d, 0x04, 0x19, 0x0a, 0x0e, 0x0a, 0x07, 0x04, - 0x02, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x1d, 0x04, 0x14, 0x0a, 0x0e, 0x0a, 0x07, 0x04, - 0x02, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x1d, 0x17, 0x18, 0x0a, 0x0d, 0x0a, 0x06, 0x04, - 0x02, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x1e, 0x04, 0x32, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, - 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x1e, 0x04, 0x2d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, - 0x04, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x1e, 0x30, 0x31, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, - 0x04, 0x00, 0x02, 0x04, 0x12, 0x03, 0x1f, 0x04, 0x29, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x04, - 0x00, 0x02, 0x04, 0x01, 0x12, 0x03, 0x1f, 0x04, 0x24, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x04, - 0x00, 0x02, 0x04, 0x02, 0x12, 0x03, 0x1f, 0x27, 0x28, 0x0a, 0x44, 0x0a, 0x04, 0x04, 0x02, 0x03, - 0x00, 0x12, 0x04, 0x23, 0x02, 0x25, 0x03, 0x1a, 0x36, 0x20, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, - 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x65, 0x76, - 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x00, 0x01, 0x12, 0x03, 0x23, 0x0a, 0x16, 0x0a, 0x0d, 0x0a, - 0x06, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, 0x24, 0x04, 0x2b, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x24, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x24, 0x0d, 0x1d, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x24, 0x1e, 0x26, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x24, 0x29, 0x2a, 0x0a, 0x44, 0x0a, 0x04, - 0x04, 0x02, 0x03, 0x01, 0x12, 0x04, 0x28, 0x02, 0x2a, 0x03, 0x1a, 0x36, 0x20, 0x43, 0x6f, 0x6d, - 0x62, 0x69, 0x6e, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6e, 0x79, 0x20, 0x6d, 0x75, 0x73, 0x74, - 0x20, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x72, 0x75, - 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x01, 0x01, 0x12, 0x03, 0x28, 0x0a, 0x16, - 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x12, 0x03, 0x29, 0x04, 0x2b, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, 0x29, 0x04, 0x0c, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x29, 0x0d, 0x1d, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x29, 0x1e, 0x26, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x29, 0x29, 0x2a, 0x0a, - 0x0c, 0x0a, 0x04, 0x04, 0x02, 0x08, 0x00, 0x12, 0x04, 0x2c, 0x02, 0x30, 0x03, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x02, 0x08, 0x00, 0x01, 0x12, 0x03, 0x2c, 0x08, 0x0c, 0x0a, 0x0b, 0x0a, 0x04, 0x04, - 0x02, 0x02, 0x00, 0x12, 0x03, 0x2d, 0x04, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, - 0x06, 0x12, 0x03, 0x2d, 0x04, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, - 0x03, 0x2d, 0x0f, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x2d, - 0x16, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x03, 0x2e, 0x04, 0x23, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x06, 0x12, 0x03, 0x2e, 0x04, 0x10, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, 0x2e, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x2e, 0x21, 0x22, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, - 0x02, 0x12, 0x03, 0x2f, 0x04, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x06, 0x12, - 0x03, 0x2f, 0x04, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x01, 0x12, 0x03, 0x2f, - 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x03, 0x12, 0x03, 0x2f, 0x21, 0x22, - 0x0a, 0x35, 0x0a, 0x02, 0x04, 0x03, 0x12, 0x04, 0x34, 0x00, 0x4d, 0x01, 0x1a, 0x29, 0x20, 0x41, - 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x67, 0x6f, 0x76, - 0x65, 0x72, 0x6e, 0x73, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x03, 0x01, 0x12, 0x03, - 0x34, 0x08, 0x16, 0x0a, 0x1b, 0x0a, 0x04, 0x04, 0x03, 0x04, 0x00, 0x12, 0x04, 0x36, 0x02, 0x3c, - 0x03, 0x1a, 0x0d, 0x20, 0x42, 0x61, 0x73, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x0a, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x36, 0x07, 0x19, 0x0a, 0x0d, - 0x0a, 0x06, 0x04, 0x03, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x37, 0x04, 0x29, 0x0a, 0x0e, 0x0a, - 0x07, 0x04, 0x03, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x37, 0x04, 0x24, 0x0a, 0x0e, 0x0a, - 0x07, 0x04, 0x03, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x37, 0x27, 0x28, 0x0a, 0x0d, 0x0a, - 0x06, 0x04, 0x03, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x38, 0x04, 0x23, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x03, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x38, 0x04, 0x1e, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x03, 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x38, 0x21, 0x22, 0x0a, 0x0d, 0x0a, 0x06, - 0x04, 0x03, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x39, 0x04, 0x22, 0x0a, 0x0e, 0x0a, 0x07, 0x04, - 0x03, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x39, 0x04, 0x1d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, - 0x03, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x39, 0x20, 0x21, 0x0a, 0x0d, 0x0a, 0x06, 0x04, - 0x03, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x3a, 0x04, 0x2c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, - 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x3a, 0x04, 0x27, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, - 0x04, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x3a, 0x2a, 0x2b, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x03, - 0x04, 0x00, 0x02, 0x04, 0x12, 0x03, 0x3b, 0x04, 0x32, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x04, - 0x00, 0x02, 0x04, 0x01, 0x12, 0x03, 0x3b, 0x04, 0x2d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x04, - 0x00, 0x02, 0x04, 0x02, 0x12, 0x03, 0x3b, 0x30, 0x31, 0x0a, 0x44, 0x0a, 0x04, 0x04, 0x03, 0x03, - 0x00, 0x12, 0x04, 0x3f, 0x02, 0x41, 0x03, 0x1a, 0x36, 0x20, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, - 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x65, 0x76, - 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x03, 0x00, 0x01, 0x12, 0x03, 0x3f, 0x0a, 0x16, 0x0a, 0x0d, 0x0a, - 0x06, 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, 0x40, 0x04, 0x29, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x40, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x40, 0x0d, 0x1b, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x40, 0x1c, 0x24, 0x0a, 0x0e, 0x0a, 0x07, - 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x40, 0x27, 0x28, 0x0a, 0x44, 0x0a, 0x04, - 0x04, 0x03, 0x03, 0x01, 0x12, 0x04, 0x44, 0x02, 0x46, 0x03, 0x1a, 0x36, 0x20, 0x43, 0x6f, 0x6d, - 0x62, 0x69, 0x6e, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6e, 0x79, 0x20, 0x6d, 0x75, 0x73, 0x74, - 0x20, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x72, 0x75, - 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x03, 0x01, 0x01, 0x12, 0x03, 0x44, 0x0a, 0x16, - 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x12, 0x03, 0x45, 0x04, 0x29, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, 0x45, 0x04, 0x0c, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x45, 0x0d, 0x1b, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x45, 0x1c, 0x24, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x45, 0x27, 0x28, 0x0a, - 0x0c, 0x0a, 0x04, 0x04, 0x03, 0x08, 0x00, 0x12, 0x04, 0x48, 0x02, 0x4c, 0x03, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x03, 0x08, 0x00, 0x01, 0x12, 0x03, 0x48, 0x08, 0x0c, 0x0a, 0x0b, 0x0a, 0x04, 0x04, - 0x03, 0x02, 0x00, 0x12, 0x03, 0x49, 0x04, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, - 0x06, 0x12, 0x03, 0x49, 0x04, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x01, 0x12, - 0x03, 0x49, 0x17, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x03, 0x12, 0x03, 0x49, - 0x1e, 0x1f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x03, 0x02, 0x01, 0x12, 0x03, 0x4a, 0x04, 0x23, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x01, 0x06, 0x12, 0x03, 0x4a, 0x04, 0x10, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x12, 0x03, 0x4a, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x03, 0x02, 0x01, 0x03, 0x12, 0x03, 0x4a, 0x21, 0x22, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x03, 0x02, - 0x02, 0x12, 0x03, 0x4b, 0x04, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, 0x06, 0x12, - 0x03, 0x4b, 0x04, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, 0x01, 0x12, 0x03, 0x4b, - 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, 0x03, 0x12, 0x03, 0x4b, 0x21, 0x22, - 0x0a, 0x38, 0x0a, 0x02, 0x04, 0x04, 0x12, 0x04, 0x50, 0x00, 0x68, 0x01, 0x1a, 0x2c, 0x20, 0x41, - 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x67, 0x6f, 0x76, - 0x65, 0x72, 0x6e, 0x73, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x04, - 0x01, 0x12, 0x03, 0x50, 0x08, 0x1f, 0x0a, 0x1b, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x12, 0x04, - 0x52, 0x02, 0x57, 0x03, 0x1a, 0x0d, 0x20, 0x42, 0x61, 0x73, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x04, 0x00, 0x01, 0x12, 0x03, 0x52, 0x07, - 0x1c, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x53, 0x04, 0x2c, - 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x53, 0x04, 0x27, - 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x53, 0x2a, 0x2b, - 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x54, 0x04, 0x25, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x54, 0x04, 0x20, 0x0a, - 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x54, 0x23, 0x24, 0x0a, - 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x55, 0x04, 0x2f, 0x0a, 0x0e, - 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x55, 0x04, 0x2a, 0x0a, 0x0e, - 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x55, 0x2d, 0x2e, 0x0a, 0x0d, - 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x56, 0x04, 0x35, 0x0a, 0x0e, 0x0a, - 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x56, 0x04, 0x30, 0x0a, 0x0e, 0x0a, - 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x56, 0x33, 0x34, 0x0a, 0x44, 0x0a, - 0x04, 0x04, 0x04, 0x03, 0x00, 0x12, 0x04, 0x5a, 0x02, 0x5c, 0x03, 0x1a, 0x36, 0x20, 0x43, 0x6f, + 0x73, 0x42, 0x15, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, + 0x4d, 0x4d, 0xaa, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x2e, 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, + 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xe2, 0x02, 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, + 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x1a, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, 0x4d, 0x6c, 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xe6, 0x18, 0x0a, + 0x06, 0x12, 0x04, 0x01, 0x00, 0x68, 0x01, 0x0a, 0x2e, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, + 0x12, 0x1a, 0x24, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, + 0x22, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, + 0x0b, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x30, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x09, 0x00, + 0x0b, 0x01, 0x1a, 0x24, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, + 0x03, 0x09, 0x08, 0x21, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0a, 0x02, + 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x0a, 0x02, 0x0b, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x0c, 0x14, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0a, 0x17, 0x18, 0x0a, 0x37, 0x0a, 0x02, 0x04, + 0x01, 0x12, 0x04, 0x0e, 0x00, 0x15, 0x01, 0x1a, 0x2b, 0x20, 0x54, 0x68, 0x65, 0x20, 0x73, 0x65, + 0x74, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x67, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x0e, 0x08, 0x11, + 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x0f, 0x02, 0x29, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x0f, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0f, 0x13, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x00, 0x03, 0x12, 0x03, 0x0f, 0x27, 0x28, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, + 0x03, 0x10, 0x02, 0x2c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x06, 0x12, 0x03, 0x10, + 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x10, 0x13, 0x27, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x10, 0x2a, 0x2b, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x03, 0x11, 0x02, 0x39, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x02, 0x06, 0x12, 0x03, 0x11, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x02, 0x01, 0x12, 0x03, 0x11, 0x1e, 0x34, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, + 0x12, 0x03, 0x11, 0x37, 0x38, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x03, 0x12, 0x03, 0x12, + 0x02, 0x2f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x06, 0x12, 0x03, 0x12, 0x02, 0x19, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x01, 0x12, 0x03, 0x12, 0x1a, 0x2a, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x03, 0x12, 0x03, 0x12, 0x2d, 0x2e, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x01, 0x02, 0x04, 0x12, 0x03, 0x13, 0x02, 0x32, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x04, 0x06, 0x12, 0x03, 0x13, 0x02, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x01, + 0x12, 0x03, 0x13, 0x1a, 0x2d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x03, 0x12, 0x03, + 0x13, 0x30, 0x31, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x05, 0x12, 0x03, 0x14, 0x02, 0x38, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x06, 0x12, 0x03, 0x14, 0x02, 0x19, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x01, 0x12, 0x03, 0x14, 0x1a, 0x33, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x01, 0x02, 0x05, 0x03, 0x12, 0x03, 0x14, 0x36, 0x37, 0x0a, 0x4c, 0x0a, 0x02, 0x04, 0x02, + 0x12, 0x04, 0x18, 0x00, 0x31, 0x01, 0x1a, 0x40, 0x20, 0x41, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x67, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x73, 0x20, 0x61, + 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x6d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, + 0x03, 0x18, 0x08, 0x18, 0x0a, 0x1b, 0x0a, 0x04, 0x04, 0x02, 0x04, 0x00, 0x12, 0x04, 0x1a, 0x02, + 0x20, 0x03, 0x1a, 0x0d, 0x20, 0x42, 0x61, 0x73, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x04, 0x00, 0x01, 0x12, 0x03, 0x1a, 0x07, 0x11, 0x0a, + 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x1b, 0x04, 0x20, 0x0a, 0x0e, + 0x0a, 0x07, 0x04, 0x02, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x1b, 0x04, 0x1b, 0x0a, 0x0e, + 0x0a, 0x07, 0x04, 0x02, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x1b, 0x1e, 0x1f, 0x0a, 0x0d, + 0x0a, 0x06, 0x04, 0x02, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x1c, 0x04, 0x1a, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x1c, 0x04, 0x15, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x1c, 0x18, 0x19, 0x0a, 0x0d, 0x0a, + 0x06, 0x04, 0x02, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x1d, 0x04, 0x19, 0x0a, 0x0e, 0x0a, 0x07, + 0x04, 0x02, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x1d, 0x04, 0x14, 0x0a, 0x0e, 0x0a, 0x07, + 0x04, 0x02, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x1d, 0x17, 0x18, 0x0a, 0x0d, 0x0a, 0x06, + 0x04, 0x02, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x1e, 0x04, 0x32, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x02, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x1e, 0x04, 0x2d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x02, 0x04, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x1e, 0x30, 0x31, 0x0a, 0x0d, 0x0a, 0x06, 0x04, + 0x02, 0x04, 0x00, 0x02, 0x04, 0x12, 0x03, 0x1f, 0x04, 0x29, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x04, 0x00, 0x02, 0x04, 0x01, 0x12, 0x03, 0x1f, 0x04, 0x24, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x04, 0x00, 0x02, 0x04, 0x02, 0x12, 0x03, 0x1f, 0x27, 0x28, 0x0a, 0x44, 0x0a, 0x04, 0x04, 0x02, + 0x03, 0x00, 0x12, 0x04, 0x23, 0x02, 0x25, 0x03, 0x1a, 0x36, 0x20, 0x43, 0x6f, 0x6d, 0x62, 0x69, + 0x6e, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x65, + 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x00, 0x01, 0x12, 0x03, 0x23, 0x0a, 0x16, 0x0a, 0x0d, + 0x0a, 0x06, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, 0x24, 0x04, 0x2b, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x24, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x24, 0x0d, 0x1d, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x24, 0x1e, 0x26, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x24, 0x29, 0x2a, 0x0a, 0x44, 0x0a, + 0x04, 0x04, 0x02, 0x03, 0x01, 0x12, 0x04, 0x28, 0x02, 0x2a, 0x03, 0x1a, 0x36, 0x20, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x70, - 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x75, 0x73, + 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6e, 0x79, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x72, - 0x75, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x03, 0x00, 0x01, 0x12, 0x03, 0x5a, 0x0a, - 0x16, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, 0x5b, 0x04, 0x32, - 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x5b, 0x04, 0x0c, - 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x5b, 0x0d, 0x24, - 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x5b, 0x25, 0x2d, - 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x5b, 0x30, 0x31, - 0x0a, 0x44, 0x0a, 0x04, 0x04, 0x04, 0x03, 0x01, 0x12, 0x04, 0x5f, 0x02, 0x61, 0x03, 0x1a, 0x36, - 0x20, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, - 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6e, 0x79, 0x20, - 0x6d, 0x75, 0x73, 0x74, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, - 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x03, 0x01, 0x01, 0x12, - 0x03, 0x5f, 0x0a, 0x16, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x12, 0x03, - 0x60, 0x04, 0x32, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, - 0x60, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, - 0x60, 0x0d, 0x24, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, - 0x60, 0x25, 0x2d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, - 0x60, 0x30, 0x31, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x04, 0x08, 0x00, 0x12, 0x04, 0x63, 0x02, 0x67, - 0x03, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x08, 0x00, 0x01, 0x12, 0x03, 0x63, 0x08, 0x0c, 0x0a, - 0x0b, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x00, 0x12, 0x03, 0x64, 0x04, 0x23, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x04, 0x02, 0x00, 0x06, 0x12, 0x03, 0x64, 0x04, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, - 0x02, 0x00, 0x01, 0x12, 0x03, 0x64, 0x1a, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, - 0x03, 0x12, 0x03, 0x64, 0x21, 0x22, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x01, 0x12, 0x03, - 0x65, 0x04, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, 0x06, 0x12, 0x03, 0x65, 0x04, - 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, 0x01, 0x12, 0x03, 0x65, 0x11, 0x1e, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, 0x03, 0x12, 0x03, 0x65, 0x21, 0x22, 0x0a, 0x0b, 0x0a, - 0x04, 0x04, 0x04, 0x02, 0x02, 0x12, 0x03, 0x66, 0x04, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, - 0x02, 0x02, 0x06, 0x12, 0x03, 0x66, 0x04, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x02, - 0x01, 0x12, 0x03, 0x66, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x02, 0x03, 0x12, - 0x03, 0x66, 0x21, 0x22, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0xb8, 0x1a, 0x0a, - 0x2e, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x19, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x10, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x69, 0x6e, 0x69, 0x74, 0x69, - 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x22, 0x80, 0x03, 0x0a, 0x16, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, - 0x50, 0x0a, 0x0d, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, - 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x52, 0x0c, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x65, - 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x5f, 0x72, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, - 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, - 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x5c, 0x0a, 0x13, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x52, 0x12, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x41, 0x64, 0x64, 0x65, 0x64, 0x12, 0x60, 0x0a, 0x15, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x52, 0x14, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x22, 0x9b, 0x04, 0x0a, 0x0c, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x6e, 0x69, 0x74, - 0x69, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, - 0x65, 0x64, 0x42, 0x79, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x52, 0x0a, 0x0d, 0x61, - 0x64, 0x64, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x49, 0x6e, 0x62, 0x6f, - 0x78, 0x52, 0x0c, 0x61, 0x64, 0x64, 0x65, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x12, - 0x56, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x78, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, - 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, - 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x12, 0x71, 0x0a, 0x16, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, + 0x75, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x01, 0x01, 0x12, 0x03, 0x28, 0x0a, + 0x16, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x12, 0x03, 0x29, 0x04, 0x2b, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, 0x29, 0x04, 0x0c, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x29, 0x0d, 0x1d, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x29, 0x1e, 0x26, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x29, 0x29, 0x2a, + 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x02, 0x08, 0x00, 0x12, 0x04, 0x2c, 0x02, 0x30, 0x03, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x02, 0x08, 0x00, 0x01, 0x12, 0x03, 0x2c, 0x08, 0x0c, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x02, 0x02, 0x00, 0x12, 0x03, 0x2d, 0x04, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, + 0x00, 0x06, 0x12, 0x03, 0x2d, 0x04, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, + 0x12, 0x03, 0x2d, 0x0f, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, + 0x2d, 0x16, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x03, 0x2e, 0x04, 0x23, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x06, 0x12, 0x03, 0x2e, 0x04, 0x10, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, 0x2e, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x2e, 0x21, 0x22, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, + 0x02, 0x02, 0x12, 0x03, 0x2f, 0x04, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x06, + 0x12, 0x03, 0x2f, 0x04, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x01, 0x12, 0x03, + 0x2f, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x03, 0x12, 0x03, 0x2f, 0x21, + 0x22, 0x0a, 0x35, 0x0a, 0x02, 0x04, 0x03, 0x12, 0x04, 0x34, 0x00, 0x4d, 0x01, 0x1a, 0x29, 0x20, + 0x41, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x67, 0x6f, + 0x76, 0x65, 0x72, 0x6e, 0x73, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x03, 0x01, 0x12, + 0x03, 0x34, 0x08, 0x16, 0x0a, 0x1b, 0x0a, 0x04, 0x04, 0x03, 0x04, 0x00, 0x12, 0x04, 0x36, 0x02, + 0x3c, 0x03, 0x1a, 0x0d, 0x20, 0x42, 0x61, 0x73, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x36, 0x07, 0x19, 0x0a, + 0x0d, 0x0a, 0x06, 0x04, 0x03, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x37, 0x04, 0x29, 0x0a, 0x0e, + 0x0a, 0x07, 0x04, 0x03, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x37, 0x04, 0x24, 0x0a, 0x0e, + 0x0a, 0x07, 0x04, 0x03, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x37, 0x27, 0x28, 0x0a, 0x0d, + 0x0a, 0x06, 0x04, 0x03, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x38, 0x04, 0x23, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x03, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x38, 0x04, 0x1e, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x03, 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x38, 0x21, 0x22, 0x0a, 0x0d, 0x0a, + 0x06, 0x04, 0x03, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x39, 0x04, 0x22, 0x0a, 0x0e, 0x0a, 0x07, + 0x04, 0x03, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x39, 0x04, 0x1d, 0x0a, 0x0e, 0x0a, 0x07, + 0x04, 0x03, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x39, 0x20, 0x21, 0x0a, 0x0d, 0x0a, 0x06, + 0x04, 0x03, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x3a, 0x04, 0x2c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x03, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x3a, 0x04, 0x27, 0x0a, 0x0e, 0x0a, 0x07, 0x04, + 0x03, 0x04, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x3a, 0x2a, 0x2b, 0x0a, 0x0d, 0x0a, 0x06, 0x04, + 0x03, 0x04, 0x00, 0x02, 0x04, 0x12, 0x03, 0x3b, 0x04, 0x32, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, + 0x04, 0x00, 0x02, 0x04, 0x01, 0x12, 0x03, 0x3b, 0x04, 0x2d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, + 0x04, 0x00, 0x02, 0x04, 0x02, 0x12, 0x03, 0x3b, 0x30, 0x31, 0x0a, 0x44, 0x0a, 0x04, 0x04, 0x03, + 0x03, 0x00, 0x12, 0x04, 0x3f, 0x02, 0x41, 0x03, 0x1a, 0x36, 0x20, 0x43, 0x6f, 0x6d, 0x62, 0x69, + 0x6e, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x65, + 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x03, 0x00, 0x01, 0x12, 0x03, 0x3f, 0x0a, 0x16, 0x0a, 0x0d, + 0x0a, 0x06, 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, 0x40, 0x04, 0x29, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x40, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x40, 0x0d, 0x1b, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x40, 0x1c, 0x24, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x03, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x40, 0x27, 0x28, 0x0a, 0x44, 0x0a, + 0x04, 0x04, 0x03, 0x03, 0x01, 0x12, 0x04, 0x44, 0x02, 0x46, 0x03, 0x1a, 0x36, 0x20, 0x43, 0x6f, + 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6e, 0x79, 0x20, 0x6d, 0x75, 0x73, + 0x74, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x72, + 0x75, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x03, 0x01, 0x01, 0x12, 0x03, 0x44, 0x0a, + 0x16, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x12, 0x03, 0x45, 0x04, 0x29, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, 0x45, 0x04, 0x0c, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x45, 0x0d, 0x1b, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x45, 0x1c, 0x24, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x03, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x45, 0x27, 0x28, + 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x03, 0x08, 0x00, 0x12, 0x04, 0x48, 0x02, 0x4c, 0x03, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x03, 0x08, 0x00, 0x01, 0x12, 0x03, 0x48, 0x08, 0x0c, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x03, 0x02, 0x00, 0x12, 0x03, 0x49, 0x04, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, + 0x00, 0x06, 0x12, 0x03, 0x49, 0x04, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x01, + 0x12, 0x03, 0x49, 0x17, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x03, 0x12, 0x03, + 0x49, 0x1e, 0x1f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x03, 0x02, 0x01, 0x12, 0x03, 0x4a, 0x04, 0x23, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x01, 0x06, 0x12, 0x03, 0x4a, 0x04, 0x10, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x12, 0x03, 0x4a, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x03, 0x02, 0x01, 0x03, 0x12, 0x03, 0x4a, 0x21, 0x22, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x03, + 0x02, 0x02, 0x12, 0x03, 0x4b, 0x04, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, 0x06, + 0x12, 0x03, 0x4b, 0x04, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, 0x01, 0x12, 0x03, + 0x4b, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x02, 0x03, 0x12, 0x03, 0x4b, 0x21, + 0x22, 0x0a, 0x38, 0x0a, 0x02, 0x04, 0x04, 0x12, 0x04, 0x50, 0x00, 0x68, 0x01, 0x1a, 0x2c, 0x20, + 0x41, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x67, 0x6f, + 0x76, 0x65, 0x72, 0x6e, 0x73, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, + 0x04, 0x01, 0x12, 0x03, 0x50, 0x08, 0x1f, 0x0a, 0x1b, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x12, + 0x04, 0x52, 0x02, 0x57, 0x03, 0x1a, 0x0d, 0x20, 0x42, 0x61, 0x73, 0x65, 0x20, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x04, 0x00, 0x01, 0x12, 0x03, 0x52, + 0x07, 0x1c, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x53, 0x04, + 0x2c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x53, 0x04, + 0x27, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x53, 0x2a, + 0x2b, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x54, 0x04, 0x25, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x54, 0x04, 0x20, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x54, 0x23, 0x24, + 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x55, 0x04, 0x2f, 0x0a, + 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x55, 0x04, 0x2a, 0x0a, + 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x55, 0x2d, 0x2e, 0x0a, + 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x56, 0x04, 0x35, 0x0a, 0x0e, + 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x56, 0x04, 0x30, 0x0a, 0x0e, + 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x56, 0x33, 0x34, 0x0a, 0x44, + 0x0a, 0x04, 0x04, 0x04, 0x03, 0x00, 0x12, 0x04, 0x5a, 0x02, 0x5c, 0x03, 0x1a, 0x36, 0x20, 0x43, + 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, + 0x72, 0x75, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x03, 0x00, 0x01, 0x12, 0x03, 0x5a, + 0x0a, 0x16, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, 0x5b, 0x04, + 0x32, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x5b, 0x04, + 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x5b, 0x0d, + 0x24, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x5b, 0x25, + 0x2d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x5b, 0x30, + 0x31, 0x0a, 0x44, 0x0a, 0x04, 0x04, 0x04, 0x03, 0x01, 0x12, 0x04, 0x5f, 0x02, 0x61, 0x03, 0x1a, + 0x36, 0x20, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, + 0x6c, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6e, 0x79, + 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x03, 0x01, 0x01, + 0x12, 0x03, 0x5f, 0x0a, 0x16, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x12, + 0x03, 0x60, 0x04, 0x32, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x04, 0x12, + 0x03, 0x60, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x06, 0x12, + 0x03, 0x60, 0x0d, 0x24, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, + 0x03, 0x60, 0x25, 0x2d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x04, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, + 0x03, 0x60, 0x30, 0x31, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x04, 0x08, 0x00, 0x12, 0x04, 0x63, 0x02, + 0x67, 0x03, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x08, 0x00, 0x01, 0x12, 0x03, 0x63, 0x08, 0x0c, + 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x00, 0x12, 0x03, 0x64, 0x04, 0x23, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x04, 0x02, 0x00, 0x06, 0x12, 0x03, 0x64, 0x04, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x04, 0x02, 0x00, 0x01, 0x12, 0x03, 0x64, 0x1a, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, + 0x00, 0x03, 0x12, 0x03, 0x64, 0x21, 0x22, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x01, 0x12, + 0x03, 0x65, 0x04, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, 0x06, 0x12, 0x03, 0x65, + 0x04, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, 0x01, 0x12, 0x03, 0x65, 0x11, 0x1e, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, 0x03, 0x12, 0x03, 0x65, 0x21, 0x22, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x04, 0x02, 0x02, 0x12, 0x03, 0x66, 0x04, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x04, 0x02, 0x02, 0x06, 0x12, 0x03, 0x66, 0x04, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, + 0x02, 0x01, 0x12, 0x03, 0x66, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x02, 0x03, + 0x12, 0x03, 0x66, 0x21, 0x22, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0xb8, 0x1a, + 0x0a, 0x2e, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x19, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x10, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0f, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x80, 0x03, 0x0a, 0x16, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, + 0x12, 0x50, 0x0a, 0x0d, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x52, 0x14, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x1a, 0x22, 0x0a, 0x05, 0x49, 0x6e, - 0x62, 0x6f, 0x78, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x1a, 0x94, - 0x01, 0x0a, 0x13, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, 0x6e, 0x65, - 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6f, 0x6c, - 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6e, 0x65, 0x77, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0xec, 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x6d, + 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0c, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, + 0x65, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x5f, 0x72, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x17, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, - 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, 0x2f, - 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x4d, 0xaa, 0x02, 0x18, 0x58, 0x6d, 0x74, - 0x70, 0x2e, 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, - 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, - 0xe2, 0x02, 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1a, 0x58, 0x6d, 0x74, 0x70, 0x3a, 0x3a, - 0x4d, 0x6c, 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xa8, 0x0f, 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, 0x38, 0x01, 0x0a, - 0x2f, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x25, 0x20, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, 0x63, 0x6f, - 0x64, 0x69, 0x6e, 0x67, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x0a, - 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x22, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, - 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, - 0x3a, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x09, 0x00, 0x0d, 0x01, 0x1a, 0x2e, 0x20, 0x41, 0x20, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x64, - 0x20, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x49, 0x44, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, - 0x00, 0x01, 0x12, 0x03, 0x09, 0x08, 0x18, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, - 0x03, 0x0a, 0x02, 0x26, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x0a, - 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x0a, 0x0b, 0x10, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x11, 0x21, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0a, 0x24, 0x25, 0x0a, 0x0b, 0x0a, 0x04, - 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x0b, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, - 0x01, 0x05, 0x12, 0x03, 0x0b, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, - 0x12, 0x03, 0x0b, 0x09, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, - 0x0b, 0x1b, 0x1c, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x0c, 0x02, 0x2a, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x0c, 0x02, 0x08, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x0c, 0x09, 0x25, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x0c, 0x28, 0x29, 0x0a, 0x64, 0x0a, 0x02, 0x04, 0x01, - 0x12, 0x04, 0x10, 0x00, 0x1d, 0x01, 0x1a, 0x23, 0x20, 0x54, 0x68, 0x65, 0x20, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x20, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x0a, 0x22, 0x33, 0x20, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x6c, 0x69, 0x6e, 0x74, 0x3a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x20, - 0x52, 0x45, 0x50, 0x45, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x4e, - 0x41, 0x4d, 0x45, 0x53, 0x5f, 0x50, 0x4c, 0x55, 0x52, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x0a, - 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x10, 0x08, 0x1e, 0x0a, 0x39, 0x0a, 0x04, - 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x14, 0x02, 0x2e, 0x1a, 0x2c, 0x20, 0x4d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x62, 0x65, - 0x65, 0x6e, 0x20, 0x61, 0x64, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x04, - 0x12, 0x03, 0x14, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, - 0x14, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x14, 0x1c, - 0x29, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x14, 0x2c, 0x2d, 0x0a, - 0x3b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x16, 0x02, 0x30, 0x1a, 0x2e, 0x20, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, - 0x20, 0x62, 0x65, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x01, 0x02, 0x01, 0x04, 0x12, 0x03, 0x16, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, - 0x02, 0x01, 0x06, 0x12, 0x03, 0x16, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, - 0x01, 0x12, 0x03, 0x16, 0x1c, 0x2b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, - 0x03, 0x16, 0x2e, 0x2f, 0x0a, 0x52, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x03, 0x18, 0x02, - 0x34, 0x1a, 0x45, 0x20, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x62, 0x65, 0x65, 0x6e, - 0x20, 0x61, 0x64, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, + 0x69, 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x5c, 0x0a, 0x13, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, + 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x52, 0x12, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x41, 0x64, 0x64, 0x65, 0x64, 0x12, 0x60, 0x0a, 0x15, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, + 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x52, 0x14, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x22, 0x9b, 0x04, 0x0a, 0x0c, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x74, 0x65, 0x64, 0x42, 0x79, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x52, 0x0a, 0x0d, + 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x49, 0x6e, 0x62, + 0x6f, 0x78, 0x52, 0x0c, 0x61, 0x64, 0x64, 0x65, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, + 0x12, 0x56, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x62, 0x6f, + 0x78, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x6d, 0x74, 0x70, + 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x12, 0x71, 0x0a, 0x16, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x78, 0x6d, 0x74, 0x70, 0x2e, + 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x14, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x1a, 0x22, 0x0a, 0x05, 0x49, + 0x6e, 0x62, 0x6f, 0x78, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x1a, + 0x94, 0x01, 0x0a, 0x13, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x6f, 0x6c, 0x64, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, 0x6e, + 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6f, + 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6e, 0x65, 0x77, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0xec, 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, + 0x6d, 0x74, 0x70, 0x2e, 0x6d, 0x6c, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x17, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x78, 0x6d, 0x74, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x33, 0x2f, 0x67, 0x6f, + 0x2f, 0x6d, 0x6c, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x58, 0x4d, 0x4d, 0xaa, 0x02, 0x18, 0x58, 0x6d, + 0x74, 0x70, 0x2e, 0x4d, 0x6c, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0xca, 0x02, 0x18, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, + 0x73, 0x5c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x73, 0xe2, 0x02, 0x24, 0x58, 0x6d, 0x74, 0x70, 0x5c, 0x4d, 0x6c, 0x73, 0x5c, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1a, 0x58, 0x6d, 0x74, 0x70, 0x3a, + 0x3a, 0x4d, 0x6c, 0x73, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0xa8, 0x0f, 0x0a, 0x06, 0x12, 0x04, 0x01, 0x00, 0x38, 0x01, + 0x0a, 0x2f, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x01, 0x00, 0x12, 0x1a, 0x25, 0x20, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, 0x63, + 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x03, 0x00, 0x22, 0x0a, 0x08, 0x0a, 0x01, 0x08, + 0x12, 0x03, 0x05, 0x00, 0x47, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x05, 0x00, 0x47, + 0x0a, 0x3a, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x09, 0x00, 0x0d, 0x01, 0x1a, 0x2e, 0x20, 0x41, + 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x49, 0x44, 0x73, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, + 0x04, 0x00, 0x01, 0x12, 0x03, 0x09, 0x08, 0x18, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, + 0x12, 0x03, 0x0a, 0x02, 0x26, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, + 0x0a, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x0a, 0x0b, + 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0a, 0x11, 0x21, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0a, 0x24, 0x25, 0x0a, 0x0b, 0x0a, + 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x0b, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, + 0x02, 0x01, 0x05, 0x12, 0x03, 0x0b, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, + 0x01, 0x12, 0x03, 0x0b, 0x09, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, + 0x03, 0x0b, 0x1b, 0x1c, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x0c, 0x02, + 0x2a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x0c, 0x02, 0x08, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x0c, 0x09, 0x25, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x0c, 0x28, 0x29, 0x0a, 0x64, 0x0a, 0x02, 0x04, + 0x01, 0x12, 0x04, 0x10, 0x00, 0x1d, 0x01, 0x1a, 0x23, 0x20, 0x54, 0x68, 0x65, 0x20, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x20, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x0a, 0x22, 0x33, 0x20, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x6c, 0x69, 0x6e, 0x74, 0x3a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, + 0x20, 0x52, 0x45, 0x50, 0x45, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, + 0x4e, 0x41, 0x4d, 0x45, 0x53, 0x5f, 0x50, 0x4c, 0x55, 0x52, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, + 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x10, 0x08, 0x1e, 0x0a, 0x39, 0x0a, + 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x14, 0x02, 0x2e, 0x1a, 0x2c, 0x20, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x62, + 0x65, 0x65, 0x6e, 0x20, 0x61, 0x64, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, + 0x04, 0x12, 0x03, 0x14, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x06, 0x12, + 0x03, 0x14, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x14, + 0x1c, 0x29, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x14, 0x2c, 0x2d, + 0x0a, 0x3b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x16, 0x02, 0x30, 0x1a, 0x2e, 0x20, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, + 0x65, 0x20, 0x62, 0x65, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x01, 0x04, 0x12, 0x03, 0x16, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x01, 0x06, 0x12, 0x03, 0x16, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x01, 0x01, 0x12, 0x03, 0x16, 0x1c, 0x2b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, + 0x12, 0x03, 0x16, 0x2e, 0x2f, 0x0a, 0x52, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x03, 0x18, + 0x02, 0x34, 0x1a, 0x45, 0x20, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x62, 0x65, 0x65, + 0x6e, 0x20, 0x61, 0x64, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x2c, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x65, 0x64, 0x20, 0x62, + 0x79, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x02, 0x04, 0x12, 0x03, 0x18, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x06, + 0x12, 0x03, 0x18, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, + 0x18, 0x1c, 0x2f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x18, 0x32, + 0x33, 0x0a, 0x45, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x03, 0x12, 0x03, 0x1a, 0x02, 0x36, 0x1a, 0x38, + 0x20, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x72, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x2c, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x65, 0x64, 0x20, 0x62, 0x79, - 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, - 0x04, 0x12, 0x03, 0x18, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x06, 0x12, - 0x03, 0x18, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x18, - 0x1c, 0x2f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x18, 0x32, 0x33, - 0x0a, 0x45, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x03, 0x12, 0x03, 0x1a, 0x02, 0x36, 0x1a, 0x38, 0x20, - 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x72, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x2c, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, - 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x04, - 0x12, 0x03, 0x1a, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x06, 0x12, 0x03, - 0x1a, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x01, 0x12, 0x03, 0x1a, 0x1c, - 0x31, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x03, 0x12, 0x03, 0x1a, 0x34, 0x35, 0x0a, - 0x6b, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x21, 0x00, 0x38, 0x01, 0x1a, 0x5f, 0x20, 0x41, 0x20, - 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x2e, 0x0a, 0x20, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x20, 0x61, 0x64, - 0x64, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x62, 0x6f, - 0x78, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, - 0x74, 0x6f, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, - 0x04, 0x02, 0x01, 0x12, 0x03, 0x21, 0x08, 0x14, 0x0a, 0x41, 0x0a, 0x04, 0x04, 0x02, 0x03, 0x00, - 0x12, 0x04, 0x23, 0x02, 0x25, 0x03, 0x1a, 0x33, 0x20, 0x41, 0x6e, 0x20, 0x69, 0x6e, 0x62, 0x6f, - 0x78, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x61, 0x64, 0x64, 0x65, 0x64, - 0x20, 0x6f, 0x72, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, - 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x02, 0x03, 0x00, 0x01, 0x12, 0x03, 0x23, 0x0a, 0x0f, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x03, - 0x00, 0x02, 0x00, 0x12, 0x03, 0x24, 0x04, 0x18, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, - 0x02, 0x00, 0x05, 0x12, 0x03, 0x24, 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, - 0x02, 0x00, 0x01, 0x12, 0x03, 0x24, 0x0b, 0x13, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, - 0x02, 0x00, 0x03, 0x12, 0x03, 0x24, 0x16, 0x17, 0x0a, 0x3d, 0x0a, 0x04, 0x04, 0x02, 0x03, 0x01, - 0x12, 0x04, 0x28, 0x02, 0x2f, 0x03, 0x1a, 0x2f, 0x20, 0x41, 0x20, 0x73, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x74, - 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x01, 0x01, - 0x12, 0x03, 0x28, 0x0a, 0x1d, 0x0a, 0x2b, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x12, - 0x03, 0x2a, 0x04, 0x1a, 0x1a, 0x1c, 0x20, 0x54, 0x68, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x2a, - 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x2a, - 0x0b, 0x15, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x2a, - 0x18, 0x19, 0x0a, 0x23, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x12, 0x03, 0x2c, 0x04, - 0x22, 0x1a, 0x14, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, - 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, - 0x01, 0x04, 0x12, 0x03, 0x2c, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, - 0x01, 0x05, 0x12, 0x03, 0x2c, 0x0d, 0x13, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, - 0x01, 0x01, 0x12, 0x03, 0x2c, 0x14, 0x1d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, - 0x01, 0x03, 0x12, 0x03, 0x2c, 0x20, 0x21, 0x0a, 0x22, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, - 0x02, 0x12, 0x03, 0x2e, 0x04, 0x22, 0x1a, 0x13, 0x20, 0x54, 0x68, 0x65, 0x20, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, - 0x02, 0x03, 0x01, 0x02, 0x02, 0x04, 0x12, 0x03, 0x2e, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, - 0x02, 0x03, 0x01, 0x02, 0x02, 0x05, 0x12, 0x03, 0x2e, 0x0d, 0x13, 0x0a, 0x0e, 0x0a, 0x07, 0x04, - 0x02, 0x03, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x2e, 0x14, 0x1d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, - 0x02, 0x03, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x2e, 0x20, 0x21, 0x0a, 0x0b, 0x0a, 0x04, 0x04, - 0x02, 0x02, 0x00, 0x12, 0x03, 0x31, 0x02, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, - 0x05, 0x12, 0x03, 0x31, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, - 0x03, 0x31, 0x09, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x31, - 0x21, 0x22, 0x0a, 0x2e, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x03, 0x33, 0x02, 0x23, 0x1a, - 0x21, 0x20, 0x54, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x20, 0x61, 0x64, - 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x04, 0x12, 0x03, 0x33, 0x02, 0x0a, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x06, 0x12, 0x03, 0x33, 0x0b, 0x10, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, 0x33, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x33, 0x21, 0x22, 0x0a, 0x30, 0x0a, 0x04, 0x04, 0x02, - 0x02, 0x02, 0x12, 0x03, 0x35, 0x02, 0x25, 0x1a, 0x23, 0x20, 0x54, 0x68, 0x65, 0x20, 0x69, 0x6e, - 0x62, 0x6f, 0x78, 0x65, 0x73, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x02, 0x02, 0x02, 0x04, 0x12, 0x03, 0x35, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, - 0x02, 0x02, 0x06, 0x12, 0x03, 0x35, 0x0b, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, - 0x01, 0x12, 0x03, 0x35, 0x11, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x03, 0x12, - 0x03, 0x35, 0x23, 0x24, 0x0a, 0x31, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x03, 0x12, 0x03, 0x37, 0x02, - 0x3a, 0x1a, 0x24, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x04, - 0x12, 0x03, 0x37, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x06, 0x12, 0x03, - 0x37, 0x0b, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x01, 0x12, 0x03, 0x37, 0x1f, - 0x35, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x03, 0x12, 0x03, 0x37, 0x38, 0x39, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, + 0x04, 0x12, 0x03, 0x1a, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x06, 0x12, + 0x03, 0x1a, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x01, 0x12, 0x03, 0x1a, + 0x1c, 0x31, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x03, 0x12, 0x03, 0x1a, 0x34, 0x35, + 0x0a, 0x6b, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x21, 0x00, 0x38, 0x01, 0x1a, 0x5f, 0x20, 0x41, + 0x20, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x20, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x20, 0x61, + 0x64, 0x64, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x62, + 0x6f, 0x78, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, + 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0a, 0x0a, + 0x03, 0x04, 0x02, 0x01, 0x12, 0x03, 0x21, 0x08, 0x14, 0x0a, 0x41, 0x0a, 0x04, 0x04, 0x02, 0x03, + 0x00, 0x12, 0x04, 0x23, 0x02, 0x25, 0x03, 0x1a, 0x33, 0x20, 0x41, 0x6e, 0x20, 0x69, 0x6e, 0x62, + 0x6f, 0x78, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x61, 0x64, 0x64, 0x65, + 0x64, 0x20, 0x6f, 0x72, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x03, 0x00, 0x01, 0x12, 0x03, 0x23, 0x0a, 0x0f, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, 0x24, 0x04, 0x18, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x24, 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x24, 0x0b, 0x13, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x24, 0x16, 0x17, 0x0a, 0x3d, 0x0a, 0x04, 0x04, 0x02, 0x03, + 0x01, 0x12, 0x04, 0x28, 0x02, 0x2f, 0x03, 0x1a, 0x2f, 0x20, 0x41, 0x20, 0x73, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, + 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x01, + 0x01, 0x12, 0x03, 0x28, 0x0a, 0x1d, 0x0a, 0x2b, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, + 0x12, 0x03, 0x2a, 0x04, 0x1a, 0x1a, 0x1c, 0x20, 0x54, 0x68, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, + 0x2a, 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, + 0x2a, 0x0b, 0x15, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, + 0x2a, 0x18, 0x19, 0x0a, 0x23, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x12, 0x03, 0x2c, + 0x04, 0x22, 0x1a, 0x14, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, + 0x73, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, + 0x02, 0x01, 0x04, 0x12, 0x03, 0x2c, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, + 0x02, 0x01, 0x05, 0x12, 0x03, 0x2c, 0x0d, 0x13, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, + 0x02, 0x01, 0x01, 0x12, 0x03, 0x2c, 0x14, 0x1d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, + 0x02, 0x01, 0x03, 0x12, 0x03, 0x2c, 0x20, 0x21, 0x0a, 0x22, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, + 0x02, 0x02, 0x12, 0x03, 0x2e, 0x04, 0x22, 0x1a, 0x13, 0x20, 0x54, 0x68, 0x65, 0x20, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, + 0x04, 0x02, 0x03, 0x01, 0x02, 0x02, 0x04, 0x12, 0x03, 0x2e, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, + 0x04, 0x02, 0x03, 0x01, 0x02, 0x02, 0x05, 0x12, 0x03, 0x2e, 0x0d, 0x13, 0x0a, 0x0e, 0x0a, 0x07, + 0x04, 0x02, 0x03, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x2e, 0x14, 0x1d, 0x0a, 0x0e, 0x0a, 0x07, + 0x04, 0x02, 0x03, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x2e, 0x20, 0x21, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x02, 0x02, 0x00, 0x12, 0x03, 0x31, 0x02, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, + 0x00, 0x05, 0x12, 0x03, 0x31, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, + 0x12, 0x03, 0x31, 0x09, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, + 0x31, 0x21, 0x22, 0x0a, 0x2e, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x03, 0x33, 0x02, 0x23, + 0x1a, 0x21, 0x20, 0x54, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x20, 0x61, + 0x64, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x04, 0x12, 0x03, 0x33, 0x02, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x06, 0x12, 0x03, 0x33, 0x0b, 0x10, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, 0x33, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x33, 0x21, 0x22, 0x0a, 0x30, 0x0a, 0x04, 0x04, + 0x02, 0x02, 0x02, 0x12, 0x03, 0x35, 0x02, 0x25, 0x1a, 0x23, 0x20, 0x54, 0x68, 0x65, 0x20, 0x69, + 0x6e, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x02, 0x02, 0x02, 0x04, 0x12, 0x03, 0x35, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x02, 0x02, 0x02, 0x06, 0x12, 0x03, 0x35, 0x0b, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, + 0x02, 0x01, 0x12, 0x03, 0x35, 0x11, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x03, + 0x12, 0x03, 0x35, 0x23, 0x24, 0x0a, 0x31, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x03, 0x12, 0x03, 0x37, + 0x02, 0x3a, 0x1a, 0x24, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, + 0x04, 0x12, 0x03, 0x37, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x06, 0x12, + 0x03, 0x37, 0x0b, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x01, 0x12, 0x03, 0x37, + 0x1f, 0x35, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x03, 0x12, 0x03, 0x37, 0x38, 0x39, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, ]; include!("xmtp.mls.message_contents.serde.rs"); // @@protoc_insertion_point(module) \ No newline at end of file diff --git a/xmtp_proto/src/gen/xmtp.mls.message_contents.serde.rs b/xmtp_proto/src/gen/xmtp.mls.message_contents.serde.rs index 97bee139c..b34d3b16a 100644 --- a/xmtp_proto/src/gen/xmtp.mls.message_contents.serde.rs +++ b/xmtp_proto/src/gen/xmtp.mls.message_contents.serde.rs @@ -70,287 +70,6 @@ impl<'de> serde::Deserialize<'de> for Compression { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for ConsentEntityType { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let variant = match self { - Self::Unspecified => "CONSENT_ENTITY_TYPE_UNSPECIFIED", - Self::ConversationId => "CONSENT_ENTITY_TYPE_CONVERSATION_ID", - Self::InboxId => "CONSENT_ENTITY_TYPE_INBOX_ID", - Self::Address => "CONSENT_ENTITY_TYPE_ADDRESS", - }; - serializer.serialize_str(variant) - } -} -impl<'de> serde::Deserialize<'de> for ConsentEntityType { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "CONSENT_ENTITY_TYPE_UNSPECIFIED", - "CONSENT_ENTITY_TYPE_CONVERSATION_ID", - "CONSENT_ENTITY_TYPE_INBOX_ID", - "CONSENT_ENTITY_TYPE_ADDRESS", - ]; - - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ConsentEntityType; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) - } - - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) - }) - } - - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "CONSENT_ENTITY_TYPE_UNSPECIFIED" => Ok(ConsentEntityType::Unspecified), - "CONSENT_ENTITY_TYPE_CONVERSATION_ID" => Ok(ConsentEntityType::ConversationId), - "CONSENT_ENTITY_TYPE_INBOX_ID" => Ok(ConsentEntityType::InboxId), - "CONSENT_ENTITY_TYPE_ADDRESS" => Ok(ConsentEntityType::Address), - _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), - } - } - } - deserializer.deserialize_any(GeneratedVisitor) - } -} -impl serde::Serialize for ConsentState { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let variant = match self { - Self::Unspecified => "CONSENT_STATE_UNSPECIFIED", - Self::Allowed => "CONSENT_STATE_ALLOWED", - Self::Denied => "CONSENT_STATE_DENIED", - }; - serializer.serialize_str(variant) - } -} -impl<'de> serde::Deserialize<'de> for ConsentState { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "CONSENT_STATE_UNSPECIFIED", - "CONSENT_STATE_ALLOWED", - "CONSENT_STATE_DENIED", - ]; - - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ConsentState; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) - } - - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) - }) - } - - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "CONSENT_STATE_UNSPECIFIED" => Ok(ConsentState::Unspecified), - "CONSENT_STATE_ALLOWED" => Ok(ConsentState::Allowed), - "CONSENT_STATE_DENIED" => Ok(ConsentState::Denied), - _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), - } - } - } - deserializer.deserialize_any(GeneratedVisitor) - } -} -impl serde::Serialize for ConsentUpdate { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.entity_type != 0 { - len += 1; - } - if self.state != 0 { - len += 1; - } - if !self.entity.is_empty() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("xmtp.mls.message_contents.ConsentUpdate", len)?; - if self.entity_type != 0 { - let v = ConsentEntityType::try_from(self.entity_type) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.entity_type)))?; - struct_ser.serialize_field("entityType", &v)?; - } - if self.state != 0 { - let v = ConsentState::try_from(self.state) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.state)))?; - struct_ser.serialize_field("state", &v)?; - } - if !self.entity.is_empty() { - struct_ser.serialize_field("entity", &self.entity)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for ConsentUpdate { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "entity_type", - "entityType", - "state", - "entity", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - EntityType, - State, - Entity, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "entityType" | "entity_type" => Ok(GeneratedField::EntityType), - "state" => Ok(GeneratedField::State), - "entity" => Ok(GeneratedField::Entity), - _ => Err(serde::de::Error::unknown_field(value, FIELDS)), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ConsentUpdate; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct xmtp.mls.message_contents.ConsentUpdate") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut entity_type__ = None; - let mut state__ = None; - let mut entity__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::EntityType => { - if entity_type__.is_some() { - return Err(serde::de::Error::duplicate_field("entityType")); - } - entity_type__ = Some(map_.next_value::()? as i32); - } - GeneratedField::State => { - if state__.is_some() { - return Err(serde::de::Error::duplicate_field("state")); - } - state__ = Some(map_.next_value::()? as i32); - } - GeneratedField::Entity => { - if entity__.is_some() { - return Err(serde::de::Error::duplicate_field("entity")); - } - entity__ = Some(map_.next_value()?); - } - } - } - Ok(ConsentUpdate { - entity_type: entity_type__.unwrap_or_default(), - state: state__.unwrap_or_default(), - entity: entity__.unwrap_or_default(), - }) - } - } - deserializer.deserialize_struct("xmtp.mls.message_contents.ConsentUpdate", FIELDS, GeneratedVisitor) - } -} impl serde::Serialize for ContentTypeId { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -4026,8 +3745,8 @@ impl serde::Serialize for plaintext_envelope::V2 { plaintext_envelope::v2::MessageType::DeviceSyncReply(v) => { struct_ser.serialize_field("deviceSyncReply", v)?; } - plaintext_envelope::v2::MessageType::ConsentUpdate(v) => { - struct_ser.serialize_field("consentUpdate", v)?; + plaintext_envelope::v2::MessageType::UserPreferenceUpdate(v) => { + struct_ser.serialize_field("userPreferenceUpdate", v)?; } } } @@ -4048,8 +3767,8 @@ impl<'de> serde::Deserialize<'de> for plaintext_envelope::V2 { "deviceSyncRequest", "device_sync_reply", "deviceSyncReply", - "consent_update", - "consentUpdate", + "user_preference_update", + "userPreferenceUpdate", ]; #[allow(clippy::enum_variant_names)] @@ -4058,7 +3777,7 @@ impl<'de> serde::Deserialize<'de> for plaintext_envelope::V2 { Content, DeviceSyncRequest, DeviceSyncReply, - ConsentUpdate, + UserPreferenceUpdate, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -4084,7 +3803,7 @@ impl<'de> serde::Deserialize<'de> for plaintext_envelope::V2 { "content" => Ok(GeneratedField::Content), "deviceSyncRequest" | "device_sync_request" => Ok(GeneratedField::DeviceSyncRequest), "deviceSyncReply" | "device_sync_reply" => Ok(GeneratedField::DeviceSyncReply), - "consentUpdate" | "consent_update" => Ok(GeneratedField::ConsentUpdate), + "userPreferenceUpdate" | "user_preference_update" => Ok(GeneratedField::UserPreferenceUpdate), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -4134,11 +3853,11 @@ impl<'de> serde::Deserialize<'de> for plaintext_envelope::V2 { message_type__ = map_.next_value::<::std::option::Option<_>>()?.map(plaintext_envelope::v2::MessageType::DeviceSyncReply) ; } - GeneratedField::ConsentUpdate => { + GeneratedField::UserPreferenceUpdate => { if message_type__.is_some() { - return Err(serde::de::Error::duplicate_field("consentUpdate")); + return Err(serde::de::Error::duplicate_field("userPreferenceUpdate")); } - message_type__ = map_.next_value::<::std::option::Option<_>>()?.map(plaintext_envelope::v2::MessageType::ConsentUpdate) + message_type__ = map_.next_value::<::std::option::Option<_>>()?.map(plaintext_envelope::v2::MessageType::UserPreferenceUpdate) ; } } @@ -4336,3 +4055,97 @@ impl<'de> serde::Deserialize<'de> for PolicySet { deserializer.deserialize_struct("xmtp.mls.message_contents.PolicySet", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for UserPreferenceUpdate { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.contents.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("xmtp.mls.message_contents.UserPreferenceUpdate", len)?; + if !self.contents.is_empty() { + struct_ser.serialize_field("contents", &self.contents.iter().map(pbjson::private::base64::encode).collect::>())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for UserPreferenceUpdate { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "contents", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Contents, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "contents" => Ok(GeneratedField::Contents), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = UserPreferenceUpdate; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct xmtp.mls.message_contents.UserPreferenceUpdate") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut contents__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Contents => { + if contents__.is_some() { + return Err(serde::de::Error::duplicate_field("contents")); + } + contents__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } + } + } + Ok(UserPreferenceUpdate { + contents: contents__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("xmtp.mls.message_contents.UserPreferenceUpdate", FIELDS, GeneratedVisitor) + } +} diff --git a/xmtp_v2/Cargo.toml b/xmtp_v2/Cargo.toml index b1fcf7e7a..5917affcd 100644 --- a/xmtp_v2/Cargo.toml +++ b/xmtp_v2/Cargo.toml @@ -1,19 +1,19 @@ [package] edition = "2021" +license.workspace = true name = "xmtp_v2" rust-version = "1.64" version.workspace = true -license.workspace = true [dependencies] aes-gcm = "0.10.1" ecdsa = "0.15.1" generic-array = "0.14.6" hex = { workspace = true } -hkdf = "0.12.3" +hkdf.workspace = true k256 = { version = "0.12.0", features = ["ecdh"] } rand = { workspace = true } -sha2 = "0.10.6" +sha2.workspace = true sha3 = "0.10.6" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/xmtp_v2/src/k256_helper.rs b/xmtp_v2/src/k256_helper.rs index 338ab2e28..ae671100e 100644 --- a/xmtp_v2/src/k256_helper.rs +++ b/xmtp_v2/src/k256_helper.rs @@ -9,9 +9,9 @@ use sha3::Keccak256; /// diffie_hellman - compute the shared secret between a secret key and a public key /// NOTE: This is a custom implementation of the diffie_hellman operation /// because RustCrypto hides the `y` coordinate from visibility when constructing a SharedSecret. - +/// /// [Read more](https://github.com/RustCrypto/traits/blob/d57b54b9fcf5b28745547cb9fef313ab09780918/elliptic-curve/src/ecdh.rs#L60) - +/// /// XMTP uses the entire point in uncompressed format as secret material fn diffie_hellman(secret_key: &SecretKey, public_key: &PublicKey) -> Result, String> { // Get the public projective point from the public key @@ -20,7 +20,7 @@ fn diffie_hellman(secret_key: &SecretKey, public_key: &PublicKey) -> Result