Move to the kotlin test generator.
This commit is contained in:
parent
33e90be18b
commit
2019d89021
12
.editorconfig
Normal file
12
.editorconfig
Normal 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
|
||||
36
.github/workflows/main.yml
vendored
36
.github/workflows/main.yml
vendored
@ -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
13
.gitignore
vendored
@ -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
824
Cargo.lock
generated
@ -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"
|
||||
@ -1,5 +0,0 @@
|
||||
[workspace]
|
||||
|
||||
resolver = "2"
|
||||
|
||||
members = ["converter"]
|
||||
38
README.md
38
README.md
@ -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 Proto3’s [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
24
build.gradle.kts
Normal 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"))
|
||||
}
|
||||
@ -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"
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
//
|
||||
// Copyright 2023 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));
|
||||
@ -1 +0,0 @@
|
||||
../../../Backup.proto
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
249
gradlew
vendored
Executable 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
92
gradlew.bat
vendored
Normal 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
|
||||
@ -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"
|
||||
@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
uuid=$(uuidgen)
|
||||
|
||||
echo "UUID: $uuid"
|
||||
echo "UUID Base64: $(echo $uuid | xxd -r -p | base64)"
|
||||
@ -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
|
||||
@ -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
130
src/main/kotlin/Main.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
499
src/main/kotlin/Permutations.kt
Normal file
499
src/main/kotlin/Permutations.kt
Normal 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]
|
||||
39
src/main/kotlin/SeededRandom.kt
Normal file
39
src/main/kotlin/SeededRandom.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
274
src/main/kotlin/StandardFrames.kt
Normal file
274
src/main/kotlin/StandardFrames.kt
Normal 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
|
||||
)
|
||||
)
|
||||
}
|
||||
17
src/main/kotlin/TestCase.kt
Normal file
17
src/main/kotlin/TestCase.kt
Normal 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
97
src/main/kotlin/Util.kt
Normal 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), "{}")
|
||||
}
|
||||
126
src/main/kotlin/tests/AccountDataTestCase.kt
Normal file
126
src/main/kotlin/tests/AccountDataTestCase.kt
Normal 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
|
||||
}
|
||||
}
|
||||
30
src/main/kotlin/tests/AdHocCallTestCase.kt
Normal file
30
src/main/kotlin/tests/AdHocCallTestCase.kt
Normal 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()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
108
src/main/kotlin/tests/ChatItemContactMessageTestCase.kt
Normal file
108
src/main/kotlin/tests/ChatItemContactMessageTestCase.kt
Normal 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) }
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
43
src/main/kotlin/tests/ChatItemGiftBadgeTestCase.kt
Normal file
43
src/main/kotlin/tests/ChatItemGiftBadgeTestCase.kt
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
66
src/main/kotlin/tests/ChatItemGroupCallUpdateTestCase.kt
Normal file
66
src/main/kotlin/tests/ChatItemGroupCallUpdateTestCase.kt
Normal 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()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
75
src/main/kotlin/tests/ChatItemPaymentNotificationTestCase.kt
Normal file
75
src/main/kotlin/tests/ChatItemPaymentNotificationTestCase.kt
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
35
src/main/kotlin/tests/ChatItemProfileChangeUpdateTestCase.kt
Normal file
35
src/main/kotlin/tests/ChatItemProfileChangeUpdateTestCase.kt
Normal 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()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
39
src/main/kotlin/tests/ChatItemRemoteDeleteTestCase.kt
Normal file
39
src/main/kotlin/tests/ChatItemRemoteDeleteTestCase.kt
Normal 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()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
35
src/main/kotlin/tests/ChatItemSimpleUpdatesTestCase.kt
Normal file
35
src/main/kotlin/tests/ChatItemSimpleUpdatesTestCase.kt
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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) }
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
56
src/main/kotlin/tests/ChatItemStickerMessageTestCase.kt
Normal file
56
src/main/kotlin/tests/ChatItemStickerMessageTestCase.kt
Normal 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!!))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
30
src/main/kotlin/tests/ChatItemThreadMergeUpdateTestCase.kt
Normal file
30
src/main/kotlin/tests/ChatItemThreadMergeUpdateTestCase.kt
Normal 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()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
57
src/main/kotlin/tests/ChatTestCase.kt
Normal file
57
src/main/kotlin/tests/ChatTestCase.kt
Normal 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()
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
31
src/main/kotlin/tests/RecipientCallLinkTestCase.kt
Normal file
31
src/main/kotlin/tests/RecipientCallLinkTestCase.kt
Normal 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()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
50
src/main/kotlin/tests/RecipientContactsTestCase.kt
Normal file
50
src/main/kotlin/tests/RecipientContactsTestCase.kt
Normal 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()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
66
src/main/kotlin/tests/RecipientDistributionListTestCase.kt
Normal file
66
src/main/kotlin/tests/RecipientDistributionListTestCase.kt
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
105
src/main/kotlin/tests/RecipientGroupsTestCase.kt
Normal file
105
src/main/kotlin/tests/RecipientGroupsTestCase.kt
Normal 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()
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
14
src/main/kotlin/tests/StandardFramesTestCase.kt
Normal file
14
src/main/kotlin/tests/StandardFramesTestCase.kt
Normal 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
|
||||
}
|
||||
}
|
||||
26
src/main/kotlin/tests/StickerPackTestCase.kt
Normal file
26
src/main/kotlin/tests/StickerPackTestCase.kt
Normal 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()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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.
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
BIN
test-cases/account_data_00.binproto
Normal file
BIN
test-cases/account_data_00.binproto
Normal file
Binary file not shown.
84
test-cases/account_data_00.txtproto
Normal file
84
test-cases/account_data_00.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_01.binproto
Normal file
BIN
test-cases/account_data_01.binproto
Normal file
Binary file not shown.
99
test-cases/account_data_01.txtproto
Normal file
99
test-cases/account_data_01.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_02.binproto
Normal file
BIN
test-cases/account_data_02.binproto
Normal file
Binary file not shown.
119
test-cases/account_data_02.txtproto
Normal file
119
test-cases/account_data_02.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_03.binproto
Normal file
BIN
test-cases/account_data_03.binproto
Normal file
Binary file not shown.
107
test-cases/account_data_03.txtproto
Normal file
107
test-cases/account_data_03.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_04.binproto
Normal file
BIN
test-cases/account_data_04.binproto
Normal file
Binary file not shown.
107
test-cases/account_data_04.txtproto
Normal file
107
test-cases/account_data_04.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_05.binproto
Normal file
BIN
test-cases/account_data_05.binproto
Normal file
Binary file not shown.
101
test-cases/account_data_05.txtproto
Normal file
101
test-cases/account_data_05.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_06.binproto
Normal file
BIN
test-cases/account_data_06.binproto
Normal file
Binary file not shown.
119
test-cases/account_data_06.txtproto
Normal file
119
test-cases/account_data_06.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_07.binproto
Normal file
BIN
test-cases/account_data_07.binproto
Normal file
Binary file not shown.
104
test-cases/account_data_07.txtproto
Normal file
104
test-cases/account_data_07.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_08.binproto
Normal file
BIN
test-cases/account_data_08.binproto
Normal file
Binary file not shown.
104
test-cases/account_data_08.txtproto
Normal file
104
test-cases/account_data_08.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_09.binproto
Normal file
BIN
test-cases/account_data_09.binproto
Normal file
Binary file not shown.
85
test-cases/account_data_09.txtproto
Normal file
85
test-cases/account_data_09.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_10.binproto
Normal file
BIN
test-cases/account_data_10.binproto
Normal file
Binary file not shown.
102
test-cases/account_data_10.txtproto
Normal file
102
test-cases/account_data_10.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_11.binproto
Normal file
BIN
test-cases/account_data_11.binproto
Normal file
Binary file not shown.
91
test-cases/account_data_11.txtproto
Normal file
91
test-cases/account_data_11.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_12.binproto
Normal file
BIN
test-cases/account_data_12.binproto
Normal file
Binary file not shown.
94
test-cases/account_data_12.txtproto
Normal file
94
test-cases/account_data_12.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_13.binproto
Normal file
BIN
test-cases/account_data_13.binproto
Normal file
Binary file not shown.
91
test-cases/account_data_13.txtproto
Normal file
91
test-cases/account_data_13.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_14.binproto
Normal file
BIN
test-cases/account_data_14.binproto
Normal file
Binary file not shown.
102
test-cases/account_data_14.txtproto
Normal file
102
test-cases/account_data_14.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_15.binproto
Normal file
BIN
test-cases/account_data_15.binproto
Normal file
Binary file not shown.
91
test-cases/account_data_15.txtproto
Normal file
91
test-cases/account_data_15.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_16.binproto
Normal file
BIN
test-cases/account_data_16.binproto
Normal file
Binary file not shown.
94
test-cases/account_data_16.txtproto
Normal file
94
test-cases/account_data_16.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_17.binproto
Normal file
BIN
test-cases/account_data_17.binproto
Normal file
Binary file not shown.
91
test-cases/account_data_17.txtproto
Normal file
91
test-cases/account_data_17.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_18.binproto
Normal file
BIN
test-cases/account_data_18.binproto
Normal file
Binary file not shown.
96
test-cases/account_data_18.txtproto
Normal file
96
test-cases/account_data_18.txtproto
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
test-cases/account_data_19.binproto
Normal file
BIN
test-cases/account_data_19.binproto
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user