Move to the kotlin test generator.

This commit is contained in:
Greyson Parrelli 2024-08-28 10:09:29 -04:00 committed by GitHub
parent 33e90be18b
commit 2019d89021
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
641 changed files with 43859 additions and 3146 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*.{kt,kts}]
indent_size = 2
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_allow_trailing_comma = false
ktlint_code_style = intellij_idea
ktlint_standard_class-naming = disabled
ktlint_standard_no-wildcard-imports = disabled
[**/generated/**/*]
ktlint = disabled

View File

@ -7,25 +7,27 @@ on:
- main
jobs:
generate_and_compare:
name: Generate and compare .binprotos
run:
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- uses: gradle/actions/setup-gradle@v4
- run: sudo apt-get update && sudo apt-get install protobuf-compiler
- run: rustup toolchain install stable
- name: Generate tests
run: ./gradlew run
- name: Generate `.binproto`s for all `.jsonproto`s
run: ${PWD}/scripts/generate-binprotos.sh
- name: Ensure output matches committed tests
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "Git status is not clean after running the Gradle script. Output does not match the committed tests!"
exit 1
else
echo "Git status is clean. No changes detected."
fi
- name: Compare generated `.binproto`s
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "Files changed after generating .binprotos!"
exit 1
fi
- name: Check lint
run: ./gradlew ktlintCheck

13
.gitignore vendored
View File

@ -1,3 +1,12 @@
.DS_Store
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
.idea/
!.idea/file.template.settings.xml
!.idea/fileTemplates/
.gradle/
build/
target
### Mac OS ###
.DS_Store

824
Cargo.lock generated
View File

@ -1,824 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anstyle-parse"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "assert_matches"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap-stdin"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b2273396940ab439c58cd300e1e93a07768fce6e7a578f24089aab40b3a9d00"
dependencies = [
"thiserror",
]
[[package]]
name = "clap_builder"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "displaydoc"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "futures"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "home"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "json5"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
dependencies = [
"pest",
"pest_derive",
"serde",
]
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libsignal-message-backup-io"
version = "0.1.0"
dependencies = [
"arrayvec",
"assert_matches",
"clap",
"clap-stdin",
"displaydoc",
"futures",
"json5",
"protobuf",
"protobuf-codegen",
"protobuf-json-mapping",
"serde_json",
"thiserror",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "pest"
version = "2.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
dependencies = [
"unicode-ident",
]
[[package]]
name = "protobuf"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0"
dependencies = [
"once_cell",
"protobuf-support",
"thiserror",
]
[[package]]
name = "protobuf-codegen"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32777b0b3f6538d9d2e012b3fad85c7e4b9244b5958d04a6415f4333782b7a77"
dependencies = [
"anyhow",
"once_cell",
"protobuf",
"protobuf-parse",
"regex",
"tempfile",
"thiserror",
]
[[package]]
name = "protobuf-json-mapping"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8440284a60d89deae81f67cc713f03f770ae51461e7613e44808910502693e"
dependencies = [
"protobuf",
"protobuf-support",
"thiserror",
]
[[package]]
name = "protobuf-parse"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96cb37955261126624a25b5e6bda40ae34cf3989d52a783087ca6091b29b5642"
dependencies = [
"anyhow",
"indexmap",
"log",
"protobuf",
"protobuf-support",
"tempfile",
"thiserror",
"which",
]
[[package]]
name = "protobuf-support"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7"
dependencies = [
"thiserror",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.202"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.202"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
]
[[package]]
name = "thiserror"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "ucd-trie"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

View File

@ -1,5 +0,0 @@
[workspace]
resolver = "2"
members = ["converter"]

View File

@ -1,26 +1,22 @@
# Signal Message Backup shared integration test cases
# Signal Message Backup Tests
The goal of this project is to provide a set of shared backup test files that clients can use to validate that they are
importing and exporting data correctly. They can do this by importing the test file, exporting a new one, and verifying
that the new file is functionally equivalent to the original (using the [libsignal](https://github.com/signalapp/libsignal) comparator).
At a high level, the goal of Backup integration tests is to validate that each client can import a given Backup instance into local state; export that local state into a new Backup instance; and confirm that the imported and exported Backups are equivalent. This requires a representation of Backup instances that is both human- and computer-readable and -writable, and a set of shared Backup instances that all clients are consuming in tests.
The tests themselves are generated through a Kotlin DSL that allows for the easy creation of permutations of a given
proto. This helps ensure that we have strong coverage over a wide range of possible backup files.
## Key Terms
# Using shared test cases
The test cases are located in [test-cases](test-cases). The actual backup file has a `.binproto` extension. Each of
these is paired with a `.txtproto` file that contains a human-readable version of the backup file to aid in debugging.
- “Backup binary” format: the `<varint><proto data>...` format used to serialize a Backup.
- `.binproto`: a file extension identifying a backup binary file that is neither gzipped nor encrypted.
The `.binproto`'s themselves are unencrypted and un-gzipped. This is to avoid any need for a shared key.
## Representing test-case Backup instances
# Creating test cases
If you import this project into IntelliJ, it comes with a template for creating a new test case. If you right-click
the `tests` package and click `New > Signal Backup Test Case`, you should have a basic template. Everything is generated
with a largely straightforward kotlin DSL. There's lots of existing test cases you can use as an example to see how
things are put together and how you can easily generate permutations of a given proto.
We will represent Backup test-case instances using JSON, relying on Proto3s [JSON-mapping specification](https://protobuf.dev/programming-guides/proto3/#json) to convert types in `Backup.proto` to JSON objects. Specifically, a JSON representation of a Backup (a “`.jsonproto`” file) will contain a single top-level JSON array containing JSON representations of the ordered protos in a backup binary.
This format will allow developers to manually write test cases representing specific scenarios as well as to inspect and perform manual validation on existing test cases during development.
### Importing test cases in client tests
While `.jsonproto` is human-interpretable, client test frameworks are expecting to import `.binproto` files. Fortunately, a `.jsonproto` is straightforwardly convertible to `.binproto` by reading the JSON array, mapping each contained object back to its Proto3 representation, and re-serializing those protos.
To convert a `.jsonproto` test case into its corresponding `.binproto`, ensure you have Rust/`cargo` installed on your machine and run:
```sh
; cargo run test-cases/{test-case-name}.jsonproto > test-cases/{test-case-name}.binproto
```
Alternatively, having added a `.jsonproto` file to `test-cases/`, run `./scripts/generate-binprotos.sh`. CI will do this on every push/pull-request, to validate that all committed `.binproto` files match their corresponding `.jsonproto`s. A generated `.binproto` should be committed to the repo for each added `.jsonproto`.
After creating the test file, add it to the `ALL_TEST_CASES` list in `Main.kt`, and run the project. If you're not using
Intellij, you can simply run `./gradlew run` from the terminal.

24
build.gradle.kts Normal file
View File

@ -0,0 +1,24 @@
plugins {
id("org.jetbrains.kotlin.jvm") version "1.9.10"
id("com.squareup.wire") version "4.4.3"
id("org.jlleitschuh.gradle.ktlint") version "12.1.1"
application
}
repositories {
mavenCentral()
mavenLocal()
}
wire {
kotlin {}
}
application {
mainClass = "MainKt"
}
dependencies {
implementation("org.signal:libsignal-client:0.56.0")
implementation(kotlin("reflect"))
}

View File

@ -1,35 +0,0 @@
#
# Copyright (C) 2023 Signal Messenger, LLC.
# SPDX-License-Identifier: AGPL-3.0-only
#
[package]
name = "libsignal-message-backup-io"
version = "0.1.0"
edition = "2021"
authors = ["Signal Messenger LLC"]
license = "AGPL-3.0-only"
default-run = "json_to_binproto"
[[bin]]
name = "json_to_binproto"
[[bin]]
name = "binproto_to_json"
[dependencies]
arrayvec = "0.7.4"
assert_matches = "1.5.0"
clap = { version = "4.2.1", features = ["derive"] }
clap-stdin = "0.3.0"
displaydoc = "0.2.4"
futures = "0.3.29"
json5 = "0.4.1"
protobuf = "3.3.0"
protobuf-json-mapping = "3.3.0"
serde_json = "1.0"
thiserror = "1.0.50"
[build-dependencies]
protobuf = "3.3.0"
protobuf-codegen = "3.3.0"

View File

@ -1,34 +0,0 @@
//
// Copyright 2022 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
fn main() {
const PROTOS_DIR: &str = "protos";
let out_dir = format!(
"{}/{PROTOS_DIR}",
std::env::var("OUT_DIR").expect("OUT_DIR env var not set")
);
std::fs::create_dir_all(&out_dir).expect("failed to create output directory");
const PROTOS: &[&str] = &["src/proto/backup.proto"];
let mut codegen = protobuf_codegen::Codegen::new();
codegen
.protoc()
.protoc_extra_arg(
// Enable optional fields. This isn't needed in the most recent
// protobuf compiler version, but adding it lets us support older
// versions that might be installed in CI or on developer machines.
"--experimental_allow_proto3_optional",
)
.include("src")
.out_dir(&out_dir)
.inputs(PROTOS)
.run_from_script();
for proto in PROTOS {
println!("cargo:rerun-if-changed={}", proto);
}
}

View File

@ -1,28 +0,0 @@
//
// Copyright (C) 2024 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
use clap::Parser;
use clap_stdin::FileOrStdin;
use futures::io::AllowStdIo;
#[derive(Parser)]
/// Compresses and encrypts an unencrypted backup file.
struct CliArgs {
/// the file to read from, or '-' to read from stdin
filename: FileOrStdin,
}
fn main() {
let CliArgs { filename } = CliArgs::parse();
eprintln!("reading from {:?}", filename.source);
let json_array = futures::executor::block_on(libsignal_message_backup_io::convert_to_json(
AllowStdIo::new(filename.into_reader().expect("failed to open")),
))
.expect("failed to convert");
print!("{:#}", serde_json::Value::Array(json_array));
}

View File

@ -1,46 +0,0 @@
//
// Copyright (C) 2024 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
use std::io::{Read as _, Write as _};
use assert_matches::assert_matches;
use clap::Parser;
use clap_stdin::FileOrStdin;
#[derive(Parser)]
/// Compresses and encrypts an unencrypted backup file.
struct CliArgs {
/// the file to read from, or '-' to read from stdin
filename: FileOrStdin,
}
fn main() {
let CliArgs { filename } = CliArgs::parse();
eprintln!("reading from {:?}", filename.source);
let contents =
json5::from_str(&String::from_utf8(read_file(filename)).expect("not a string"))
.expect("invalid JSON");
let contents = assert_matches!(contents, serde_json::Value::Array(contents) => contents);
let serialized =
libsignal_message_backup_io::convert_from_json(contents).expect("failed to convert");
std::io::stdout()
.write_all(&serialized)
.expect("failed to write");
}
fn read_file(filename: FileOrStdin) -> Vec<u8> {
let source = filename.source.clone();
let mut contents = Vec::new();
filename
.into_reader()
.unwrap_or_else(|e| panic!("failed to read {source:?}: {e}"))
.read_to_end(&mut contents)
.expect("IO error");
contents
}

View File

@ -1,82 +0,0 @@
//
// Copyright (C) 2024 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
//! Signal remote message backup conversion utilities.
mod parse;
mod proto;
#[derive(Debug, thiserror::Error, displaydoc::Display)]
pub enum ConvertJsonError {
/// input array was empty
EmptyArray,
/// failed to parse JSON as proto: {0}
ProtoJsonParse(#[from] protobuf_json_mapping::ParseError),
/// failed to print proto as JSON: {0}
ProtoJsonPrint(#[from] protobuf_json_mapping::PrintError),
/// JSON error: {0}
Json(#[from] serde_json::Error),
/// failed to encode/decode binary protobuf: {0}
ProtoEncode(#[from] protobuf::Error),
/// input/output error: {0}
Io(#[from] std::io::Error),
}
impl From<crate::parse::ParseError> for ConvertJsonError {
fn from(value: crate::parse::ParseError) -> Self {
match value {
crate::parse::ParseError::Decode(e) => e.into(),
crate::parse::ParseError::Io(e) => e.into(),
}
}
}
pub fn convert_from_json(json: Vec<serde_json::Value>) -> Result<Box<[u8]>, ConvertJsonError> {
let mut it = json.into_iter();
let backup_info = protobuf_json_mapping::parse_from_str::<proto::backup::BackupInfo>(
&it.next().ok_or(ConvertJsonError::EmptyArray)?.to_string(),
)?;
let mut serialized = Vec::new();
protobuf::Message::write_length_delimited_to_vec(&backup_info, &mut serialized)?;
for json_frame in it {
let frame =
protobuf_json_mapping::parse_from_str::<proto::backup::Frame>(&json_frame.to_string())?;
protobuf::Message::write_length_delimited_to_vec(&frame, &mut serialized)?;
}
Ok(serialized.into_boxed_slice())
}
pub async fn convert_to_json(
length_delimited_binproto: impl futures::AsyncRead + Unpin,
) -> Result<Vec<serde_json::Value>, ConvertJsonError> {
fn binary_proto_to_json<M: protobuf::MessageFull>(
binary: &[u8],
) -> Result<serde_json::Value, ConvertJsonError> {
let proto = M::parse_from_bytes(binary)?;
let json_proto = protobuf_json_mapping::print_to_string(&proto)?;
Ok(serde_json::from_str(&json_proto)?)
}
let mut reader = crate::parse::VarintDelimitedReader::new(length_delimited_binproto);
let mut array = Vec::new();
let backup_info = reader
.read_next()
.await?
.ok_or(ConvertJsonError::EmptyArray)?;
array.push(binary_proto_to_json::<proto::backup::BackupInfo>(
&backup_info,
)?);
while let Some(frame) = reader.read_next().await? {
array.push(binary_proto_to_json::<proto::backup::Frame>(&frame)?);
}
Ok(array)
}

View File

@ -1,220 +0,0 @@
//
// Copyright (C) 2023 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
use std::fmt::Debug;
use arrayvec::ArrayVec;
use futures::io::{AsyncRead, AsyncReadExt as _};
#[derive(Debug, displaydoc::Display, thiserror::Error)]
pub enum ParseError {
/// io: {0}
Io(#[from] std::io::Error),
/// proto decode error: {0}
Decode(#[from] protobuf::Error),
}
const VARINT_MAX_LENGTH: usize = 10;
pub struct VarintDelimitedReader<R> {
reader: R,
buffer: ArrayVec<u8, VARINT_MAX_LENGTH>,
}
impl<R: AsyncRead + Unpin> VarintDelimitedReader<R> {
pub fn new(reader: R) -> Self {
Self {
reader,
buffer: ArrayVec::new(),
}
}
pub async fn read_next(&mut self) -> Result<Option<Box<[u8]>>, ParseError> {
let length = match self.read_next_varint().await? {
None => return Ok(None),
Some(length) => length,
};
let Self { reader, buffer } = self;
// Read `length` bytes, first from the buffer, then from the reader.
let mut buf = Vec::with_capacity(length);
let buffered_byte_count = length.min(buffer.len());
buf.extend_from_slice(&buffer[..buffered_byte_count]);
buffer.drain(..buffered_byte_count);
if buffered_byte_count < length {
buf.resize(length, 0);
reader.read_exact(&mut buf[buffered_byte_count..]).await?;
}
Ok(Some(buf.into_boxed_slice()))
}
async fn read_next_varint(&mut self) -> Result<Option<usize>, ParseError> {
let Self { buffer, reader } = self;
// First fill up the buffer with zeros so it can be treated as a slice.
// Keep track of how many bytes in the buffer have actually been read
// from the reader.
let mut read_bytes = buffer.len();
buffer.extend(
[0; VARINT_MAX_LENGTH][..buffer.remaining_capacity()]
.iter()
.cloned(),
);
// Read into the invalid portion until it's full or the reader is empty.
loop {
let n = reader.read(&mut buffer[read_bytes..]).await?;
if n == 0 {
break;
}
read_bytes += n;
}
// Chop off any zeroed-but-not-read bytes.
buffer.truncate(read_bytes);
if read_bytes == 0 {
return Ok(None);
}
let mut proto_reader = protobuf::CodedInputStream::from_bytes(buffer);
let length = proto_reader
.read_raw_varint32()
.map_err(|_: protobuf::Error| {
std::io::Error::from(std::io::ErrorKind::UnexpectedEof)
})?;
// Remove the consumed bytes from the buffer.
let consumed_byte_count: usize =
proto_reader.pos().try_into().expect("< VARINT_MAX_LENGTH");
drop(proto_reader);
buffer.drain(..consumed_byte_count);
Ok(Some(length.try_into().expect("u32::MAX < usize::MAX")))
}
}
#[cfg(test)]
mod test {
use assert_matches::assert_matches;
use futures::executor::block_on;
use futures::io::Cursor;
use futures::pin_mut;
use super::*;
#[test]
fn read_length_delimited_empty() {
let bytes = [];
let reader = VarintDelimitedReader::new(bytes.as_slice());
pin_mut!(reader);
assert_matches!(block_on(reader.read_next()), Ok(None));
}
#[test]
fn read_length_delimited_truncated() {
const MESSAGE_SIZE: usize = 10;
const VARINT_LEN: usize = 1;
let mut short_buf = [0; VARINT_LEN + MESSAGE_SIZE - 1];
{
let mut writer = protobuf::CodedOutputStream::bytes(&mut short_buf);
writer
.write_raw_varint32(MESSAGE_SIZE.try_into().unwrap())
.expect("can hold varint");
writer.flush().expect("can write");
assert_eq!(
writer.total_bytes_written(),
u64::try_from(VARINT_LEN).unwrap()
);
}
let reader = VarintDelimitedReader::new(short_buf.as_slice());
pin_mut!(reader);
assert_matches!(
block_on(reader.read_next()),
Err(ParseError::Io(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof
);
}
struct MessageAndLen<const L: usize, const M: usize> {
varint: [u8; L],
message: [u8; M],
}
impl<const L: usize, const M: usize> MessageAndLen<L, M> {
const fn new(varint: [u8; L], message: [u8; M]) -> Self {
Self { varint, message }
}
fn into_reader(self) -> impl AsyncRead {
Cursor::new(self.varint).chain(Cursor::new(self.message))
}
}
#[track_caller]
fn assert_valid<const M: usize, const L: usize>(
MessageAndLen { varint, message }: &MessageAndLen<M, L>,
) {
let mut buf = [0u8; L];
let mut writer = protobuf::CodedOutputStream::bytes(&mut buf);
writer
.write_raw_varint32(message.len().try_into().unwrap())
.expect("correct length");
writer.flush().expect("can write");
let written_bytes = writer.total_bytes_written().try_into().unwrap();
drop(writer);
let buf = &buf[..written_bytes];
assert_eq!(buf, varint);
}
#[test]
fn read_length_delimited_multiple_messages() {
const FIRST: MessageAndLen<1, 5> = MessageAndLen::new([5], *b"12345");
const SECOND: MessageAndLen<1, 7> = MessageAndLen::new([7], *b"abcdefg");
const THIRD: MessageAndLen<2, 256> = MessageAndLen::new([0x80, 0x02], [0xab; 256]);
// Assert that our constants are correct before using them as input to
// the actual test.
assert_valid(&FIRST);
assert_valid(&SECOND);
assert_valid(&THIRD);
let concatenated_reader = FIRST
.into_reader()
.chain(SECOND.into_reader().chain(THIRD.into_reader()));
let reader = VarintDelimitedReader::new(concatenated_reader);
pin_mut!(reader);
// Read two messages.
assert_eq!(
*block_on(reader.read_next())
.expect("can read")
.expect("has frame"),
FIRST.message
);
assert_eq!(
*block_on(reader.read_next())
.expect("can read")
.expect("has frame"),
SECOND.message
);
assert_eq!(
*block_on(reader.read_next())
.expect("can read")
.expect("has frame"),
THIRD.message
);
assert_matches!(block_on(reader.read_next()), Ok(None));
}
}

View File

@ -1,6 +0,0 @@
//
// Copyright 2023 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));

View File

@ -1 +0,0 @@
../../../Backup.proto

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
gradlew vendored Executable file
View File

@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
gradlew.bat vendored Normal file
View File

@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1,14 +0,0 @@
#!/usr/bin/env sh
if ! command -v openssl > /dev/null; then
echo "Error: OpenSSL not installed!"
exit 1
fi
if [ "$#" -ne 1 ]; then
length=32
else
length="$1"
fi
openssl rand -base64 "$length"

View File

@ -1,6 +0,0 @@
#!/usr/bin/env sh
uuid=$(uuidgen)
echo "UUID: $uuid"
echo "UUID Base64: $(echo $uuid | xxd -r -p | base64)"

View File

@ -1,18 +0,0 @@
#!/usr/bin/env sh
if ! command -v cargo > /dev/null; then
echo "Error: cargo not installed!"
exit 1
fi
if [ -z "$TEST_CASE_DIR" ]; then
TEST_CASE_DIR="$PWD/test-cases"
fi
for TEST_CASE_FILE in $(ls $TEST_CASE_DIR/*.jsonproto); do
TEST_CASE_NAME=$(basename $TEST_CASE_FILE .jsonproto)
cargo run \
"$TEST_CASE_DIR/$TEST_CASE_NAME.jsonproto" \
> "$TEST_CASE_DIR/$TEST_CASE_NAME.binproto"
done

View File

@ -1,23 +0,0 @@
#!/usr/bin/env sh
if ! command -v cargo > /dev/null; then
echo "Error: cargo not installed!"
exit 1
fi
if [ "$#" -ne 1 ]; then
echo "Error: expected the jsonproto file name as an argument!"
exit 1
fi
TEST_CASE_FILE="$1"
if [ -z "$TEST_CASE_DIR" ]; then
TEST_CASE_DIR="$PWD/test-cases"
fi
TEST_CASE_NAME=$(basename $TEST_CASE_FILE .jsonproto)
cargo run \
"$TEST_CASE_DIR/$TEST_CASE_NAME.jsonproto" \
> "$TEST_CASE_DIR/$TEST_CASE_NAME.binproto"

130
src/main/kotlin/Main.kt Normal file
View File

@ -0,0 +1,130 @@
@file:Suppress("UNCHECKED_CAST")
import com.squareup.wire.Message
import org.signal.libsignal.messagebackup.ComparableBackup
import org.signal.libsignal.messagebackup.MessageBackup
import tests.*
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.OutputStream
const val OUTPUT_DIR = "test-cases"
val ALL_TEST_CASES = listOf(
StandardFramesTestCase,
AccountDataTestCase,
RecipientContactsTestCase,
RecipientGroupsTestCase,
RecipientDistributionListTestCase,
RecipientCallLinkTestCase,
StickerPackTestCase,
AdHocCallTestCase,
ChatTestCase,
ChatItemSimpleUpdatesTestCase,
ChatItemContactMessageTestCase,
ChatItemLearnedProfileUpdateTestCase,
ChatItemProfileChangeUpdateTestCase,
ChatItemSessionSwitchoverUpdateTestCase,
ChatItemThreadMergeUpdateTestCase,
ChatItemExpirationTimerUpdateTestCase,
ChatItemPaymentNotificationTestCase,
// ChatItemGiftBadgeTestCase, TODO figure out a way to generate valid presentations
ChatItemIndividualCallUpdateTestCase,
ChatItemGroupCallUpdateTestCase,
ChatItemStandardMessageTextOnlyTestCase,
ChatItemStandardMessageFormattedTextTestCase,
ChatItemStandardMessageStandardAttachmentsTestCase,
ChatItemStandardMessageSpecialAttachmentsTestCase,
ChatItemStandardMessageLongTextTestCase,
ChatItemStandardMessageWithEditsTestCase,
ChatItemStandardMessageWithQuoteTestCase,
ChatItemStickerMessageTestCase,
ChatItemRemoteDeleteTestCase
)
fun main() {
val startTime = System.currentTimeMillis()
println("Initializing...")
init()
runAllTests()
println("Complete! Took ${System.currentTimeMillis() - startTime} ms.")
}
private fun init() {
File(OUTPUT_DIR).mkdir()
File(OUTPUT_DIR).listFiles { _, name -> name.endsWith(".txtproto") }?.forEach { it.delete() }
File(OUTPUT_DIR).listFiles { _, name -> name.endsWith(".binproto") }?.forEach { it.delete() }
}
private fun runAllTests() {
ALL_TEST_CASES.forEach { test ->
runTest(
testName = test.baseFileName,
init = {
test.initialize()
with(test) {
execute()
}
}
)
}
}
private fun runTest(testName: String, init: PermutationScope.() -> Unit) {
println("Generating $testName...")
val snapshots = permute { init() }
// We keep a separate index because we don't want to increment it if the snapshot produces invalid output
var index = 0
snapshots.forEach { snapshot ->
val binary = framesToBytes(snapshot)
// Implicitly validates the backup. Throws exception on error.
ComparableBackup.readUnencrypted(MessageBackup.Purpose.REMOTE_BACKUP, binary.inputStream(), binary.size.toLong())
// For one-off tests with no permutations, it's nice to not have a trailing number
val baseFileName = if (snapshots.size == 1) {
testName
} else {
"${testName}_${index.minDigits(2)}"
}
File("$OUTPUT_DIR/$baseFileName.binproto").writeBytes(binary)
val json = snapshot.joinToString("\n\n") { it.prettyPrint() }
File("$OUTPUT_DIR/$baseFileName.txtproto").writeText(json.prefixWithWarningComment())
index++
}
}
private fun framesToBytes(frames: List<Message<*, *>>): ByteArray {
val output = ByteArrayOutputStream()
PlainTextBackupWriter(output).use { writer ->
frames.forEach { writer.write(it) }
}
return output.toByteArray()
}
private fun String.prefixWithWarningComment(): String {
return "// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!\n\n$this"
}
private fun Int.minDigits(minDigits: Int): String {
return this.toString().padStart(minDigits, '0')
}
private class PlainTextBackupWriter(private val outputStream: OutputStream) : AutoCloseable {
fun write(frame: Message<*, *>) {
val frameBytes: ByteArray = frame.encode()
outputStream.writeVarInt32(frameBytes.size)
outputStream.write(frameBytes)
}
override fun close() {
outputStream.close()
}
}

View File

@ -0,0 +1,499 @@
@file:Suppress("UNCHECKED_CAST")
@file:OptIn(ExperimentalStdlibApi::class)
import com.squareup.wire.Message
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.backup.v2.proto.*
import java.util.*
import kotlin.math.max
/**
* Generates a list of snapshots, ensuring that a snapshot exists for each possible value of each individual generator.
*
* @param snapshotCount If set to a value > 0, it will generate exactly that many snapshots. Otherwise, it will generate
* the minimum number required to cover the minSizes of all registered generators.
*/
fun permute(snapshotCount: Int = -1, generateSnapshot: PermutationScope.() -> Unit): List<List<Message<*, *>>> {
val permutationScope = PermutationScope()
val snapshots: MutableList<List<Message<*, *>>> = mutableListOf()
if (snapshotCount > 0) {
repeat(snapshotCount) {
permutationScope.generateSnapshot()
snapshots += permutationScope.next()
}
} else {
while (permutationScope.hasNext()) {
permutationScope.generateSnapshot()
snapshots += permutationScope.next()
}
}
return snapshots
}
/**
* Like [permute], but assumes that each snapshot only has a single element, and returns a list of that element type.
*/
private fun <T> permuteSingle(snapshotCount: Int = -1, init: PermutationScope.() -> Unit): List<T> {
val snapshots = permute(snapshotCount) { init() }
return snapshots.map {
if (it.isEmpty()) throw IllegalStateException("No frames in snapshot! Did you forget to add to `frames`?")
it[0] as T
}
}
/**
* Given a list of generators, returns a new list of generators that are null prefixed/suffixed to ensure that only
* one generator will output a value at any given time. This is to aid in generating permutations for oneof fields.
*
* Example:
*
* ```kotlin
* val (gen1, gen2) = oneOf(Generators.list("a", "b"), Generators.list("c", "d"))
*
* // The generators are now effectively:
* // gen1: ("a", "b", null, null)
* // gen2: (null, null, "c", "d")
* ```
*
* When you destructure things like this, the typing can get a bit weird/annoying, so [someOneOf] was made to more
* easily force the correct typing.
*
* ```kotlin
* val (gen1, gen2) = oneOf(Generators.list("a", "b"), Generators.list("c", "d"))
* // ...
* frames += MyModel(myField = someOneOf(gen1))
*/
fun oneOf(vararg generators: Generator<Any?>): List<Generator<Any?>> {
val sizes = generators.map { it.minSize }
val totalSize = sizes.sum()
val output: MutableList<Generator<Any?>> = mutableListOf()
var prefixNullCount = 0
for (i in 0 until sizes.size) {
val prefixNulls = NullGenerator<Any>(prefixNullCount)
val suffixNulls = NullGenerator<Any>(totalSize - prefixNullCount - sizes[i])
output.add(Generators.merge(prefixNulls, generators[i], suffixNulls))
prefixNullCount += sizes[i]
}
return output
}
class PermutationScope : Iterator<List<Message<*, *>>> {
val frames: MutableList<Message<*, *>> = mutableListOf()
private val registeredGenerators: MutableList<Generator<*>> = mutableListOf()
private var generatorIndex = 0
private var snapshotIndex = 0
private var targetSnapshotCount = 1
override fun hasNext(): Boolean {
return snapshotIndex < targetSnapshotCount
}
override fun next(): List<Message<*, *>> {
generatorIndex = 0
snapshotIndex++
return frames.toList().also {
frames.clear()
}
}
fun <T> somePermutation(init: PermutationScope.() -> Unit): T {
val snapshots = permuteSingle<T> { init() }
return some(ListGenerator(snapshots))
}
fun <T> someNullablePermutation(init: PermutationScope.() -> Unit): T? {
val snapshots = permuteSingle<T> { init() }
return some(ListGenerator(snapshots).nullable())
}
fun someString(): String = some(Generators.strings())
fun someNonEmptyString(): String = some(Generators.nonEmptyStrings())
fun someNullableString(): String? = some(Generators.strings().nullable())
fun someEmoji(): String = some(Generators.emoji())
fun someBoolean(): Boolean = some(Generators.booleans())
fun someNullableBoolean(): Boolean? = some(Generators.booleans().nullable())
fun someInt(lower: Int = Int.MIN_VALUE, upper: Int = Int.MAX_VALUE): Int = some(Generators.ints(lower, upper))
fun someNullableInt(): Int? = some(Generators.ints().nullable())
fun somePositiveInt(): Int = some(Generators.ints(0))
fun somePositiveLong(): Long = some(Generators.longs(0))
fun someFloat(lower: Float = Float.MIN_VALUE, upper: Float = Float.MAX_VALUE): Float = some(Generators.floats(lower, upper))
fun someBytes(size: Int): ByteArray = some(Generators.bytes(size))
fun someNullableBytes(size: Int): ByteArray? = some(Generators.bytes(size).nullable())
fun someUrl(): String = some(Generators.urls())
fun someTimestamp(): Long = some(Generators.timestamps())
fun someNonZeroTimestamp(): Long = some(Generators.nonZeroTimestamps())
fun someIncrementingTimestamp(): Long = some(Generators.incrementingTimestamps())
fun someDecrementingTimestamp(lower: Long = 1659383695000, upper: Long = 1911844456000): Long = some(Generators.decrementingTimestamps(lower, upper))
fun someE164(): Long = some(Generators.e164s())
fun someUuid(): UUID = some(Generators.uuids())
fun someColor(): Int = some(Generators.colors())
fun someNullableUsername(): String? = some(Generators.usernames().nullable())
fun <T> someEnum(clazz: Class<T>, excluding: T? = null): T = excluding?.let { some(Generators.enum(clazz, excluding)) } ?: some(Generators.enum(clazz))
fun <T> someEnum(clazz: Class<T>, vararg excluding: T): T = some(Generators.enum(clazz, *excluding))
fun <T> someNullableEnum(clazz: Class<T>): T? = some(Generators.enum(clazz).nullable())
fun someNullableFilePointer(): FilePointer? = some(Generators.filePointer().nullable())
fun <T> some(generator: Generator<T>): T {
if (snapshotIndex == 0) {
registeredGenerators.add(generator)
targetSnapshotCount = max(targetSnapshotCount, generator.minSize)
}
return registeredGenerators[generatorIndex++].next() as T
}
fun <T> someOneOf(generator: Generator<Any?>): T? = some(generator as Generator<T?>)
}
fun <T> Generator<T>.nullable(): Generator<T?> {
return Generators.merge(NullGenerator(1), this as Generator<T?>)
}
fun <T> Generator<T>.asList(vararg sizes: Int): Generator<List<T>> {
val target = this
return object : Generator<List<T>> {
private var sizeIndex = 0
override val minSize: Int = sizes.size
override fun next(): List<T> = (0 until sizes[sizeIndex]).map { target.next() }.also { sizeIndex = (sizeIndex + 1) % sizes.size }
}
}
fun <T, R> Generator<T>.map(transform: (T) -> R): Generator<R> {
val target = this
return object : Generator<R> {
override val minSize: Int = target.minSize
override fun next(): R = transform(target.next())
}
}
fun <T> T.asGenerator(): Generator<T> {
return Generators.single(this)
}
/**
* A generator is a class that generates an infinite sequence of values of type [T].
*
* While it should always produce a value, the generator specifies a [minSize], indicating the minimum number of values
* it should generate before being considered "complete".
*/
interface Generator<T> {
val minSize: Int
fun next(): T
}
private class ListGenerator<T>(val list: List<T>) : Generator<T> {
private var index = 0
override val minSize: Int = list.size
override fun next(): T = list[index++ % list.size]
}
private class NullGenerator<T>(override val minSize: Int) : Generator<T?> {
override fun next(): T? = null
}
private class UuidGenerator : Generator<UUID> {
override val minSize: Int = 1
override fun next(): UUID = SeededRandom.uuid()
}
private class ByteGenerator(private val numBytes: Int, override val minSize: Int = 1) : Generator<ByteArray> {
override fun next(): ByteArray = SeededRandom.bytes(numBytes)
}
private class IncrementingTimestampGenerator : Generator<Long> {
private var onDeck = SeededRandom.long(lower = 1659383695000, upper = 1911844456000)
override val minSize: Int = 2
override fun next(): Long = onDeck.also { onDeck += 1 }
}
private class DecrementingTimestampGenerator(lower: Long, upper: Long) : Generator<Long> {
private var onDeck = SeededRandom.long(lower = lower, upper = upper)
override val minSize: Int = 2
override fun next(): Long = onDeck.also { onDeck -= 1 }
}
object Generators {
fun strings(): Generator<String> = Generators.list("", SeededRandom.string(), SeededRandom.string())
fun nonEmptyStrings(): Generator<String> = Generators.list(SeededRandom.string(), SeededRandom.string())
fun emoji(): Generator<String> = Generators.list("\uD83D\uDC80", "👍", "👎")
fun usernames(): Generator<String> = Generators.list("${SeededRandom.string()}.${SeededRandom.int(10, 100)}", "${SeededRandom.string()}.${SeededRandom.int(10, 100)}")
fun booleans(): Generator<Boolean> = Generators.list(true, false)
fun ints(lower: Int = Int.MIN_VALUE, upper: Int = Int.MAX_VALUE): Generator<Int> = Generators.list(SeededRandom.int(lower, upper), SeededRandom.int(lower, upper), SeededRandom.int(lower, upper))
fun longs(lower: Long = Long.MIN_VALUE, upper: Long = Long.MAX_VALUE): Generator<Long> = Generators.list(0L, SeededRandom.long(lower, upper), SeededRandom.long(lower, upper))
fun nonZeroLongs(lower: Long = Long.MIN_VALUE, upper: Long = Long.MAX_VALUE): Generator<Long> = Generators.list(SeededRandom.long(lower, upper), SeededRandom.long(lower, upper), SeededRandom.long(lower, upper))
fun floats(lower: Float = Float.MIN_VALUE, upper: Float = Float.MAX_VALUE): Generator<Float> = Generators.list(0f, SeededRandom.float(lower, upper), SeededRandom.float(lower, upper))
fun bytes(numBytes: Int, minSize: Int = 1): Generator<ByteArray> = ByteGenerator(numBytes, minSize)
fun <T> enum(clazz: Class<T>, excluding: T? = null): Generator<T> = ListGenerator(clazz.enumConstants.filterNot { it == excluding }.toList())
fun <T> enum(clazz: Class<T>, vararg excluding: T): Generator<T> = ListGenerator(clazz.enumConstants.filterNot { excluding.contains(it) }.toList())
fun urls(): Generator<String> = Generators.list("", "https://example.com/" + SeededRandom.string(), "https://example.com/" + SeededRandom.string())
fun timestamps(): Generator<Long> = Generators.list(0L, SeededRandom.long(lower = 1659383695000, upper = 1911844456000), SeededRandom.long(lower = 1659383695000, upper = 1911844456000))
fun nonZeroTimestamps(): Generator<Long> = Generators.list(SeededRandom.long(lower = 1659383695000, upper = 1911844456000), SeededRandom.long(lower = 1659383695000, upper = 1911844456000))
fun incrementingTimestamps(): Generator<Long> = IncrementingTimestampGenerator()
fun decrementingTimestamps(lower: Long = 1659383695000, upper: Long = 1911844456000): Generator<Long> = DecrementingTimestampGenerator(lower, upper)
fun e164s(): Generator<Long> = Generators.list(seededRandomE164(), seededRandomE164())
fun uuids(): Generator<UUID> = UuidGenerator()
fun cdnNumbers(): Generator<Int> = Generators.list(0, 2, 3)
fun emails(): Generator<String> = Generators.list("${SeededRandom.string()}@${SeededRandom.string()}.com", "${SeededRandom.string()}@${SeededRandom.string()}.org")
fun blurHashes(): Generator<String> = Generators.list("LfLh6Voa9NIW?wNF-ooL-;WAX8oy", "LGG*f,-i.l-o?G\$~?Zt7pHN1=tE3", "LdIOX?NE9Y4T~pRPRjE1X9f5jrt6", "LJR,66e.~Cxu%LoLM|S2%3WWIosm", "LIM:}RB8?-^L.d4]O.nkK_ruI?od")
fun contentTypes(): Generator<String> = Generators.list("image/jpeg", "image/png", "image/gif", "audio/mp3", "video/mp4")
fun picoMobs(): Generator<String> = Generators.list(SeededRandom.string(18, 25, "123456789"), SeededRandom.string(18, 25, "123456789"))
fun colors(): Generator<Int> = Generators.list(seededRandomColor(), seededRandomColor(), seededRandomColor())
fun <T> list(vararg items: T): Generator<T> = ListGenerator(items.toList())
fun <T> list(items: List<T>): Generator<T> = ListGenerator(items)
fun <T> single(item: T): Generator<T> = Generators.list(item)
fun <T> permutation(snapshotCount: Int = -1, init: PermutationScope.() -> Unit): Generator<T> {
val snapshots = permuteSingle<T> { init() }
return ListGenerator(snapshots)
}
fun <T> merge(vararg generators: Generator<T>): Generator<T> {
val allItems: List<T> = mutableListOf<T>().apply {
generators.forEach { generator ->
repeat(generator.minSize) {
add(generator.next())
}
}
}
return ListGenerator(allItems)
}
fun filePointer(contentTypeGenerator: Generator<String> = Generators.contentTypes()): Generator<FilePointer> {
val (backupLocatorGenerator, attachmentLocatorGenerator, invalidAttachmentLocatorGenerator) = oneOf(
Generators.permutation {
val transitCdnKey = some(Generators.nonEmptyStrings().nullable())
val transitCdnNumber = some(Generators.cdnNumbers().nullable())
val digest = someBytes(16)
frames += FilePointer.BackupLocator(
mediaName = digest.toHexString(),
cdnNumber = some(Generators.cdnNumbers()),
key = someBytes(16).toByteString(),
digest = digest.toByteString(),
size = somePositiveInt(),
transitCdnNumber = if (transitCdnKey != null) {
transitCdnNumber
} else {
null
},
transitCdnKey = transitCdnKey
)
},
Generators.permutation {
frames += FilePointer.AttachmentLocator(
cdnKey = someNonEmptyString(),
cdnNumber = some(Generators.cdnNumbers()),
uploadTimestamp = someTimestamp(),
key = someBytes(16).toByteString(),
digest = someBytes(16).toByteString(),
size = somePositiveInt()
)
},
Generators.single(FilePointer.InvalidAttachmentLocator())
)
return Generators.permutation {
val incrementalMac: ByteArray? = some(Generators.bytes(16).nullable())
val incrementalMacChunkSize: Int? = some(Generators.list(1024, 2048))
val contentType = some(contentTypeGenerator)
val blurHash = some(Generators.blurHashes().nullable())
frames += FilePointer(
backupLocator = someOneOf(backupLocatorGenerator),
attachmentLocator = someOneOf(attachmentLocatorGenerator),
invalidAttachmentLocator = someOneOf(invalidAttachmentLocatorGenerator),
contentType = contentType,
incrementalMac = incrementalMac?.toByteString(),
incrementalMacChunkSize = if (incrementalMac != null) {
incrementalMacChunkSize
} else {
null
},
fileName = some(Generators.nonEmptyStrings().nullable()),
width = some(Generators.ints(0, 4096).nullable()),
height = some(Generators.ints(0, 4096).nullable()),
caption = someNullableString(),
blurHash = if (contentType.startsWith("audio")) null else blurHash
)
}
}
fun sendStatus(recipientIdGenerator: Generator<Long>): Generator<SendStatus> {
val (
pendingGenerator,
sentGenerator,
deliveredGenerator,
readGenerator,
viewedGenerator,
skippedGenerator,
failedGenerator
) = oneOf(
Generators.list(SendStatus.Pending()),
Generators.permutation {
frames += SendStatus.Sent(
sealedSender = someBoolean()
)
},
Generators.permutation {
frames += SendStatus.Delivered(
sealedSender = someBoolean()
)
},
Generators.permutation {
frames += SendStatus.Read(
sealedSender = someBoolean()
)
},
Generators.permutation {
frames += SendStatus.Viewed(
sealedSender = someBoolean()
)
},
Generators.list(SendStatus.Skipped()),
Generators.permutation {
frames += SendStatus.Failed(
reason = someEnum(SendStatus.Failed.FailureReason::class.java)
)
}
)
return Generators.permutation {
frames += SendStatus(
recipientId = some(recipientIdGenerator),
timestamp = someTimestamp(),
pending = someOneOf(pendingGenerator),
sent = someOneOf(sentGenerator),
delivered = someOneOf(deliveredGenerator),
read = someOneOf(readGenerator),
viewed = someOneOf(viewedGenerator),
skipped = someOneOf(skippedGenerator),
failed = someOneOf(failedGenerator)
)
}
}
/**
* Generates a pair of generators that can be used for setting incoming/outgoing message details.
* For outgoing statuses, one will be made for each of the outgoing recipients.
*/
fun incomingOutgoingDetails(vararg outgoingRecipients: Recipient): Pair<Generator<ChatItem.IncomingMessageDetails?>, Generator<ChatItem.OutgoingMessageDetails?>> {
val sendStatusGenerators = outgoingRecipients.map { outgoingRecipient ->
Generators.sendStatus(
recipientIdGenerator = Generators.single(outgoingRecipient.id)
)
}
val (incoming, outgoing) = oneOf(
Generators.permutation {
frames += ChatItem.IncomingMessageDetails(
dateReceived = someIncrementingTimestamp(),
dateServerSent = someIncrementingTimestamp(),
read = someBoolean(),
sealedSender = someBoolean()
)
},
Generators.permutation {
frames += ChatItem.OutgoingMessageDetails(
sendStatus = sendStatusGenerators.map { some(it) }
)
}
)
return incoming as Generator<ChatItem.IncomingMessageDetails?> to outgoing as Generator<ChatItem.OutgoingMessageDetails?>
}
fun reactions(maxReactions: Int, vararg authors: Recipient): Generator<List<Reaction>> {
return Generators.permutation<Reaction> {
frames += Reaction(
emoji = someEmoji(),
authorId = some(Generators.list(*authors.map { it.id }.toTypedArray())),
sentTimestamp = someIncrementingTimestamp(),
sortOrder = some(
Generators.merge(
Generators.list(1, 2),
Generators.timestamps()
)
)
)
}.asList(*(0..maxReactions).toList().toIntArray())
}
/**
* Returns a generator that generates a list of T. The size of the lists it outputs will be determined by [sizes].
* It will use a fresh generator for each list, generated by [generatorFactory].
*
* Note that this is different from [Generator.asList], which does _not_ reset the generator for each list, instead
* using the same generator throughout.
*/
fun <T> lists(sizes: List<Int>, generatorFactory: () -> Generator<T>): Generator<List<T>> {
return object : Generator<List<T>> {
var sizeIndex = 0
override val minSize: Int = sizes.size
override fun next(): List<T> {
val generator = generatorFactory()
return (0 until sizes[sizeIndex])
.map { generator.next() }
.also { sizeIndex = (sizeIndex + 1) % sizes.size }
}
}
}
/** Generates a random e164 in the protected test number range */
private fun seededRandomE164(): Long {
return when (SeededRandom.int(0, 3)) {
0 -> "1${SeededRandom.int(100, 1000)}55501${SeededRandom.int(10, 100)}"
1 -> "447700900${SeededRandom.int(100, 1000)}"
2 -> "3363998${SeededRandom.int(1000, 10_000)}"
else -> throw AssertionError("Unreachable")
}.toLong()
}
private fun seededRandomColor(): Int {
return ("FF" + SeededRandom.string(from = 6, until = 7, characterSet = "0123456789ABCDEF")).hexToInt()
}
}
private operator fun <T> List<T>.component6(): T = this[5]
private operator fun <T> List<T>.component7(): T = this[6]

View File

@ -0,0 +1,39 @@
import java.util.*
import kotlin.random.Random
object SeededRandom {
private var random = Random(0)
fun reset(seed: Long = 0) {
random = Random(seed)
}
fun uuid(): UUID {
return UUID(random.nextLong(), random.nextLong())
}
fun bytes(length: Int): ByteArray {
return ByteArray(length).also { random.nextBytes(it) }
}
fun string(from: Int = 5, until: Int = 15, characterSet: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"): String {
val length = random.nextInt(from, until)
return (1..length)
.map { characterSet.random(random) }
.joinToString("")
}
fun long(lower: Long = Long.MIN_VALUE, upper: Long = Long.MAX_VALUE): Long {
return random.nextLong(lower, upper)
}
fun int(lower: Int = Int.MIN_VALUE, upper: Int = Int.MAX_VALUE): Int {
return random.nextInt(lower, upper)
}
fun float(lower: Float = Float.MIN_VALUE, upper: Float = Float.MAX_VALUE): Float {
val diff = upper - lower
return lower + (random.nextFloat() * diff)
}
}

View File

@ -0,0 +1,274 @@
@file:OptIn(ExperimentalStdlibApi::class)
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.backup.v2.proto.*
import java.util.*
/**
* Defines a set of standard frames that can be used in various tests.
* Helpful when you need an arbitrary recipient, chat, etc.
*/
object StandardFrames {
private val MY_STORY_UUID: UUID = UUID.fromString("00000000-0000-0000-0000-000000000000")
val SELF_PROFILE_KEY: ByteArray = base64Decode("YQKRq+3DQklInaOaMcmlzZnN0m/1hzLiaONX7gB12dg=")
val SELF_ACI: ByteArray = UUID(100, 100).toByteArray()
val SELF_PNI: ByteArray = UUID(101, 101).toByteArray()
val backupInfo = BackupInfo(
version = 1,
backupTimeMs = 1715636551000
)
val recipientSelf = Frame(
recipient = Recipient(
id = 1,
self = Self()
)
)
val recipientReleaseNotes = Frame(
recipient = Recipient(
id = 2,
releaseNotes = ReleaseNotes()
)
)
val recipientMyStory = Frame(
recipient = Recipient(
id = 3,
distributionList = DistributionListItem(
distributionId = MY_STORY_UUID.toByteArray().toByteString(),
distributionList = DistributionList(
name = "My Story",
privacyMode = DistributionList.PrivacyMode.ALL
)
)
)
)
val accountData = Frame(
account = AccountData(
profileKey = SELF_PROFILE_KEY.toByteString(),
username = "boba_fett.66",
usernameLink = AccountData.UsernameLink(
entropy = base64Decode("ZWdcc9AOsBAF47t8SkfylstlVPeJgSOIFekV2CT9LpM=").toByteString(),
serverId = base64Decode("YcEBogDVQheJwgUY2El68A==").toByteString(),
color = AccountData.UsernameLink.Color.OLIVE
),
givenName = "Boba",
familyName = "Fett",
avatarUrlPath = "",
donationSubscriberData = AccountData.SubscriberData(
subscriberId = base64Decode("7LtoxzQzGi6jM82nR8mMRVNlImFYK0/OWuDeqE3OZRk=").toByteString(),
currencyCode = "USD",
manuallyCancelled = true
),
// TODO
// backupsSubscriberData = AccountData.SubscriberData(
// subscriberId = base64Decode("7LtoxzQzGi6jM82nR8mMRVNlImFYK0/OWuDeqE3OZRk=").toByteString(),
// currencyCode = "USD",
// manuallyCancelled = false
// ),
accountSettings = AccountData.AccountSettings(
readReceipts = true,
sealedSenderIndicators = true,
typingIndicators = true,
linkPreviews = true,
notDiscoverableByPhoneNumber = true,
preferContactAvatars = true,
universalExpireTimerSeconds = 3600,
preferredReactionEmoji = listOf("a", "b", "c"),
displayBadgesOnProfile = true,
keepMutedChatsArchived = true,
hasSetMyStoriesPrivacy = true,
hasViewedOnboardingStory = true,
storiesDisabled = true,
storyViewReceiptsEnabled = true,
hasSeenGroupStoryEducationSheet = true,
hasCompletedUsernameOnboarding = true,
phoneNumberSharingMode = AccountData.PhoneNumberSharingMode.NOBODY,
customChatColors = listOf(
ChatStyle.CustomChatColor(
id = 1,
solid = "FF000000".hexToInt()
),
ChatStyle.CustomChatColor(
id = 2,
solid = "FFFF0000".hexToInt()
),
ChatStyle.CustomChatColor(
id = 3,
solid = "FF00FF00".hexToInt()
)
)
)
)
)
val MANDATORY_FRAMES = listOf(
backupInfo,
accountData,
recipientSelf,
recipientReleaseNotes,
recipientMyStory
)
val chatSelf = Frame(
chat = Chat(
id = 1,
recipientId = recipientSelf.recipient!!.id
)
)
val recipientAlice = Frame(
recipient = Recipient(
id = 4,
contact = Contact(
aci = UUID(0, 1).toByteArray().toByteString(),
pni = UUID(0, 2).toByteArray().toByteString(),
e164 = 16105550101,
profileGivenName = "Alice",
profileFamilyName = "Smith",
registered = Contact.Registered(),
profileKey = base64Decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=").toByteString()
)
)
)
val recipientBob = Frame(
recipient = Recipient(
id = 5,
contact = Contact(
aci = UUID(0, 3).toByteArray().toByteString(),
pni = UUID(0, 4).toByteArray().toByteString(),
e164 = 16105550102,
profileGivenName = "Bob",
profileFamilyName = "Jones",
registered = Contact.Registered(),
profileKey = base64Decode("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=").toByteString()
)
)
)
val recipientCarol = Frame(
recipient = Recipient(
id = 6,
contact = Contact(
aci = UUID(0, 5).toByteArray().toByteString(),
pni = UUID(0, 6).toByteArray().toByteString(),
e164 = 16105550103,
profileGivenName = "Carol",
profileFamilyName = "Johnson",
registered = Contact.Registered(),
profileKey = base64Decode("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=").toByteString()
)
)
)
val recipientDan = Frame(
recipient = Recipient(
id = 7,
contact = Contact(
aci = UUID(0, 7).toByteArray().toByteString(),
pni = UUID(0, 8).toByteArray().toByteString(),
e164 = 16105550104,
profileGivenName = "Dan",
profileFamilyName = "Brown",
registered = Contact.Registered(),
profileKey = base64Decode("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=").toByteString()
)
)
)
val recipientEve = Frame(
recipient = Recipient(
id = 8,
contact = Contact(
aci = UUID(0, 9).toByteArray().toByteString(),
pni = UUID(0, 10).toByteArray().toByteString(),
e164 = 16105550105,
profileGivenName = "Eve",
profileFamilyName = "Green",
registered = Contact.Registered(),
profileKey = base64Decode("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE=").toByteString()
)
)
)
val recipientFrank = Frame(
recipient = Recipient(
id = 9,
contact = Contact(
aci = UUID(0, 11).toByteArray().toByteString(),
pni = UUID(0, 12).toByteArray().toByteString(),
e164 = 16105550106,
profileGivenName = "Frank",
profileFamilyName = "Johnson",
registered = Contact.Registered(),
profileKey = base64Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=").toByteString()
)
)
)
/** A group with you, Alice, and Bob */
val recipientGroupAB = Frame(
recipient = Recipient(
id = 10,
group = Group(
masterKey = ByteArray(32) { 0 }.toByteString(),
whitelisted = true,
snapshot = Group.GroupSnapshot(
title = Group.GroupAttributeBlob(
title = "Me, Alice, Bob"
),
avatarUrl = "https://example.com/avatar.jpg",
version = 1,
members = listOf(
Group.Member(
userId = SELF_ACI.toByteString(),
role = Group.Member.Role.ADMINISTRATOR,
profileKey = recipientAlice.recipient!!.contact!!.profileKey!!
),
Group.Member(
userId = recipientAlice.recipient!!.contact!!.aci!!,
role = Group.Member.Role.DEFAULT,
profileKey = recipientAlice.recipient!!.contact!!.profileKey!!
),
Group.Member(
userId = recipientBob.recipient!!.contact!!.aci!!,
role = Group.Member.Role.DEFAULT,
profileKey = recipientBob.recipient!!.contact!!.profileKey!!
)
)
)
)
)
)
val recipientCallLink = Frame(
recipient = Recipient(
callLink = CallLink(
rootKey = ByteArray(16) { 0 }.toByteString(),
adminKey = ByteArray(32) { 1 }.toByteString(),
name = "Test Call Link",
restrictions = CallLink.Restrictions.NONE,
expirationMs = 0
)
)
)
val chatAlice = Frame(
chat = Chat(
id = 2,
recipientId = recipientAlice.recipient!!.id
)
)
val chatGroupAB = Frame(
chat = Chat(
id = 3,
recipientId = recipientGroupAB.recipient!!.id
)
)
}

View File

@ -0,0 +1,17 @@
/**
* Base class for a test case. Each test should strive to test a specific "category" of backup items.
*
* Note that tests should be deterministic. All the prebuilt generators are already deterministic, using a seeded
* random generator. If you need your own random values outside the context of a generator, be sure to use [SeededRandom].
*
* @param baseFileName The root of the file name that will be used for test output. If the value is "foo", the output
* will be "foo_00.binproto", "foo_01.binproto", etc.
*/
abstract class TestCase(val baseFileName: String) {
fun initialize() {
SeededRandom.reset()
}
abstract fun PermutationScope.execute()
}

97
src/main/kotlin/Util.kt Normal file
View File

@ -0,0 +1,97 @@
import okio.ByteString
import java.io.OutputStream
import java.nio.ByteBuffer
import java.util.Base64
import java.util.UUID
import kotlin.reflect.full.declaredMemberProperties
/**
* Writes a 32-bit variable-length integer to the stream.
*
* The format uses one byte for each 7 bits of the integer, with the most significant bit (MSB) of each byte indicating whether more bytes need to be read.
*/
fun OutputStream.writeVarInt32(value: Int) {
var remaining = value
while (true) {
// We write 7 bits of the integer at a time
val lowestSevenBits = remaining and 0x7F
remaining = remaining ushr 7
if (remaining == 0) {
// If there are no more bits to write, we're done
write(lowestSevenBits)
return
} else {
// Otherwise, we need to write the next 7 bits, and set the MSB to 1 to indicate that there are more bits to come
write(lowestSevenBits or 0x80)
}
}
}
fun UUID.toByteArray(): ByteArray {
val buffer: ByteBuffer = ByteBuffer.wrap(ByteArray(16))
buffer.putLong(this.mostSignificantBits)
buffer.putLong(this.leastSignificantBits)
return buffer.array()
}
fun base64Decode(value: String): ByteArray {
return Base64.getDecoder().decode(value)
}
/**
* Takes a class and converts it to a nice, multi-line string with good indentation. Optimized for readability.
*/
@OptIn(ExperimentalStdlibApi::class)
fun Any?.prettyPrint(indent: String = ""): String {
when (this) {
null -> return "${indent}null"
is Int,
is Long,
is Float,
is Double,
is Boolean -> return "${indent}$this"
is String -> return "${indent}\"$this\""
is ByteArray -> return "<${this.toHexString(HexFormat.Default)}>"
is ByteString -> return "<${this.toByteArray().toHexString(HexFormat.Default)}>"
is Enum<*> -> return "${indent}${this::class.simpleName}.${this.name}"
}
val clazz = this!!::class
val fields = clazz.declaredMemberProperties
if (fields.isEmpty()) {
return "${indent}${clazz.simpleName} {}"
}
val fieldsString = clazz.declaredMemberProperties
.filter {
when (val value = it.getter.call(this)) {
is List<*> -> value.isNotEmpty()
is Boolean -> value != false
is Long -> value != 0L
is Int -> value != 0
else -> value != null
}
}
.joinToString(separator = "\n") { field ->
when (val value = field.getter.call(this)) {
is List<*> -> {
if (value.isEmpty()) {
"$indent ${field.name} = []"
} else {
"$indent ${field.name} = [\n${value.joinToString(separator = ",\n") { it.prettyPrint(indent = "$indent ") }}\n$indent ]"
}
}
else -> {
"$indent ${field.name} = ${value.prettyPrint(indent = "$indent ")}"
}
}
}
return "$indent${clazz.simpleName} {\n$fieldsString\n$indent}"
.replace(Regex("=\\s+"), "= ")
.replace(Regex("\\{\\s+}", RegexOption.MULTILINE), "{}")
}

View File

@ -0,0 +1,126 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generator
import Generators
import PermutationScope
import TestCase
import nullable
import okio.ByteString.Companion.toByteString
import oneOf
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
import org.thoughtcrime.securesms.backup.v2.proto.Frame
/**
* Every reasonable permutation of AccountData.
*/
object AccountDataTestCase : TestCase("account_data") {
override fun PermutationScope.execute() {
frames += StandardFrames.backupInfo
val (wallpaperPhotoGenerator, wallpaperPresetGenerator) = oneOf(
Generators.filePointer() as Generator<Any?>,
Generators.enum(ChatStyle.WallpaperPreset::class.java, excluding = ChatStyle.WallpaperPreset.UNKNOWN_WALLPAPER_PRESET) as Generator<Any?>
)
val (bubbleAutoGenerator, bubblePresetGenerator, bubbleCustomGenerator) = oneOf(
Generators.list(ChatStyle.AutomaticBubbleColor()),
Generators.enum(ChatStyle.BubbleColorPreset::class.java, excluding = ChatStyle.BubbleColorPreset.UNKNOWN_BUBBLE_COLOR_PRESET) as Generator<Any?>,
Generators.list(1L, 2L, 3L) as Generator<Any?>
)
val usernameLinkGenerator: Generator<AccountData.UsernameLink?> = Generators.permutation<AccountData.UsernameLink?> {
frames += AccountData.UsernameLink(
entropy = someBytes(32).toByteString(),
serverId = someBytes(16).toByteString(),
color = someEnum(AccountData.UsernameLink.Color::class.java, excluding = AccountData.UsernameLink.Color.UNKNOWN)
)
}.nullable()
val usernameLink = some(usernameLinkGenerator)
val username = some(Generators.usernames())
frames += Frame(
account = AccountData(
profileKey = someBytes(32).toByteString(),
username = if (usernameLink != null) {
username
} else {
null
},
usernameLink = usernameLink,
givenName = someString(),
familyName = someString(),
avatarUrlPath = someUrl(),
donationSubscriberData = someNullablePermutation {
frames += AccountData.SubscriberData(
subscriberId = someBytes(32).toByteString(),
currencyCode = some(Generators.list("USD", "EUR", "GBP")),
manuallyCancelled = someBoolean()
)
},
backupsSubscriberData = someNullablePermutation {
frames += AccountData.SubscriberData(
subscriberId = someBytes(32).toByteString(),
currencyCode = some(Generators.list("USD", "EUR", "GBP")),
manuallyCancelled = someBoolean()
)
},
accountSettings = AccountData.AccountSettings(
readReceipts = someBoolean(),
sealedSenderIndicators = someBoolean(),
typingIndicators = someBoolean(),
linkPreviews = someBoolean(),
notDiscoverableByPhoneNumber = someBoolean(),
preferContactAvatars = someBoolean(),
universalExpireTimerSeconds = somePositiveInt(),
preferredReactionEmoji = listOf(),
displayBadgesOnProfile = someBoolean(),
keepMutedChatsArchived = someBoolean(),
hasSetMyStoriesPrivacy = someBoolean(),
hasViewedOnboardingStory = someBoolean(),
storiesDisabled = someBoolean(),
storyViewReceiptsEnabled = someBoolean(),
hasSeenGroupStoryEducationSheet = someBoolean(),
hasCompletedUsernameOnboarding = someBoolean(),
phoneNumberSharingMode = someEnum(AccountData.PhoneNumberSharingMode::class.java, excluding = AccountData.PhoneNumberSharingMode.UNKNOWN),
defaultChatStyle = someNullablePermutation {
frames += ChatStyle(
wallpaperPreset = someOneOf(wallpaperPresetGenerator),
wallpaperPhoto = someOneOf(wallpaperPhotoGenerator),
autoBubbleColor = someOneOf(bubbleAutoGenerator),
bubbleColorPreset = someOneOf(bubblePresetGenerator),
customColorId = someOneOf(bubbleCustomGenerator),
dimWallpaperInDarkMode = someBoolean()
)
},
customChatColors = listOf(
ChatStyle.CustomChatColor(
id = 1,
solid = someColor()
),
ChatStyle.CustomChatColor(
id = 2,
solid = someColor()
),
ChatStyle.CustomChatColor(
id = 3,
gradient = ChatStyle.Gradient(
angle = someInt(0, 360),
colors = listOf(someColor(), someColor()),
positions = listOf(0f, 1f)
)
)
)
)
)
)
frames += StandardFrames.recipientSelf
frames += StandardFrames.recipientReleaseNotes
frames += StandardFrames.recipientMyStory
}
}

View File

@ -0,0 +1,30 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generators
import PermutationScope
import StandardFrames
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall
import org.thoughtcrime.securesms.backup.v2.proto.Frame
/**
* Reasonable permutations of [AdHocCall] objects.
*/
object AdHocCallTestCase : TestCase("ad_hoc_call") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientCallLink
frames += Frame(
adHocCall = AdHocCall(
callId = some(Generators.nonZeroLongs(1, Long.MAX_VALUE)),
recipientId = StandardFrames.recipientCallLink.recipient!!.id,
state = someEnum(AdHocCall.State::class.java, excluding = AdHocCall.State.UNKNOWN_STATE),
callTimestamp = someTimestamp()
)
)
}
}

View File

@ -0,0 +1,108 @@
package tests
import PermutationScope
import TestCase
import asList
import oneOf
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.ContactAttachment
import org.thoughtcrime.securesms.backup.v2.proto.ContactMessage
import org.thoughtcrime.securesms.backup.v2.proto.Frame
/**
* Every reasonable permutation of ChatItem.ContactMessage
*/
object ChatItemContactMessageTestCase : TestCase("chat_item_contact_message") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val sendStatusGenerator = Generators.sendStatus(
recipientIdGenerator = Generators.single(StandardFrames.recipientAlice.recipient!!.id)
)
val (incomingGenerator, outgoingGenerator) = oneOf(
Generators.permutation {
frames += ChatItem.IncomingMessageDetails(
dateReceived = someIncrementingTimestamp(),
dateServerSent = someIncrementingTimestamp(),
read = someBoolean(),
sealedSender = someBoolean()
)
},
Generators.permutation {
frames += ChatItem.OutgoingMessageDetails(
sendStatus = listOf(some(sendStatusGenerator))
)
}
)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someTimestamp(),
incoming = incoming as ChatItem.IncomingMessageDetails?,
outgoing = outgoing as ChatItem.OutgoingMessageDetails?,
contactMessage = ContactMessage(
contact = Generators.permutation<ContactAttachment> {
frames += ContactAttachment(
name = ContactAttachment.Name(
givenName = someNonEmptyString(),
familyName = someNonEmptyString()
),
number = Generators.permutation<ContactAttachment.Phone> {
frames += ContactAttachment.Phone(
value_ = someE164().toString(),
type = someEnum(
ContactAttachment.Phone.Type::class.java,
excluding = ContactAttachment.Phone.Type.UNKNOWN
),
label = someNullableString()
)
}.asList(0, 1, 2).let { some(it) },
email = Generators.permutation<ContactAttachment.Email> {
frames += ContactAttachment.Email(
value_ = some(Generators.emails()),
type = someEnum(
ContactAttachment.Email.Type::class.java,
excluding = ContactAttachment.Email.Type.UNKNOWN
),
label = someNullableString()
)
}.asList(0, 1, 2).let { some(it) },
address = Generators.permutation<ContactAttachment.PostalAddress> {
frames += ContactAttachment.PostalAddress(
type = someEnum(
ContactAttachment.PostalAddress.Type::class.java,
excluding = ContactAttachment.PostalAddress.Type.UNKNOWN
),
label = someNullableString(),
street = someNonEmptyString(),
pobox = someNullableString(),
neighborhood = someNullableString(),
city = someNonEmptyString(),
region = someNullableString(),
postcode = someNullableString(),
country = someNullableString()
)
}.asList(0, 1, 2).let { some(it) },
organization = someNullableString(),
avatar = someNullableFilePointer()
)
}.asList(1, 2).let { some(it) }
)
)
)
}
}

View File

@ -0,0 +1,35 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generators
import PermutationScope
import StandardFrames
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Permutations of [ExpirationTimerChatUpdate] messages.
*/
object ChatItemExpirationTimerUpdateTestCase : TestCase("chat_item_expiration_timer_update") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = some(Generators.list(StandardFrames.recipientSelf.recipient!!.id, StandardFrames.recipientAlice.recipient!!.id)),
dateSent = someTimestamp(),
directionless = ChatItem.DirectionlessMessageDetails(),
updateMessage = ChatUpdateMessage(
expirationTimerChange = ExpirationTimerChatUpdate(
expiresInMs = somePositiveLong()
)
)
)
)
}
}

View File

@ -0,0 +1,43 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import PermutationScope
import TestCase
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Incoming/outgoing gift badges
*/
object ChatItemGiftBadgeTestCase : TestCase("chat_item_gift_badge") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(StandardFrames.recipientAlice.recipient!!)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
giftBadge = GiftBadge(
receiptCredentialPresentation = someBytes(32).toByteString(),
state = someEnum(GiftBadge.State::class.java)
)
)
)
}
}

View File

@ -0,0 +1,66 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generators
import PermutationScope
import StandardFrames
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Individual call updates
*/
object ChatItemGroupCallUpdateTestCase : TestCase("chat_item_group_call_update") {
private val ENDED_STATES = setOf(
GroupCall.State.DECLINED,
GroupCall.State.MISSED,
GroupCall.State.MISSED_NOTIFICATION_PROFILE
)
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.recipientBob
frames += StandardFrames.recipientGroupAB
frames += StandardFrames.chatGroupAB
val ringerGenerator = Generators.list(
StandardFrames.recipientAlice.recipient!!.id,
StandardFrames.recipientBob.recipient!!.id
)
val startedCallGenerator = Generators.list(
StandardFrames.recipientSelf.recipient!!.id,
StandardFrames.recipientAlice.recipient!!.id,
StandardFrames.recipientBob.recipient!!.id
)
val callState = someEnum(GroupCall.State::class.java, excluding = GroupCall.State.UNKNOWN_STATE)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatGroupAB.chat!!.id,
authorId = StandardFrames.recipientSelf.recipient!!.id,
directionless = ChatItem.DirectionlessMessageDetails(),
dateSent = someIncrementingTimestamp(),
updateMessage = ChatUpdateMessage(
groupCall = GroupCall(
callId = somePositiveLong(),
state = callState,
ringerRecipientId = some(ringerGenerator),
startedCallRecipientId = some(startedCallGenerator),
startedCallTimestamp = someNonZeroTimestamp(),
endedCallTimestamp = if (callState in ENDED_STATES) {
100L
} else {
0L
},
read = someBoolean()
)
)
)
)
}
}

View File

@ -0,0 +1,38 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import PermutationScope
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Individual call updates
*/
object ChatItemIndividualCallUpdateTestCase : TestCase("chat_item_individual_call_update") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = StandardFrames.recipientSelf.recipient!!.id,
directionless = ChatItem.DirectionlessMessageDetails(),
dateSent = someIncrementingTimestamp(),
updateMessage = ChatUpdateMessage(
individualCall = IndividualCall(
callId = somePositiveLong(),
type = someEnum(IndividualCall.Type::class.java, excluding = IndividualCall.Type.UNKNOWN_TYPE),
direction = someEnum(IndividualCall.Direction::class.java, excluding = IndividualCall.Direction.UNKNOWN_DIRECTION),
state = someEnum(IndividualCall.State::class.java, excluding = IndividualCall.State.UNKNOWN_STATE),
startedCallTimestamp = someNonZeroTimestamp(),
read = someBoolean()
)
)
)
)
}
}

View File

@ -0,0 +1,46 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generator
import Generators
import PermutationScope
import StandardFrames
import TestCase
import oneOf
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.LearnedProfileChatUpdate
/**
* Permutations of the [LearnedProfileChatUpdate] message.
*/
object ChatItemLearnedProfileUpdateTestCase : TestCase("chat_item_learned_profile_update") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (e164Generator, usernameGenerator) = oneOf(
Generators.e164s() as Generator<Any?>,
Generators.usernames() as Generator<Any?>
)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = StandardFrames.recipientSelf.recipient!!.id,
dateSent = someIncrementingTimestamp(),
directionless = ChatItem.DirectionlessMessageDetails(),
updateMessage = ChatUpdateMessage(
learnedProfileChange = LearnedProfileChatUpdate(
e164 = someOneOf(e164Generator),
username = someOneOf(usernameGenerator)
)
)
)
)
}
}

View File

@ -0,0 +1,75 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generators
import PermutationScope
import TestCase
import okio.ByteString.Companion.toByteString
import oneOf
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Incoming/outgoing payment update messages.
*/
object ChatItemPaymentNotificationTestCase : TestCase("chat_item_payment_notification") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(StandardFrames.recipientAlice.recipient!!)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
val (transactionGenerator, failedTransactionGenerator) = oneOf(
Generators.permutation {
val publicKey = listOf(some(Generators.bytes(32)).toByteString())
val keyImages = listOf(some(Generators.bytes(32)).toByteString())
frames += PaymentNotification.TransactionDetails.Transaction(
status = someEnum(PaymentNotification.TransactionDetails.Transaction.Status::class.java),
mobileCoinIdentification = PaymentNotification.TransactionDetails.MobileCoinTxoIdentification(
publicKey = publicKey.takeIf { incoming != null } ?: emptyList(),
keyImages = keyImages.takeIf { outgoing != null } ?: emptyList()
),
timestamp = someIncrementingTimestamp(),
blockIndex = somePositiveLong(),
blockTimestamp = someTimestamp(),
transaction = someBytes(32).toByteString(),
receipt = someBytes(32).toByteString()
)
},
Generators.permutation {
frames += PaymentNotification.TransactionDetails.FailedTransaction(
reason = someEnum(PaymentNotification.TransactionDetails.FailedTransaction.FailureReason::class.java)
)
}
)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
paymentNotification = PaymentNotification(
amountMob = some(Generators.picoMobs()),
feeMob = some(Generators.picoMobs()),
note = someNullableString(),
transactionDetails = PaymentNotification.TransactionDetails(
transaction = someOneOf(transactionGenerator),
failedTransaction = someOneOf(failedTransactionGenerator)
)
)
)
)
}
}

View File

@ -0,0 +1,35 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import PermutationScope
import StandardFrames
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Permutations of the [ProfileChangeChatUpdate] message.
*/
object ChatItemProfileChangeUpdateTestCase : TestCase("chat_item_profile_change_update") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = StandardFrames.recipientAlice.recipient!!.id,
dateSent = someTimestamp(),
directionless = ChatItem.DirectionlessMessageDetails(),
updateMessage = ChatUpdateMessage(
profileChange = ProfileChangeChatUpdate(
previousName = someNonEmptyString(),
newName = someNonEmptyString()
)
)
)
)
}
}

View File

@ -0,0 +1,39 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import PermutationScope
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Incoming/outgoing remote deleted messages.
*/
object ChatItemRemoteDeleteTestCase : TestCase("chat_item_remote_delete") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(StandardFrames.recipientAlice.recipient!!)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
remoteDeletedMessage = RemoteDeletedMessage()
)
)
}
}

View File

@ -0,0 +1,34 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import PermutationScope
import StandardFrames
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Permutations of the [SessionSwitchoverChatUpdate] message.
*/
object ChatItemSessionSwitchoverUpdateTestCase : TestCase("chat_item_session_switchover_update") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = StandardFrames.recipientAlice.recipient!!.id,
dateSent = someTimestamp(),
directionless = ChatItem.DirectionlessMessageDetails(),
updateMessage = ChatUpdateMessage(
sessionSwitchover = SessionSwitchoverChatUpdate(
e164 = someE164()
)
)
)
)
}
}

View File

@ -0,0 +1,35 @@
package tests
import PermutationScope
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.SimpleChatUpdate
/**
* All simple chat updates.
*/
object ChatItemSimpleUpdatesTestCase : TestCase("chat_item_simple_updates") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = StandardFrames.recipientAlice.recipient!!.id,
dateSent = someTimestamp(),
directionless = ChatItem.DirectionlessMessageDetails(),
updateMessage = ChatUpdateMessage(
simpleUpdate = SimpleChatUpdate(
type = someEnum(SimpleChatUpdate.Type::class.java, excluding = SimpleChatUpdate.Type.UNKNOWN)
)
)
)
)
}
}

View File

@ -0,0 +1,69 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generator
import Generators
import PermutationScope
import TestCase
import asList
import oneOf
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Incoming/outgoing text-only messages with formatted text. Excludes mentions.
*/
object ChatItemStandardMessageFormattedTextTestCase : TestCase("chat_item_standard_message_formatted_text") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.recipientBob
frames += StandardFrames.recipientGroupAB
frames += StandardFrames.chatGroupAB
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(
StandardFrames.recipientAlice.recipient!!,
StandardFrames.recipientBob.recipient!!
)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
val (mentionGenerator, styleGenerator) = oneOf(
Generators.list(
StandardFrames.recipientAlice.recipient.contact!!.aci,
StandardFrames.recipientBob.recipient.contact!!.aci
),
Generators.enum(BodyRange.Style::class.java) as Generator<Any?>
)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatGroupAB.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
standardMessage = StandardMessage(
text = Text(
body = "0123456789",
bodyRanges = Generators.permutation<BodyRange> {
frames += BodyRange(
start = some(Generators.list(0, 0, 0, 1, 2, 3)),
length = some(Generators.list(1, 5, 10, 5, 7, 3)),
style = someOneOf(styleGenerator),
mentionAci = someOneOf(mentionGenerator)
)
}.asList(0, 1, 2, 3, 4, 5).let { some(it) }
)
)
)
)
}
}

View File

@ -0,0 +1,47 @@
package tests
import PermutationScope
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
import org.thoughtcrime.securesms.backup.v2.proto.Text
/**
* Incoming/outgoing messages with long text.
*/
object ChatItemStandardMessageLongTextTestCase : TestCase("chat_item_standard_message_long_text") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(StandardFrames.recipientAlice.recipient!!)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
standardMessage = StandardMessage(
text = Text(
body = someString()
),
longText = someNullableFilePointer(),
reactions = some(Generators.reactions(2, StandardFrames.recipientSelf.recipient!!, StandardFrames.recipientAlice.recipient))
)
)
)
}
}

View File

@ -0,0 +1,63 @@
package tests
import PermutationScope
import TestCase
import asList
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
/**
* Incoming/outgoing messages with special attachments (i.e. voice note, borderless).
*/
object ChatItemStandardMessageSpecialAttachmentsTestCase : TestCase("chat_item_standard_message_special_attachments") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(StandardFrames.recipientAlice.recipient!!)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
standardMessage = StandardMessage(
quote = null,
attachments = Generators.permutation<MessageAttachment> {
val flag = some(Generators.list(MessageAttachment.Flag.VOICE_MESSAGE, MessageAttachment.Flag.BORDERLESS, MessageAttachment.Flag.GIF))
val pointer = some(Generators.filePointer())
frames += MessageAttachment(
flag = flag,
pointer = if (flag == MessageAttachment.Flag.VOICE_MESSAGE) {
pointer.copy(contentType = "audio/mp3", blurHash = null)
} else if (flag == MessageAttachment.Flag.GIF) {
pointer.copy(contentType = "video/mp4")
} else if (flag == MessageAttachment.Flag.BORDERLESS) {
pointer.copy(contentType = "image/png")
} else {
pointer
},
wasDownloaded = someBoolean()
)
}.asList(1).let { some(it) },
reactions = some(Generators.reactions(2, StandardFrames.recipientSelf.recipient!!, StandardFrames.recipientAlice.recipient))
)
)
)
}
}

View File

@ -0,0 +1,54 @@
package tests
import PermutationScope
import TestCase
import asList
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Incoming/outgoing messages with standard attachments (i.e. no flags, meaning no voice notes, etc.).
*/
object ChatItemStandardMessageStandardAttachmentsTestCase : TestCase("chat_item_standard_message_standard_attachments") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(StandardFrames.recipientAlice.recipient!!)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
standardMessage = StandardMessage(
quote = null,
text = someNullablePermutation {
frames += Text(
body = someString()
)
},
attachments = Generators.permutation<MessageAttachment> {
frames += MessageAttachment(
pointer = some(Generators.filePointer()),
flag = MessageAttachment.Flag.NONE,
wasDownloaded = someBoolean()
)
}.asList(1, 3, 5).let { some(it) },
reactions = some(Generators.reactions(2, StandardFrames.recipientSelf.recipient!!, StandardFrames.recipientAlice.recipient))
)
)
)
}
}

View File

@ -0,0 +1,46 @@
package tests
import PermutationScope
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
import org.thoughtcrime.securesms.backup.v2.proto.Text
/**
* Incoming/outgoing text-only messages.
*/
object ChatItemStandardMessageTextOnlyTestCase : TestCase("chat_item_standard_message_text_only") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(StandardFrames.recipientAlice.recipient!!)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
standardMessage = StandardMessage(
text = Text(
body = someString()
),
reactions = some(Generators.reactions(2, StandardFrames.recipientSelf.recipient!!, StandardFrames.recipientAlice.recipient))
)
)
)
}
}

View File

@ -0,0 +1,73 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generators
import PermutationScope
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
import org.thoughtcrime.securesms.backup.v2.proto.Text
/**
* Permutations of a standard message with edits/revisions.
*/
object ChatItemStandardMessageWithEditsTestCase : TestCase("chat_item_standard_message_with_edits") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val originalMessageDateSent = someNonZeroTimestamp()
val revisionGenerator = Generators.lists(listOf(1, 3)) {
Generators.permutation<ChatItem>(snapshotCount = 4) {
val dateSent = someDecrementingTimestamp(
lower = originalMessageDateSent - 100_000,
upper = originalMessageDateSent - 1
)
frames += ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = StandardFrames.recipientAlice.recipient!!.id,
dateSent = dateSent,
incoming = ChatItem.IncomingMessageDetails(
dateReceived = dateSent - 10,
dateServerSent = dateSent - 1,
read = someBoolean(),
sealedSender = someBoolean()
),
standardMessage = StandardMessage(
text = Text(
body = someString()
)
)
)
}
}
val revisions = some(revisionGenerator)
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = StandardFrames.recipientAlice.recipient!!.id,
dateSent = originalMessageDateSent,
incoming = ChatItem.IncomingMessageDetails(
dateReceived = originalMessageDateSent - 10,
dateServerSent = originalMessageDateSent - 1,
read = someBoolean(),
sealedSender = someBoolean()
),
standardMessage = StandardMessage(
text = Text(
body = someString()
)
),
revisions = revisions
)
)
}
}

View File

@ -0,0 +1,110 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generators
import PermutationScope
import StandardFrames
import TestCase
import asGenerator
import okio.ByteString.Companion.toByteString
import oneOf
import org.thoughtcrime.securesms.backup.v2.proto.*
/**
* Incoming/outgoing messages that quote other messages. We try to cover quoting every type of message here.
*/
object ChatItemStandardMessageWithQuoteTestCase : TestCase("chat_item_standard_message_with_quote") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(StandardFrames.recipientAlice.recipient!!)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
// TODO more message types!
val (
standardMessageGenerator,
contactMessageGenerator,
stickerMessageGenerator
) = oneOf(
StandardMessage(
text = Text(body = "asdf")
).asGenerator(),
ContactMessage(
contact = listOf(ContactAttachment(name = ContactAttachment.Name(givenName = "Peter", familyName = "Parker")))
).asGenerator(),
StickerMessage(
sticker = Sticker(
packId = ByteArray(16) { 0 }.toByteString(),
packKey = ByteArray(32) { 1 }.toByteString(),
emoji = "👍",
data_ = FilePointer(invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator())
)
).asGenerator()
)
val targetDateSent = 1L
val targetMessage = Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = StandardFrames.recipientAlice.recipient.id,
dateSent = targetDateSent,
incoming = ChatItem.IncomingMessageDetails(
dateReceived = targetDateSent,
dateServerSent = targetDateSent,
read = true
),
standardMessage = someOneOf(standardMessageGenerator),
contactMessage = someOneOf(contactMessageGenerator),
stickerMessage = someOneOf(stickerMessageGenerator)
)
)
frames += targetMessage
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
standardMessage = StandardMessage(
text = Text(
body = someString()
),
quote = Quote(
targetSentTimestamp = targetDateSent,
authorId = StandardFrames.recipientAlice.recipient.id,
text = if (targetMessage.chatItem?.standardMessage?.text?.body != null) {
Text(
body = targetMessage.chatItem.standardMessage.text.body,
bodyRanges = if (targetMessage.chatItem?.standardMessage?.text?.bodyRanges != null) {
targetMessage.chatItem.standardMessage.text.bodyRanges
} else {
emptyList()
}
)
} else {
null
},
type = Quote.Type.NORMAL
),
reactions = some(Generators.reactions(2, StandardFrames.recipientSelf.recipient!!, StandardFrames.recipientAlice.recipient))
)
)
)
}
}

View File

@ -0,0 +1,56 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generators
import PermutationScope
import StandardFrames
import TestCase
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.Sticker
import org.thoughtcrime.securesms.backup.v2.proto.StickerMessage
/**
* Incoming/outgoing sticker messages.
*/
object ChatItemStickerMessageTestCase : TestCase("chat_item_sticker_message") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
val (incomingGenerator, outgoingGenerator) = Generators.incomingOutgoingDetails(StandardFrames.recipientAlice.recipient!!)
val incoming = some(incomingGenerator)
val outgoing = some(outgoingGenerator)
val contentTypeGenerator = Generators.list("image/png", "image/apng", "image/webp")
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = if (outgoing != null) {
StandardFrames.recipientSelf.recipient!!.id
} else {
StandardFrames.recipientAlice.recipient!!.id
},
dateSent = someIncrementingTimestamp(),
incoming = incoming,
outgoing = outgoing,
stickerMessage = StickerMessage(
sticker = Sticker(
packId = someBytes(16).toByteString(),
packKey = someBytes(32).toByteString(),
stickerId = someInt(1, 32),
emoji = someEmoji(),
data_ = some(Generators.filePointer(contentTypeGenerator))
),
reactions = some(Generators.reactions(2, StandardFrames.recipientSelf.recipient!!, StandardFrames.recipientAlice.recipient!!))
)
)
)
}
}

View File

@ -0,0 +1,30 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import PermutationScope
import TestCase
import org.thoughtcrime.securesms.backup.v2.proto.*
object ChatItemThreadMergeUpdateTestCase : TestCase("chat_item_thread_merge_update") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.chatAlice
frames += Frame(
chatItem = ChatItem(
chatId = StandardFrames.chatAlice.chat!!.id,
authorId = StandardFrames.recipientAlice.recipient!!.id,
dateSent = someTimestamp(),
directionless = ChatItem.DirectionlessMessageDetails(),
updateMessage = ChatUpdateMessage(
threadMerge = ThreadMergeChatUpdate(
previousE164 = someE164()
)
)
)
)
}
}

View File

@ -0,0 +1,57 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generator
import PermutationScope
import StandardFrames
import TestCase
import oneOf
import org.thoughtcrime.securesms.backup.v2.proto.Chat
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
import org.thoughtcrime.securesms.backup.v2.proto.Frame
/**
* Every reasonable permutation of chats
*/
object ChatTestCase : TestCase("chat") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
val (wallpaperPhotoGenerator, wallpaperPresetGenerator) = oneOf(
Generators.filePointer() as Generator<Any?>,
Generators.enum(ChatStyle.WallpaperPreset::class.java, ChatStyle.WallpaperPreset.UNKNOWN_WALLPAPER_PRESET) as Generator<Any?>
)
val (bubbleAutoGenerator, bubblePresetGenerator, bubbleCustomGenerator) = oneOf(
Generators.list(ChatStyle.AutomaticBubbleColor()),
Generators.enum(ChatStyle.BubbleColorPreset::class.java, ChatStyle.BubbleColorPreset.UNKNOWN_BUBBLE_COLOR_PRESET) as Generator<Any?>,
Generators.list(1L, 2L, 3L)
)
frames += Frame(
chat = Chat(
id = 3,
recipientId = StandardFrames.chatAlice.chat!!.id,
archived = someBoolean(),
pinnedOrder = some(Generators.list(listOf(0, 1))),
expirationTimerMs = somePositiveLong(),
muteUntilMs = somePositiveLong(),
markedUnread = someBoolean(),
dontNotifyForMentionsIfMuted = someBoolean(),
style = somePermutation {
frames += ChatStyle(
wallpaperPreset = someOneOf(wallpaperPresetGenerator),
wallpaperPhoto = someOneOf(wallpaperPhotoGenerator),
autoBubbleColor = someOneOf(bubbleAutoGenerator),
bubbleColorPreset = someOneOf(bubblePresetGenerator),
customColorId = someOneOf(bubbleCustomGenerator),
dimWallpaperInDarkMode = someBoolean()
)
}
)
)
}
}

View File

@ -0,0 +1,31 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import PermutationScope
import TestCase
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.backup.v2.proto.CallLink
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.Recipient
/**
* Reasonable permutations of [CallLink] recipients.
*/
object RecipientCallLinkTestCase : TestCase("recipient_call_link") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += Frame(
recipient = Recipient(
callLink = CallLink(
rootKey = someBytes(16).toByteString(),
adminKey = someNullableBytes(32)?.toByteString(),
name = someString(),
restrictions = someEnum(CallLink.Restrictions::class.java, excluding = CallLink.Restrictions.UNKNOWN),
expirationMs = somePositiveLong()
)
)
)
}
}

View File

@ -0,0 +1,50 @@
package tests
import PermutationScope
import TestCase
import okio.ByteString.Companion.toByteString
import oneOf
import org.thoughtcrime.securesms.backup.v2.proto.Contact
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.Recipient
import toByteArray
/**
* Every reasonable permutation of Recipient.Contact
*/
object RecipientContactsTestCase : TestCase("recipient_contacts") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
val (registered, notRegistered) = oneOf(
Generators.list(Contact.Registered()),
Generators.permutation {
frames += Contact.NotRegistered(
unregisteredTimestamp = someTimestamp()
)
}
)
frames += Frame(
recipient = Recipient(
id = 4,
contact = Contact(
aci = someUuid().toByteArray().toByteString(),
pni = someUuid().toByteArray().toByteString(),
username = someNullableUsername(),
e164 = someE164(),
blocked = someBoolean(),
visibility = someEnum(Contact.Visibility::class.java),
registered = someOneOf(registered),
notRegistered = someOneOf(notRegistered),
profileKey = someNullableBytes(32)?.toByteString(),
profileSharing = someBoolean(),
profileGivenName = someNullableString(),
profileFamilyName = someNullableString(),
hideStory = someBoolean()
)
)
)
}
}

View File

@ -0,0 +1,66 @@
package tests
import PermutationScope
import TestCase
import asList
import map
import okio.ByteString.Companion.toByteString
import oneOf
import org.thoughtcrime.securesms.backup.v2.proto.DistributionList
import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.Recipient
import toByteArray
/**
* Every reasonable permutation of Recipient.DistributionList
*/
object RecipientDistributionListTestCase : TestCase("recipient_distribution_list") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.recipientBob
frames += StandardFrames.recipientCarol
val memberIds: List<Long> = listOf(
StandardFrames.recipientAlice,
StandardFrames.recipientBob,
StandardFrames.recipientCarol
).map { it.recipient!!.id }
val (deletionTimestampGenerator, distributionListGenerator) = oneOf(
Generators.single(someIncrementingTimestamp()),
Generators.permutation {
val privacyModeGenerator = Generators.enum(DistributionList.PrivacyMode::class.java, excluding = DistributionList.PrivacyMode.UNKNOWN)
val memberRecipientIdGenerator = Generators.list(memberIds).asList(0, 1, 2, 3)
val privacyMode = some(privacyModeGenerator)
val memberRecipientIds = some(memberRecipientIdGenerator)
frames += DistributionList(
name = someNonEmptyString(),
allowReplies = someBoolean(),
memberRecipientIds = if (privacyMode == DistributionList.PrivacyMode.ALL) {
emptyList()
} else {
memberRecipientIds
},
privacyMode = privacyMode
)
}
)
frames += Frame(
recipient = Recipient(
id = 7,
distributionList = DistributionListItem(
distributionId = someUuid().toByteArray().toByteString(),
deletionTimestamp = someOneOf(deletionTimestampGenerator),
distributionList = someOneOf(distributionListGenerator)
)
)
)
}
}

View File

@ -0,0 +1,105 @@
package tests
import Generator
import PermutationScope
import TestCase
import asList
import map
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.backup.v2.proto.Contact
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.Group
import org.thoughtcrime.securesms.backup.v2.proto.Recipient
/**
* Every reasonable permutation of Recipient.Group
*/
object RecipientGroupsTestCase : TestCase("recipient_groups") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += StandardFrames.recipientAlice
frames += StandardFrames.recipientBob
frames += StandardFrames.recipientCarol
frames += StandardFrames.recipientDan
frames += StandardFrames.recipientEve
frames += StandardFrames.recipientFrank
frames += Frame(
recipient = Recipient(
id = 10,
group = Group(
masterKey = someBytes(32).toByteString(),
whitelisted = someBoolean(),
hideStory = someBoolean(),
storySendMode = someEnum(Group.StorySendMode::class.java),
snapshot = somePermutation {
frames += Group.GroupSnapshot(
title = Group.GroupAttributeBlob(
title = someNonEmptyString()
),
description = someNullablePermutation {
frames += Group.GroupAttributeBlob(
descriptionText = someNonEmptyString()
)
},
avatarUrl = someUrl(),
disappearingMessagesTimer = somePermutation {
frames += Group.GroupAttributeBlob(
disappearingMessagesDuration = somePositiveInt()
)
},
accessControl = Group.AccessControl(
attributes = someEnum(Group.AccessControl.AccessRequired::class.java, Group.AccessControl.AccessRequired.UNKNOWN, Group.AccessControl.AccessRequired.ANY, Group.AccessControl.AccessRequired.UNSATISFIABLE),
members = someEnum(Group.AccessControl.AccessRequired::class.java, Group.AccessControl.AccessRequired.UNKNOWN, Group.AccessControl.AccessRequired.ANY, Group.AccessControl.AccessRequired.UNSATISFIABLE),
addFromInviteLink = someEnum(Group.AccessControl.AccessRequired::class.java, Group.AccessControl.AccessRequired.UNKNOWN, Group.AccessControl.AccessRequired.ANY, Group.AccessControl.AccessRequired.MEMBER)
),
version = somePositiveInt(),
members = Generators.permutation<Group.Member> {
val userGenerator: Generator<Contact> = Generators.list(StandardFrames.recipientAlice.recipient!!.contact!!, StandardFrames.recipientBob.recipient!!.contact!!, StandardFrames.recipientCarol.recipient!!.contact!!)
val user = some(userGenerator)
frames += Group.Member(
userId = user.aci!!,
role = someEnum(Group.Member.Role::class.java, excluding = Group.Member.Role.UNKNOWN),
profileKey = user.profileKey!!
)
}.asList(1, 2, 3).map { members ->
members + Group.Member(
userId = StandardFrames.SELF_ACI.toByteString(),
role = Group.Member.Role.DEFAULT,
profileKey = StandardFrames.SELF_PROFILE_KEY.toByteString()
)
}.let { some(it) },
membersPendingProfileKey = Generators.permutation<Group.MemberPendingProfileKey> {
frames += Group.MemberPendingProfileKey(
member = Group.Member(
userId = StandardFrames.recipientDan.recipient!!.contact!!.aci!!,
role = Group.Member.Role.DEFAULT
),
addedByUserId = StandardFrames.recipientAlice.recipient!!.contact!!.aci!!,
timestamp = someIncrementingTimestamp()
)
}.asList(0, 1).let { some(it) },
membersPendingAdminApproval = Generators.permutation<Group.MemberPendingAdminApproval> {
frames += Group.MemberPendingAdminApproval(
userId = StandardFrames.recipientEve.recipient!!.contact!!.aci!!,
profileKey = StandardFrames.recipientEve.recipient!!.contact!!.profileKey!!,
timestamp = someIncrementingTimestamp()
)
}.asList(0, 1).let { some(it) },
members_banned = Generators.permutation<Group.MemberBanned> {
frames += Group.MemberBanned(
userId = StandardFrames.recipientFrank.recipient!!.contact!!.aci!!,
timestamp = someIncrementingTimestamp()
)
}.asList(0, 1).let { some(it) },
inviteLinkPassword = someBytes(32).toByteString(),
announcements_only = someBoolean()
)
}
)
)
)
}
}

View File

@ -0,0 +1,14 @@
package tests
import PermutationScope
import TestCase
/**
* The simplest possible test case, containing only the mandatory frames you need to have a valid backup.
*/
object StandardFramesTestCase : TestCase("standard_frames") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
}
}

View File

@ -0,0 +1,26 @@
@file:Suppress("UNCHECKED_CAST")
package tests
import Generators
import PermutationScope
import TestCase
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.StickerPack
/**
* Some sticker pack variations.
*/
object StickerPackTestCase : TestCase("sticker_pack") {
override fun PermutationScope.execute() {
frames += StandardFrames.MANDATORY_FRAMES
frames += Frame(
stickerPack = StickerPack(
packId = some(Generators.bytes(numBytes = 16, minSize = 3)).toByteString(),
packKey = some(Generators.bytes(numBytes = 32, minSize = 3)).toByteString()
)
)
}
}

View File

@ -433,14 +433,14 @@ message PaymentNotification {
Status status = 1;
// This identification is used to map the payment table to the ledger
// and is likely required otherwise we may have issues reconciling with
// and is likely required otherwise we may have issues reconciling with
// the ledger
MobileCoinTxoIdentification mobileCoinIdentification = 2;
optional uint64 timestamp = 3;
optional uint64 blockIndex = 4;
optional uint64 blockTimestamp = 5;
optional bytes transaction = 6; // mobile coin blobs
optional bytes receipt = 7; // mobile coin blobs
optional bytes transaction = 6; // mobile coin blobs
optional bytes receipt = 7; // mobile coin blobs
}
oneof payment {
@ -448,12 +448,12 @@ message PaymentNotification {
FailedTransaction failedTransaction = 2;
}
}
optional string amountMob = 1; // stored as a decimal string, e.g. 1.00001
optional string feeMob = 2; // stored as a decimal string, e.g. 1.00001
optional string note = 3;
TransactionDetails transactionDetails = 4;
}
message GiftBadge {
@ -612,7 +612,7 @@ message FilePointer {
}
// References attachments that are invalid in such a way where download
// cannot be attempted. Could range from missing digests to missing
// cannot be attempted. Could range from missing digests to missing
// CDN keys or anything else that makes download attempts impossible.
// This serves as a 'tombstone' so that the UX can show that an attachment
// did exist, but for whatever reason it's not retrievable.
@ -1150,4 +1150,4 @@ message ChatStyle {
}
bool dimWallpaperInDarkMode = 7;
}
}

Binary file not shown.

View File

@ -1,71 +0,0 @@
// Minimal backup file that includes only an account data frame.
[
{
"version": "1",
"backupTimeMs": "1715636551000"
},
{
"account": {
"profileKey": "YQKRq+3DQklInaOaMcmlzZnN0m/1hzLiaONX7gB12dg=",
"username": "boba_fett.66",
"usernameLink": {
"entropy": "ZWdcc9AOsBAF47t8SkfylstlVPeJgSOIFekV2CT9LpM=",
"serverId": "YcEBogDVQheJwgUY2El68A==",
"color": "OLIVE"
},
"givenName": "Boba",
"familyName": "Fett",
"avatarUrlPath": "",
"donationSubscriberData": {
"subscriberId": "7LtoxzQzGi6jM82nR8mMRVNlImFYK0/OWuDeqE3OZRk=",
"currencyCode": "USD",
"manuallyCancelled": true
},
"accountSettings": {
"readReceipts": true,
"sealedSenderIndicators": true,
"typingIndicators": true,
"linkPreviews": false,
"notDiscoverableByPhoneNumber": true,
"preferContactAvatars": true,
"universalExpireTimerSeconds": 3600,
"preferredReactionEmoji": ["🏎️"],
"displayBadgesOnProfile": true,
"keepMutedChatsArchived": true,
"hasSetMyStoriesPrivacy": true,
"hasViewedOnboardingStory": true,
"storiesDisabled": true,
"storyViewReceiptsEnabled": true,
"hasSeenGroupStoryEducationSheet": true,
"hasCompletedUsernameOnboarding": true,
"phoneNumberSharingMode": "NOBODY"
}
}
},
{
"recipient": {
"id": "1",
"self": {}
}
},
{
"recipient": {
"id": "2",
"releaseNotes": {}
}
},
{
"recipient": {
"id": "3",
"distributionList": {
"distributionId": "AAAAAAAAAAAAAAAAAAAAAA==",
"distributionList": {
"allowReplies": true,
"memberRecipientIds": [],
"name": "My Story",
"privacyMode": "ALL"
}
}
}
}
]

Binary file not shown.

View File

@ -0,0 +1,84 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -1325120
},
CustomChatColor {
id = 2
solid = -2537207
},
CustomChatColor {
gradient = Gradient {
angle = 59
colors = [
-15640225,
-4632205
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 1083889414
}
avatarUrlPath = ""
familyName = ""
givenName = ""
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,99 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -12608904
},
CustomChatColor {
id = 2
solid = -12266925
},
CustomChatColor {
gradient = Gradient {
angle = 253
colors = [
-3106179,
-11172082
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
autoBubbleColor = AutomaticBubbleColor {}
dimWallpaperInDarkMode = true
wallpaperPhoto = FilePointer {
backupLocator = BackupLocator {
digest = <5c0c5f7b97527985b715cd4f8972f428>
key = <3bd72e515007f15a21064b374c7c7f19>
mediaName = "5c0c5f7b97527985b715cd4f8972f428"
size = 1000441712
}
contentType = "image/jpeg"
}
}
phoneNumberSharingMode = PhoneNumberSharingMode.NOBODY
universalExpireTimerSeconds = 456011350
}
avatarUrlPath = "https://example.com/MRnBbKuo"
backupsSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <ef196f49f9c8bc5bd4f8b3400f0651ad8a3dd7d136577f53976280393bff341b>
}
donationSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <df93d2e29614e293c3e76f4e185b9058efc1fa486e362a8bfe82fac40e9e6701>
}
familyName = "JRYwuBEiK"
givenName = "FBvMKGTMcILm"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "HqzzN.96"
usernameLink = UsernameLink {
color = Color.BLUE
entropy = <023354c1c58b8bdb65b292d0fbfd03cef547a36774f7b594a5a5b7db5756287f>
serverId = <731ddfba674ec6f3bac14fed8832849e>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,119 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -7789932
},
CustomChatColor {
id = 2
solid = -11995034
},
CustomChatColor {
gradient = Gradient {
angle = 50
colors = [
-6934140,
-126129
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_ULTRAMARINE
wallpaperPhoto = FilePointer {
backupLocator = BackupLocator {
cdnNumber = 2
digest = <e3670f77ebf5e721149f599dab4f9c8a>
key = <b4fffb255879953e257ab94747b56ae5>
mediaName = "e3670f77ebf5e721149f599dab4f9c8a"
size = 2094646670
transitCdnKey = "EtjaXtfWb"
}
blurHash = "LfLh6Voa9NIW?wNF-ooL-;WAX8oy"
caption = ""
contentType = "image/png"
fileName = "fktWAZnl"
height = 3361
incrementalMac = <b0268c5da867e8b454754359d4337a3c>
incrementalMacChunkSize = 2048
width = 823
}
}
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 1514863930
}
avatarUrlPath = "https://example.com/BvsKAUVdtL"
backupsSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <7413fec76076c9df65cfa830a11a5cb82d872d622f7cb73a1c085a95699762ba>
}
donationSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <6eb51ac08d871eb76f2e7e3825d55d76500fad6608e092e4becbfbbe1e28e38b>
}
familyName = "QxVufxIRR"
givenName = "vNHEreJDueIn"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "ZggKo.88"
usernameLink = UsernameLink {
color = Color.WHITE
entropy = <00205ee8e14dd5d6df1bf0ae271eed9d640edd3bd1e1a6cfc758125a94372c26>
serverId = <44b7a5ed7fed22b2f329b86acb134608>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,107 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -1325120
},
CustomChatColor {
id = 2
solid = -2537207
},
CustomChatColor {
gradient = Gradient {
angle = 59
colors = [
-15640225,
-4632205
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_CRIMSON
dimWallpaperInDarkMode = true
wallpaperPhoto = FilePointer {
backupLocator = BackupLocator {
cdnNumber = 3
digest = <8a72bebb4db939e0342ae035ddbe7fe4>
key = <d9345fdc5ab1cf72b00738e125ef7370>
mediaName = "8a72bebb4db939e0342ae035ddbe7fe4"
size = 1693683457
transitCdnKey = "gYOzQSvJcbzGY"
transitCdnNumber = 2
}
blurHash = "LGG*f,-i.l-o?G$~?Zt7pHN1=tE3"
caption = "OymkbGZomPlLHy"
contentType = "image/gif"
fileName = "xHDjKnB"
height = 781
width = 3896
}
}
phoneNumberSharingMode = PhoneNumberSharingMode.NOBODY
universalExpireTimerSeconds = 1083889414
}
avatarUrlPath = ""
backupsSubscriberData = SubscriberData {
currencyCode = "GBP"
manuallyCancelled = true
subscriberId = <66de0801976f073e561ac824c3716b81b515c87edc658b9f52e305873211de86>
}
donationSubscriberData = SubscriberData {
currencyCode = "GBP"
manuallyCancelled = true
subscriberId = <7911e7798244f14304a45b550a99ee0df3ff32c42e228cf0ec92e3182c8face4>
}
familyName = ""
givenName = ""
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "HqzzN.96"
usernameLink = UsernameLink {
color = Color.GREY
entropy = <ea2b87608981423f87a1cf293b8aa5dac03b4e37d46a68b84baa49cf5140d38a>
serverId = <8be2cb21a0fa47a0181037dfcfe0de95>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,107 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -12608904
},
CustomChatColor {
id = 2
solid = -12266925
},
CustomChatColor {
gradient = Gradient {
angle = 253
colors = [
-3106179,
-11172082
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_VERMILION
wallpaperPhoto = FilePointer {
backupLocator = BackupLocator {
digest = <6dc1e0cd18b0df35d2dda82a4ffea322>
key = <0a038863ebb03fed8486f4196b6e656c>
mediaName = "6dc1e0cd18b0df35d2dda82a4ffea322"
size = 1000441712
}
caption = "LGdFwzenIne"
contentType = "audio/mp3"
height = 3522
incrementalMac = <b0268c5da867e8b454754359d4337a3c>
incrementalMacChunkSize = 2048
width = 710
}
}
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 456011350
}
avatarUrlPath = "https://example.com/MRnBbKuo"
familyName = "JRYwuBEiK"
givenName = "FBvMKGTMcILm"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "ZggKo.88"
usernameLink = UsernameLink {
color = Color.OLIVE
entropy = <e663e783cd45f8bcee2bba22154356f8228ce13806bcb400f2316eac211b9328>
serverId = <b1a6608635a5eb695456cf27dca7ca58>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,101 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -7789932
},
CustomChatColor {
id = 2
solid = -11995034
},
CustomChatColor {
gradient = Gradient {
angle = 50
colors = [
-6934140,
-126129
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_BURLAP
dimWallpaperInDarkMode = true
wallpaperPhoto = FilePointer {
attachmentLocator = AttachmentLocator {
cdnKey = "eejhnSfTgFokL"
digest = <00b2dee58107854a29a3b2f195b0f63b>
key = <0c3ae62e4a5aa0bbf67baa9066da53ca>
size = 193250115
}
blurHash = "LJR,66e.~Cxu%LoLM|S2%3WWIosm"
contentType = "video/mp4"
fileName = "fktWAZnl"
}
}
phoneNumberSharingMode = PhoneNumberSharingMode.NOBODY
universalExpireTimerSeconds = 1514863930
}
avatarUrlPath = "https://example.com/BvsKAUVdtL"
backupsSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <ef196f49f9c8bc5bd4f8b3400f0651ad8a3dd7d136577f53976280393bff341b>
}
donationSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <df93d2e29614e293c3e76f4e185b9058efc1fa486e362a8bfe82fac40e9e6701>
}
familyName = "QxVufxIRR"
givenName = "vNHEreJDueIn"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "HqzzN.96"
usernameLink = UsernameLink {
color = Color.GREEN
entropy = <bd816ba653e5e51923ff43cce18fcefe8f5eaf903c1ac370d80625c4e81d8138>
serverId = <890a6fcc0d7cede0d005e0aafb8cd88b>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,119 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -1325120
},
CustomChatColor {
id = 2
solid = -2537207
},
CustomChatColor {
gradient = Gradient {
angle = 59
colors = [
-15640225,
-4632205
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_FOREST
wallpaperPhoto = FilePointer {
attachmentLocator = AttachmentLocator {
cdnKey = "LmtLxdKP"
cdnNumber = 2
digest = <fcd89432f89fb1eaa786569514b8aab9>
key = <a459650a2590efd18c249edbc4fa0a59>
size = 2123148010
uploadTimestamp = 1776491210910
}
blurHash = "LIM:}RB8?-^L.d4]O.nkK_ruI?od"
caption = ""
contentType = "image/jpeg"
fileName = "xHDjKnB"
height = 3361
incrementalMac = <b0268c5da867e8b454754359d4337a3c>
incrementalMacChunkSize = 2048
width = 823
}
}
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 1083889414
}
avatarUrlPath = ""
backupsSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <7413fec76076c9df65cfa830a11a5cb82d872d622f7cb73a1c085a95699762ba>
}
donationSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <6eb51ac08d871eb76f2e7e3825d55d76500fad6608e092e4becbfbbe1e28e38b>
}
familyName = ""
givenName = ""
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "ZggKo.88"
usernameLink = UsernameLink {
color = Color.ORANGE
entropy = <fe56c791fcdea8977e30252c7516f323573b0e07b88e3658f00760e0768dc91e>
serverId = <8a31e24230f253d87497d4b73dafe4fa>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,104 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -12608904
},
CustomChatColor {
id = 2
solid = -12266925
},
CustomChatColor {
gradient = Gradient {
angle = 253
colors = [
-3106179,
-11172082
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_WINTERGREEN
dimWallpaperInDarkMode = true
wallpaperPhoto = FilePointer {
attachmentLocator = AttachmentLocator {
cdnKey = "eejhnSfTgFokL"
cdnNumber = 3
digest = <46e8dc0a531c93c2fbd8a21f072534d5>
key = <88cf6bfa88e3f24a21a78511518391fb>
size = 2101437613
uploadTimestamp = 1852195597883
}
caption = "OymkbGZomPlLHy"
contentType = "image/png"
height = 781
width = 3896
}
}
phoneNumberSharingMode = PhoneNumberSharingMode.NOBODY
universalExpireTimerSeconds = 456011350
}
avatarUrlPath = "https://example.com/MRnBbKuo"
backupsSubscriberData = SubscriberData {
currencyCode = "GBP"
manuallyCancelled = true
subscriberId = <66de0801976f073e561ac824c3716b81b515c87edc658b9f52e305873211de86>
}
donationSubscriberData = SubscriberData {
currencyCode = "GBP"
manuallyCancelled = true
subscriberId = <7911e7798244f14304a45b550a99ee0df3ff32c42e228cf0ec92e3182c8face4>
}
familyName = "JRYwuBEiK"
givenName = "FBvMKGTMcILm"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "HqzzN.96"
usernameLink = UsernameLink {
color = Color.PINK
entropy = <45ab5ead8eb9044ad79f0def3bddc876c8c58255a4a057c231e4090f3d46521f>
serverId = <6d1d851dd86c5b2ded7aba007b1eb92c>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,104 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -7789932
},
CustomChatColor {
id = 2
solid = -11995034
},
CustomChatColor {
gradient = Gradient {
angle = 50
colors = [
-6934140,
-126129
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_TEAL
wallpaperPhoto = FilePointer {
blurHash = "LfLh6Voa9NIW?wNF-ooL-;WAX8oy"
caption = "LGdFwzenIne"
contentType = "image/gif"
fileName = "fktWAZnl"
height = 3522
incrementalMac = <b0268c5da867e8b454754359d4337a3c>
incrementalMacChunkSize = 2048
invalidAttachmentLocator = InvalidAttachmentLocator {}
width = 710
}
}
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 1514863930
}
avatarUrlPath = "https://example.com/BvsKAUVdtL"
familyName = "QxVufxIRR"
givenName = "vNHEreJDueIn"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "ZggKo.88"
usernameLink = UsernameLink {
color = Color.PURPLE
entropy = <bf2129fa7e13decf351a46279b80ab8e71ee5c3d1a6984f8975f5943ed7cdb21>
serverId = <6edab2dab1ec8f3d1edc09d3bfc894e5>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,85 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -1325120
},
CustomChatColor {
id = 2
solid = -2537207
},
CustomChatColor {
gradient = Gradient {
angle = 59
colors = [
-15640225,
-4632205
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_BLUE
dimWallpaperInDarkMode = true
wallpaperPreset = WallpaperPreset.SOLID_BLUSH
}
phoneNumberSharingMode = PhoneNumberSharingMode.NOBODY
universalExpireTimerSeconds = 1083889414
}
avatarUrlPath = ""
backupsSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <ef196f49f9c8bc5bd4f8b3400f0651ad8a3dd7d136577f53976280393bff341b>
}
donationSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <df93d2e29614e293c3e76f4e185b9058efc1fa486e362a8bfe82fac40e9e6701>
}
familyName = ""
givenName = ""
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,102 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -12608904
},
CustomChatColor {
id = 2
solid = -12266925
},
CustomChatColor {
gradient = Gradient {
angle = 253
colors = [
-3106179,
-11172082
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_INDIGO
wallpaperPreset = WallpaperPreset.SOLID_COPPER
}
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 456011350
}
avatarUrlPath = "https://example.com/MRnBbKuo"
backupsSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <7413fec76076c9df65cfa830a11a5cb82d872d622f7cb73a1c085a95699762ba>
}
donationSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <6eb51ac08d871eb76f2e7e3825d55d76500fad6608e092e4becbfbbe1e28e38b>
}
familyName = "JRYwuBEiK"
givenName = "FBvMKGTMcILm"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "ZggKo.88"
usernameLink = UsernameLink {
color = Color.BLUE
entropy = <023354c1c58b8bdb65b292d0fbfd03cef547a36774f7b594a5a5b7db5756287f>
serverId = <731ddfba674ec6f3bac14fed8832849e>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,91 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -7789932
},
CustomChatColor {
id = 2
solid = -11995034
},
CustomChatColor {
gradient = Gradient {
angle = 50
colors = [
-6934140,
-126129
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_VIOLET
dimWallpaperInDarkMode = true
wallpaperPreset = WallpaperPreset.SOLID_DUST
}
phoneNumberSharingMode = PhoneNumberSharingMode.NOBODY
universalExpireTimerSeconds = 1514863930
}
avatarUrlPath = "https://example.com/BvsKAUVdtL"
backupsSubscriberData = SubscriberData {
currencyCode = "GBP"
manuallyCancelled = true
subscriberId = <66de0801976f073e561ac824c3716b81b515c87edc658b9f52e305873211de86>
}
donationSubscriberData = SubscriberData {
currencyCode = "GBP"
manuallyCancelled = true
subscriberId = <7911e7798244f14304a45b550a99ee0df3ff32c42e228cf0ec92e3182c8face4>
}
familyName = "QxVufxIRR"
givenName = "vNHEreJDueIn"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "HqzzN.96"
usernameLink = UsernameLink {
color = Color.WHITE
entropy = <00205ee8e14dd5d6df1bf0ae271eed9d640edd3bd1e1a6cfc758125a94372c26>
serverId = <44b7a5ed7fed22b2f329b86acb134608>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,94 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -1325120
},
CustomChatColor {
id = 2
solid = -2537207
},
CustomChatColor {
gradient = Gradient {
angle = 59
colors = [
-15640225,
-4632205
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_PLUM
wallpaperPreset = WallpaperPreset.SOLID_CELADON
}
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 1083889414
}
avatarUrlPath = ""
familyName = ""
givenName = ""
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "ZggKo.88"
usernameLink = UsernameLink {
color = Color.GREY
entropy = <ea2b87608981423f87a1cf293b8aa5dac03b4e37d46a68b84baa49cf5140d38a>
serverId = <8be2cb21a0fa47a0181037dfcfe0de95>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,91 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -12608904
},
CustomChatColor {
id = 2
solid = -12266925
},
CustomChatColor {
gradient = Gradient {
angle = 253
colors = [
-3106179,
-11172082
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_TAUPE
dimWallpaperInDarkMode = true
wallpaperPreset = WallpaperPreset.SOLID_RAINFOREST
}
phoneNumberSharingMode = PhoneNumberSharingMode.NOBODY
universalExpireTimerSeconds = 456011350
}
avatarUrlPath = "https://example.com/MRnBbKuo"
backupsSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <ef196f49f9c8bc5bd4f8b3400f0651ad8a3dd7d136577f53976280393bff341b>
}
donationSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <df93d2e29614e293c3e76f4e185b9058efc1fa486e362a8bfe82fac40e9e6701>
}
familyName = "JRYwuBEiK"
givenName = "FBvMKGTMcILm"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "HqzzN.96"
usernameLink = UsernameLink {
color = Color.OLIVE
entropy = <e663e783cd45f8bcee2bba22154356f8228ce13806bcb400f2316eac211b9328>
serverId = <b1a6608635a5eb695456cf27dca7ca58>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,102 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -7789932
},
CustomChatColor {
id = 2
solid = -11995034
},
CustomChatColor {
gradient = Gradient {
angle = 50
colors = [
-6934140,
-126129
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.SOLID_STEEL
wallpaperPreset = WallpaperPreset.SOLID_PACIFIC
}
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 1514863930
}
avatarUrlPath = "https://example.com/BvsKAUVdtL"
backupsSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <7413fec76076c9df65cfa830a11a5cb82d872d622f7cb73a1c085a95699762ba>
}
donationSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <6eb51ac08d871eb76f2e7e3825d55d76500fad6608e092e4becbfbbe1e28e38b>
}
familyName = "QxVufxIRR"
givenName = "vNHEreJDueIn"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "ZggKo.88"
usernameLink = UsernameLink {
color = Color.GREEN
entropy = <bd816ba653e5e51923ff43cce18fcefe8f5eaf903c1ac370d80625c4e81d8138>
serverId = <890a6fcc0d7cede0d005e0aafb8cd88b>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,91 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -1325120
},
CustomChatColor {
id = 2
solid = -2537207
},
CustomChatColor {
gradient = Gradient {
angle = 59
colors = [
-15640225,
-4632205
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.GRADIENT_EMBER
dimWallpaperInDarkMode = true
wallpaperPreset = WallpaperPreset.SOLID_FROST
}
phoneNumberSharingMode = PhoneNumberSharingMode.NOBODY
universalExpireTimerSeconds = 1083889414
}
avatarUrlPath = ""
backupsSubscriberData = SubscriberData {
currencyCode = "GBP"
manuallyCancelled = true
subscriberId = <66de0801976f073e561ac824c3716b81b515c87edc658b9f52e305873211de86>
}
donationSubscriberData = SubscriberData {
currencyCode = "GBP"
manuallyCancelled = true
subscriberId = <7911e7798244f14304a45b550a99ee0df3ff32c42e228cf0ec92e3182c8face4>
}
familyName = ""
givenName = ""
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "HqzzN.96"
usernameLink = UsernameLink {
color = Color.ORANGE
entropy = <fe56c791fcdea8977e30252c7516f323573b0e07b88e3658f00760e0768dc91e>
serverId = <8a31e24230f253d87497d4b73dafe4fa>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,94 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -12608904
},
CustomChatColor {
id = 2
solid = -12266925
},
CustomChatColor {
gradient = Gradient {
angle = 253
colors = [
-3106179,
-11172082
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.GRADIENT_MIDNIGHT
wallpaperPreset = WallpaperPreset.SOLID_NAVY
}
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 456011350
}
avatarUrlPath = "https://example.com/MRnBbKuo"
familyName = "JRYwuBEiK"
givenName = "FBvMKGTMcILm"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "ZggKo.88"
usernameLink = UsernameLink {
color = Color.PINK
entropy = <45ab5ead8eb9044ad79f0def3bddc876c8c58255a4a057c231e4090f3d46521f>
serverId = <6d1d851dd86c5b2ded7aba007b1eb92c>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,91 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -7789932
},
CustomChatColor {
id = 2
solid = -11995034
},
CustomChatColor {
gradient = Gradient {
angle = 50
colors = [
-6934140,
-126129
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.GRADIENT_INFRARED
dimWallpaperInDarkMode = true
wallpaperPreset = WallpaperPreset.SOLID_LILAC
}
phoneNumberSharingMode = PhoneNumberSharingMode.NOBODY
universalExpireTimerSeconds = 1514863930
}
avatarUrlPath = "https://example.com/BvsKAUVdtL"
backupsSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <ef196f49f9c8bc5bd4f8b3400f0651ad8a3dd7d136577f53976280393bff341b>
}
donationSubscriberData = SubscriberData {
currencyCode = "USD"
manuallyCancelled = true
subscriberId = <df93d2e29614e293c3e76f4e185b9058efc1fa486e362a8bfe82fac40e9e6701>
}
familyName = "QxVufxIRR"
givenName = "vNHEreJDueIn"
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
username = "HqzzN.96"
usernameLink = UsernameLink {
color = Color.PURPLE
entropy = <bf2129fa7e13decf351a46279b80ab8e71ee5c3d1a6984f8975f5943ed7cdb21>
serverId = <6edab2dab1ec8f3d1edc09d3bfc894e5>
}
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

View File

@ -0,0 +1,96 @@
// This file was auto-generated! It's only meant to show you what's in the .binproto. Do not edit!
BackupInfo {
backupTimeMs = 1715636551000
version = 1
}
Frame {
account = AccountData {
accountSettings = AccountSettings {
customChatColors = [
CustomChatColor {
id = 1
solid = -1325120
},
CustomChatColor {
id = 2
solid = -2537207
},
CustomChatColor {
gradient = Gradient {
angle = 59
colors = [
-15640225,
-4632205
]
positions = [
0.0,
1.0
]
}
id = 3
}
]
defaultChatStyle = ChatStyle {
bubbleColorPreset = BubbleColorPreset.GRADIENT_LAGOON
wallpaperPreset = WallpaperPreset.SOLID_PINK
}
displayBadgesOnProfile = true
hasCompletedUsernameOnboarding = true
hasSeenGroupStoryEducationSheet = true
hasSetMyStoriesPrivacy = true
hasViewedOnboardingStory = true
keepMutedChatsArchived = true
linkPreviews = true
notDiscoverableByPhoneNumber = true
phoneNumberSharingMode = PhoneNumberSharingMode.EVERYBODY
preferContactAvatars = true
readReceipts = true
sealedSenderIndicators = true
storiesDisabled = true
storyViewReceiptsEnabled = true
typingIndicators = true
universalExpireTimerSeconds = 1083889414
}
avatarUrlPath = ""
backupsSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <7413fec76076c9df65cfa830a11a5cb82d872d622f7cb73a1c085a95699762ba>
}
donationSubscriberData = SubscriberData {
currencyCode = "EUR"
subscriberId = <6eb51ac08d871eb76f2e7e3825d55d76500fad6608e092e4becbfbbe1e28e38b>
}
familyName = ""
givenName = ""
profileKey = <99a0b038244c4dded369b915dbc6d9d34af5273ec0c1d502acde812c7ee2f29c>
}
}
Frame {
recipient = Recipient {
id = 1
self = Self {}
}
}
Frame {
recipient = Recipient {
id = 2
releaseNotes = ReleaseNotes {}
}
}
Frame {
recipient = Recipient {
distributionList = DistributionListItem {
distributionId = <00000000000000000000000000000000>
distributionList = DistributionList {
name = "My Story"
privacyMode = PrivacyMode.ALL
}
}
id = 3
}
}

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More