From 6f16ee85d7fd2ea29c49e5069165bcf549e17b1a Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 27 Aug 2026 01:47:10 +0200 Subject: [PATCH] starting with rust rewrite --- lib/core/api/api_helper.dart | 42 + lib/core/api/operations.dart | 42 + .../api/proto/server_to_client/response.dart | 28 + lib/core/api/server/prekeys.dart | 42 + lib/core/bridge.dart | 5 + lib/core/bridge/api.dart | 489 ++ lib/core/bridge/api.freezed.dart | 888 ++++ lib/core/bridge/callbacks.dart | 113 +- lib/core/bridge/groups.dart | 79 + lib/core/frb_generated.dart | 4390 ++++++++++++++++- lib/core/frb_generated.io.dart | 478 +- lib/core/frb_generated.web.dart | 478 +- lib/main.dart | 4 +- lib/src/callbacks/callbacks.dart | 115 + .../callbacks/legacy_signal.callbacks.dart | 87 + lib/src/callbacks/logging.callbacks.dart | 3 +- .../channels/video_compression.channel.dart | 5 +- .../database/drift_logging_interceptor.dart | 28 +- lib/src/database/rust_query_executor.dart | 6 +- lib/src/database/tables/contacts.table.dart | 1 - lib/src/database/tables/labels.table.dart | 16 +- lib/src/database/tables/messages.table.dart | 5 +- lib/src/database/twonly.db.g.dart | 258 +- .../protobuf/api/http/http_requests.pb.dart | 140 + .../api/http/http_requests.pbjson.dart | 46 + .../api/websocket/client_to_server.pb.dart | 152 +- .../websocket/client_to_server.pbjson.dart | 127 +- .../api/websocket/server_to_client.pb.dart | 414 +- .../websocket/server_to_client.pbjson.dart | 173 +- .../android_photo_picker.service.dart | 4 +- lib/src/services/api/api.service.dart | 108 +- .../api/client2client/errors.c2c.dart | 13 +- .../api/client2client/groups.c2c.dart | 11 +- .../api/client2client/messages.c2c.dart | 22 +- .../api/client2client/text_message.c2c.dart | 4 +- .../api/client2client/user_discovery.c2c.dart | 16 +- lib/src/services/api/messages.api.dart | 619 +-- lib/src/services/flame.service.dart | 32 +- lib/src/services/group.service.dart | 995 +--- .../signal/protocol_state.signal.dart | 4 +- lib/src/services/user_discovery.service.dart | 5 +- lib/src/utils/keyvalue.dart | 10 +- .../contact_request_badge.comp.dart | 4 +- .../custom_color_picker_dialog.comp.dart | 21 +- .../label_editor_bottom_sheet.comp.dart | 44 +- .../components/profile_qr_code.comp.dart | 20 +- .../context_menu/group.context_menu.dart | 3 +- .../context_menu/user.context_menu.dart | 3 +- .../elements/better_list_title.element.dart | 20 +- .../elements/my_icon_button.element.dart | 1 - .../reactive_tap_feedback.element.dart | 17 +- .../permissions_view.dart | 3 +- .../zoom_selector.dart | 26 +- .../media_viewer_bottom_navigation.comp.dart | 4 +- .../restore_flame.comp.dart | 6 +- .../contact/select_contact_labels.view.dart | 47 +- .../group_create_select_members.view.dart | 7 +- .../views/groups/group_member.context.dart | 4 +- .../views/onboarding/register.view.dart | 4 +- .../mock_contact_request_actions.comp.dart | 5 +- .../setup/components/next_button.comp.dart | 4 +- .../setup/components/profile_card.comp.dart | 5 +- .../recovery_from_secure_storage.view.dart | 4 +- .../views/settings/appearance.view.dart | 15 +- .../views/settings/backup/backup_utils.dart | 4 +- .../backup/components/backup_setup.comp.dart | 3 +- .../components/second_factor_picker.comp.dart | 4 +- .../status.passwordless_recovery.comp.dart | 4 +- .../components/threshold_picker.comp.dart | 4 +- ...p_a_friend.passwordless_recovery.view.dart | 12 +- .../data_and_storage/manage_storage.view.dart | 3 +- pubspec.lock | 78 +- pubspec.yaml | 1 + ...1cd7445eb0eb0a08be9a478cd38f4a1a26bb5.json | 12 + ...109bfa0267e3668695ab9169cee567253d92b.json | 12 + ...7571d8dd6b4618434e36f2d9d97a9178ddb3b.json | 21 + ...bd54b690ec20c817a2cb1a29b0ac0fcc884ad.json | 12 + ...e9475f8b8964a8cbce2b0dd4919bd5953bc08.json | 26 + ...24284d8fb91c141ba989e2e3f4846c93d2a4c.json | 12 + ...659e1b3d7b0ff8fc6037fbf6a89fbc8051584.json | 12 + ...15a0c278dcda82838188586821354c306b1e9.json | 21 + ...5eddd33a03d40afa505b7e0b5f59e2f731a20.json | 12 + ...7d4aee9b08c0f0df9ad97431f408cae37fb7e.json | 12 + ...0023d518b365156981783a621484eefa89291.json | 21 + ...b8df3533d34a45008bddf61e1090ae1ace885.json | 12 + ...0c6ffd1460d932a31f3099a697ac5c35b882f.json | 12 + ...730d18581563906c91333025699708269181c.json | 12 + ...42d4cab0069c385c3efe8be764c6eacd8e112.json | 12 + ...3e375eccc0513cf2ae0a7e6f95f817b65b1e6.json | 26 + ...9606f04c75c01243c14149a8f7e0e32bb408d.json | 12 + ...3e1632305e9af03a872635ffe99a822ddd643.json | 26 + ...d4b1a117ad3500d9ea6a43d22c461623850c9.json | 26 + ...d537ffee52100fe1cd01b25165c6557ac16f9.json | 12 + ...84c07426a989e8b1299414d6f5e7a9cdccadb.json | 12 + ...f93d5f354e7a9f380ce3420c8feb6378c9ac3.json | 21 + ...ee125effcfc116476eeda3fbdfc7eb4c26ec9.json | 12 + ...0b0abaf5e05e73a4c534ee45bc0b950c1e356.json | 26 + ...03b45f8106967cbdc6e25cb46d92086811ac3.json | 12 + ...6dc6eb8a039744e80facc991c7fa3aa8be029.json | 26 + ...49eb2a51189212e1cdb99fb8211bd614c9558.json | 12 + ...a9dd1913ecf014c117f3b7d77e6d8267dd7d6.json | 12 + ...88aff109a5dac250d5402f33b55ec9d127f76.json | 21 + ...39fab7ed13b2124ec6153a0b55a3afe3c4ded.json | 26 + ...e90bdea08f915d736745592ddd375171787f8.json | 12 + ...5210f118dc0ab28d445b2375cf789987525ab.json | 21 + ...cad54f2b209e74849509c1905e0f106756c7d.json | 26 + ...15c9652305c82281efd37491139b814a4562a.json | 12 + ...2dd071b91356acbf297ced95ebc496ff03091.json | 12 + ...2d9ad0a5cd529fae889a28fff935174528ed1.json | 26 + ...e078e33f3cc279660cdf2ade2319b940e7a4e.json | 12 + ...9c2e2103def24c54db6c58a9beffd80036472.json | 12 + ...81b06898954f5c898abf280b9ab02d96ff457.json | 12 + ...f513be49cc860f298cf6e2cd9dc1f71265047.json | 12 + ...d939ca95add84b329bd100ef14911babe33d1.json | 26 + ...5df7f39ee9353043ec309fe128183fe1cdcd6.json | 21 + ...a932fa530f6aac3f13d41e8db5f9805691cfb.json | 12 + ...b485dea137371d504e1d3edf724174dff0264.json | 12 + ...6e780683555d0c125183b949846950cfac139.json | 12 + ...38eedcd0664aa6c8a6d2190802b7a85e77371.json | 21 + ...f96a52256b401625ae513f9b9c7cef623c1d8.json | 12 + ...b996779d7aa4d74b69c8da97f0c429a41a3aa.json | 12 + ...2a99c05846d0d37f7d7024e8345562072b0fb.json | 26 + ...b5b2db4edab812e94912b4a70c3a83ad91933.json | 12 + ...5528dbbf783b55a6731e43fae04b1f90fa13b.json | 12 + ...8e278854f560b6b184e02ea9eddad4d8d4a51.json | 12 + ...f3854d2d607de0662b9e5f7d279f5e9417ebf.json | 12 + ...1d6c338937916d57ab6640f03ad44f6845fb6.json | 12 + ...027a03ca6d6e2b163d16670fd8505f5d27142.json | 21 + ...19bd0f1ba1f98232e9d61eb0b7c920a5526f0.json | 26 + ...50c364e2e4c3b4af9d015fb2fbb87770e703f.json | 12 + ...ca886e42740dddf2e4db1a4a289e4f75a1ff4.json | 21 + ...4ebfc5c265e0b2b0bb9a032ce90778a68e512.json | 26 + ...69d4666813e03732f448f83a884134c300bfd.json | 12 + ...9f0d61d427a97869c42361c3b7611deb3cad7.json | 12 + ...410af6830f49d07258693fba1dfa765e70d23.json | 26 + ...c51562ba580cc34f8835163ee7145833e1455.json | 12 + ...2f0f96e10a5e99788123a294763100d4e25dc.json | 12 + ...a3c8cc58b2d692fb79e9445e070d949afc9ab.json | 12 + ...99ff9352ab497a2abb687a1b8ef3cb09701ab.json | 21 + ...1d57b779c3fd5c8daf1094fd66a147aea973d.json | 12 + ...48e905059918bd150b947d6341703ea9ee701.json | 21 + ...fce94fa1490b234db5db72a3775894eb5b771.json | 12 + ...8e016558d42c4b2ac93ce45a8eed0a16b9871.json | 12 + ...aa826377c5ce5e09749f780c3a6a9870157d9.json | 12 + ...f03bdb10c4c2dd00a4a57ab49c27ff7fa8d05.json | 12 + ...60255f76c7bacdd0adce57faba9fa46bce96b.json | 12 + ...a41a7f3445ea2c041dd058312564ac21af3ae.json | 12 + ...b0fe4a4b9a8630f32dd70d6397d5d0fa8d199.json | 26 + ...80d37fdec7b2d995b2553e534e9dc6e6db195.json | 47 + ...d4250b566cda50870d09ecedc7e8eb81c5c26.json | 21 + ...c3f323a44b7d69bee0e5f85e82a731011893f.json | 12 + ...dad0a83e9f7164736bdd1669650e52f879ed5.json | 12 + ...807b1a28de31a62f9d8f78c2e447f79a14f98.json | 12 + ...0781076ed1d3ca59dd52030c892dc4dc15224.json | 12 + ...5cfeeeddc63628ed9ace6db3107ab2a4262ec.json | 12 + ...040d91c2e430c936b91bd02a3f7f562edf658.json | 12 + ...7102e792e3d27722294bdc626acde1b46ba2e.json | 12 + ...1b99f3c78465544975d85e9e753337b2fce3e.json | 12 + ...24cfdf518d18dca57ba1693b0d51345a24ce1.json | 12 + ...2c07be4b7c353d12372842b6cd5eb80fb7fd3.json | 12 + ...49407492b87fc3fe4fd56dac858a77a8f6845.json | 12 + ...4e5646485b1a600c159dc7ac13370d8bba9d9.json | 12 + ...156eabc8050171ad47073f389df070f767815.json | 21 + ...63b9dd81cf5cf1545c7545c8a7ab4070d44a5.json | 21 + ...9e36b6e8ebfd52fb3828c948693e9ca5c7dbb.json | 12 + ...7c9a81dfe0f348725c57f7e01205d68b687a9.json | 12 + ...bbf5587148d472b5ae7d1b4db79fe4a2c0489.json | 12 + ...129471217ba0bff76ae265b0e0d5b6f7c73fe.json | 38 + ...1972b70885339387e1017cd2013e5a35c60b0.json | 12 + ...6ef21dacefd2a248a2a8256cd100451d9889a.json | 12 + ...fca0fabae27202f09976f1c0159cc7c3b4329.json | 12 + ...fd051f2adb7f7225fa97eae53172b0f5b19fa.json | 12 + ...e1befa52f1dc0f709de124d9c6f5ec29c88f0.json | 12 + ...47cb700bc3039c178e133def8a2dc2e8aabb6.json | 12 + rust/Cargo.lock | 795 ++- rust/Cargo.toml | 13 + rust/build.rs | 36 +- rust/scripts/prepare_sqlx.sh | 2 +- rust/src/api/groups.rs | 166 + .../incoming/client2client/additional_data.rs | 104 + .../incoming/client2client/contact.rs | 223 + .../messages/incoming/client2client/errors.rs | 59 + .../messages/incoming/client2client/groups.rs | 309 ++ .../messages/incoming/client2client/media.rs | 265 + .../incoming/client2client/messages.rs | 778 +++ .../messages/incoming/client2client/mod.rs | 260 + .../incoming/client2client/pushkeys.rs | 65 + .../incoming/client2client/reaction.rs | 63 + .../incoming/client2client/recovery.rs | 103 + .../incoming/client2client/text_message.rs | 38 + .../client2client/typing_indicator.rs | 33 + .../incoming/client2client/user_discovery.rs | 120 + .../incoming/client2client/verification.rs | 22 + rust/src/api/messages/incoming/mod.rs | 346 ++ rust/src/api/messages/mod.rs | 58 + rust/src/api/messages/outgoing.rs | 160 + rust/src/api/mod.rs | 14 + .../api/proto/api/http/http_requests.proto | 40 + .../api/websocket/client_to_server.proto | 296 ++ rust/src/api/proto/api/websocket/error.proto | 48 + .../api/websocket/server_to_client.proto | 232 + rust/src/api/proto/mod.rs | 16 + rust/src/api/runtime.rs | 237 + rust/src/api/runtime/auth.rs | 400 ++ rust/src/api/runtime/client.rs | 195 + rust/src/api/runtime/helpers.rs | 104 + rust/src/api/runtime/request.rs | 241 + rust/src/api/server.rs | 25 + rust/src/api/server/account.rs | 159 + rust/src/api/server/contacts.rs | 156 + rust/src/api/server/memories.rs | 118 + rust/src/api/server/passwordless.rs | 130 + rust/src/api/server/prekeys.rs | 100 + rust/src/api/server/purchases.rs | 97 + rust/src/api/server/transport.rs | 78 + rust/src/backup/backup_archive.rs | 102 +- rust/src/backup/backup_identity.rs | 5 + rust/src/backup/mod.rs | 5 + rust/src/bridge/api.rs | 634 +++ rust/src/bridge/callbacks.rs | 43 + rust/src/bridge/callbacks/log.rs | 5 + rust/src/bridge/callbacks/macros.rs | 5 + rust/src/bridge/groups.rs | 89 + rust/src/bridge/mod.rs | 19 +- rust/src/bridge/wrapper/app_database.rs | 5 + rust/src/bridge/wrapper/backup.rs | 5 + rust/src/bridge/wrapper/key_manager.rs | 5 + rust/src/bridge/wrapper/mod.rs | 5 + rust/src/bridge/wrapper/signal.rs | 7 +- rust/src/bridge/wrapper/user_discovery.rs | 5 + rust/src/context.rs | 236 +- rust/src/database/app/legacy_import.rs | 140 +- .../app/migrations/0002_api_state.sql | 25 + rust/src/database/app/mod.rs | 30 +- rust/src/database/app/tables/contact.rs | 215 + rust/src/database/app/tables/group_history.rs | 82 + rust/src/database/app/tables/groups.rs | 414 ++ .../database/app/tables/key_verification.rs | 70 + rust/src/database/app/tables/message.rs | 110 + rust/src/database/app/tables/mod.rs | 21 + rust/src/database/app/tables/receipt.rs | 150 + rust/src/database/mod.rs | 5 + rust/src/database/signal/mod.rs | 23 +- rust/src/error.rs | 43 + rust/src/frb_generated.rs | 4135 +++++++++++++++- rust/src/keys/backup_password_keys.rs | 5 + rust/src/keys/identity_key/mod.rs | 5 + .../keys/identity_key/signal_identity_key.rs | 5 + rust/src/keys/main_key.rs | 9 +- rust/src/keys/mod.rs | 5 + rust/src/lib.rs | 14 +- rust/src/log.rs | 117 +- rust/src/sealed_sender.rs | 26 +- rust/src/secure_storage.rs | 5 + rust/src/services/contacts.rs | 238 + rust/src/services/groups/crypto.rs | 49 + rust/src/services/groups/mod.rs | 593 +++ rust/src/services/groups/model.rs | 47 + rust/src/services/messages.rs | 357 ++ rust/src/services/mod.rs | 8 + rust/src/signal/assert_send.rs | 5 + rust/src/signal/engine.rs | 159 +- rust/src/signal/mod.rs | 5 + rust/src/signal/store.rs | 196 +- rust/src/user_config.rs | 104 + rust/src/user_discovery/error.rs | 5 + rust/src/user_discovery/mod.rs | 5 + .../user_discovery/stores/in_memory_store.rs | 5 + rust/src/user_discovery/stores/mod.rs | 5 + rust/src/user_discovery/stores/native.rs | 31 +- rust/src/user_discovery/tests.rs | 5 + rust/src/user_discovery/traits.rs | 5 + rust/src/utils.rs | 25 + rust/tests/api.rs | 356 ++ rust/tests/api_testing/mod.rs | 1 + rust/tests/api_testing/tester.rs | 470 ++ rust/tests/pqxdh_tests.rs | 73 - test/mocks/platform_channels.dart | 73 +- test/mocks/test_client.dart | 2 +- test/services/group_service_test.dart | 148 - test/services/messages_purge_test.dart | 141 +- 281 files changed, 26355 insertions(+), 2784 deletions(-) create mode 100644 lib/core/api/api_helper.dart create mode 100644 lib/core/api/operations.dart create mode 100644 lib/core/api/proto/server_to_client/response.dart create mode 100644 lib/core/api/server/prekeys.dart create mode 100644 lib/core/bridge/api.dart create mode 100644 lib/core/bridge/api.freezed.dart create mode 100644 lib/core/bridge/groups.dart create mode 100644 lib/src/callbacks/legacy_signal.callbacks.dart create mode 100644 rust/.sqlx/query-04bc6d004a70fe1ef001c3fe31f1cd7445eb0eb0a08be9a478cd38f4a1a26bb5.json create mode 100644 rust/.sqlx/query-065a4f476b0322c400c1756c0cd109bfa0267e3668695ab9169cee567253d92b.json create mode 100644 rust/.sqlx/query-07d19c693f05b07d6d3c29ff7c77571d8dd6b4618434e36f2d9d97a9178ddb3b.json create mode 100644 rust/.sqlx/query-0a000bb74996bb70072c0b0dbb3bd54b690ec20c817a2cb1a29b0ac0fcc884ad.json create mode 100644 rust/.sqlx/query-0c7011264675a005d09dbe0f690e9475f8b8964a8cbce2b0dd4919bd5953bc08.json create mode 100644 rust/.sqlx/query-117582dbddd40553a8a622ed65b24284d8fb91c141ba989e2e3f4846c93d2a4c.json create mode 100644 rust/.sqlx/query-12cfa4cbc893602b97027c6f48c659e1b3d7b0ff8fc6037fbf6a89fbc8051584.json create mode 100644 rust/.sqlx/query-174e1f8d22842961cc26633c7f715a0c278dcda82838188586821354c306b1e9.json create mode 100644 rust/.sqlx/query-1acafcad398cb31491dcee91a005eddd33a03d40afa505b7e0b5f59e2f731a20.json create mode 100644 rust/.sqlx/query-1acb6b89980e132de5c016723d87d4aee9b08c0f0df9ad97431f408cae37fb7e.json create mode 100644 rust/.sqlx/query-2070ef6adc65d84c9e5b4fd451a0023d518b365156981783a621484eefa89291.json create mode 100644 rust/.sqlx/query-2136bd64733c0268d4da52cf36db8df3533d34a45008bddf61e1090ae1ace885.json create mode 100644 rust/.sqlx/query-23bf972a725c666365f5bd324f10c6ffd1460d932a31f3099a697ac5c35b882f.json create mode 100644 rust/.sqlx/query-2484b898369866b8e84d2cdcfb5730d18581563906c91333025699708269181c.json create mode 100644 rust/.sqlx/query-26099393d47834ce775cee6533f42d4cab0069c385c3efe8be764c6eacd8e112.json create mode 100644 rust/.sqlx/query-272b5a6defc13fb9fe0cedeab4e3e375eccc0513cf2ae0a7e6f95f817b65b1e6.json create mode 100644 rust/.sqlx/query-27b030470f79c8fdcd05b10291a9606f04c75c01243c14149a8f7e0e32bb408d.json create mode 100644 rust/.sqlx/query-2e26c6221d3d2d2d64087fe5c563e1632305e9af03a872635ffe99a822ddd643.json create mode 100644 rust/.sqlx/query-2ef49cbb297f02183510fd85882d4b1a117ad3500d9ea6a43d22c461623850c9.json create mode 100644 rust/.sqlx/query-3484d2f40693259f1e6030383a9d537ffee52100fe1cd01b25165c6557ac16f9.json create mode 100644 rust/.sqlx/query-370baafebd6f09754b0e29cf2b384c07426a989e8b1299414d6f5e7a9cdccadb.json create mode 100644 rust/.sqlx/query-37ec1618898a149c969bc80fb54f93d5f354e7a9f380ce3420c8feb6378c9ac3.json create mode 100644 rust/.sqlx/query-3a31981dfd8f53dd63744ae7c15ee125effcfc116476eeda3fbdfc7eb4c26ec9.json create mode 100644 rust/.sqlx/query-400da22eb89f22254b6de1cfcea0b0abaf5e05e73a4c534ee45bc0b950c1e356.json create mode 100644 rust/.sqlx/query-42faf2b3351e83b2313cdac31bb03b45f8106967cbdc6e25cb46d92086811ac3.json create mode 100644 rust/.sqlx/query-4681180c0b45a7f3941ee9d58966dc6eb8a039744e80facc991c7fa3aa8be029.json create mode 100644 rust/.sqlx/query-49a771af89168b9e3d09e2b0cbe49eb2a51189212e1cdb99fb8211bd614c9558.json create mode 100644 rust/.sqlx/query-4d18233931fbadbfc01254449b9a9dd1913ecf014c117f3b7d77e6d8267dd7d6.json create mode 100644 rust/.sqlx/query-500c182a7888c7b36c257936ba188aff109a5dac250d5402f33b55ec9d127f76.json create mode 100644 rust/.sqlx/query-52b32153dd0cc9f5efca773487839fab7ed13b2124ec6153a0b55a3afe3c4ded.json create mode 100644 rust/.sqlx/query-5357d7c57bd2d7a7d2c52e4dd41e90bdea08f915d736745592ddd375171787f8.json create mode 100644 rust/.sqlx/query-5703922d81e137ae18f060aebc15210f118dc0ab28d445b2375cf789987525ab.json create mode 100644 rust/.sqlx/query-5988df8ec10b625bbd16ddcd2dccad54f2b209e74849509c1905e0f106756c7d.json create mode 100644 rust/.sqlx/query-5b848eba9965a386b9189ff911015c9652305c82281efd37491139b814a4562a.json create mode 100644 rust/.sqlx/query-5c21ee90d244d4859b3b9544f182dd071b91356acbf297ced95ebc496ff03091.json create mode 100644 rust/.sqlx/query-5c8abe1148ff7ea5b69309413292d9ad0a5cd529fae889a28fff935174528ed1.json create mode 100644 rust/.sqlx/query-5d60b9223c1454cb582c834911fe078e33f3cc279660cdf2ade2319b940e7a4e.json create mode 100644 rust/.sqlx/query-5f88312cd175e5fdf5539cf9bc49c2e2103def24c54db6c58a9beffd80036472.json create mode 100644 rust/.sqlx/query-61c2483b50f9f5c8a586eaa2c2381b06898954f5c898abf280b9ab02d96ff457.json create mode 100644 rust/.sqlx/query-64de4aee234dbfab635c65bb995f513be49cc860f298cf6e2cd9dc1f71265047.json create mode 100644 rust/.sqlx/query-66b487bac770deb373505952150d939ca95add84b329bd100ef14911babe33d1.json create mode 100644 rust/.sqlx/query-6c01273c757a772f2c2726b75a95df7f39ee9353043ec309fe128183fe1cdcd6.json create mode 100644 rust/.sqlx/query-6c4c06153ae73f7e638284c8f1ea932fa530f6aac3f13d41e8db5f9805691cfb.json create mode 100644 rust/.sqlx/query-6ccbb2b713538eff9f1f875d34db485dea137371d504e1d3edf724174dff0264.json create mode 100644 rust/.sqlx/query-6f27065bbb1277aa35530ac6bce6e780683555d0c125183b949846950cfac139.json create mode 100644 rust/.sqlx/query-713b45ba555a737f059569886ba38eedcd0664aa6c8a6d2190802b7a85e77371.json create mode 100644 rust/.sqlx/query-71d28416e43499e2d898f978567f96a52256b401625ae513f9b9c7cef623c1d8.json create mode 100644 rust/.sqlx/query-7498999dcb013e18ec840820caeb996779d7aa4d74b69c8da97f0c429a41a3aa.json create mode 100644 rust/.sqlx/query-790ea7b81c3e1c7b65c00f160702a99c05846d0d37f7d7024e8345562072b0fb.json create mode 100644 rust/.sqlx/query-81135d8e9734594afc97deee965b5b2db4edab812e94912b4a70c3a83ad91933.json create mode 100644 rust/.sqlx/query-81485ddfb46f464b8f8c6813a2e5528dbbf783b55a6731e43fae04b1f90fa13b.json create mode 100644 rust/.sqlx/query-82d093614f398c7aac81e4081858e278854f560b6b184e02ea9eddad4d8d4a51.json create mode 100644 rust/.sqlx/query-84c77f8397fdf0dcf0d3c9ccab7f3854d2d607de0662b9e5f7d279f5e9417ebf.json create mode 100644 rust/.sqlx/query-862a9b6bef72830c7439b51f3311d6c338937916d57ab6640f03ad44f6845fb6.json create mode 100644 rust/.sqlx/query-8babc49259c8ba5332ad81a7c11027a03ca6d6e2b163d16670fd8505f5d27142.json create mode 100644 rust/.sqlx/query-8edba8bc58b60b678cb1730572119bd0f1ba1f98232e9d61eb0b7c920a5526f0.json create mode 100644 rust/.sqlx/query-93e9fb25a53d3de02840e86d0c450c364e2e4c3b4af9d015fb2fbb87770e703f.json create mode 100644 rust/.sqlx/query-96a6b8415b84654e0bc41298aa4ca886e42740dddf2e4db1a4a289e4f75a1ff4.json create mode 100644 rust/.sqlx/query-9b551cd015eecff75483d9083ee4ebfc5c265e0b2b0bb9a032ce90778a68e512.json create mode 100644 rust/.sqlx/query-9b83943b4b9917ba6e099bbf0bd69d4666813e03732f448f83a884134c300bfd.json create mode 100644 rust/.sqlx/query-9f0ca2175ab73256273ddcb658a9f0d61d427a97869c42361c3b7611deb3cad7.json create mode 100644 rust/.sqlx/query-a110a1d7903937bf0ef806afc52410af6830f49d07258693fba1dfa765e70d23.json create mode 100644 rust/.sqlx/query-a28ac461ed3b995287f2cfd9480c51562ba580cc34f8835163ee7145833e1455.json create mode 100644 rust/.sqlx/query-a5f4452d79d2ac19f158fb5c7b92f0f96e10a5e99788123a294763100d4e25dc.json create mode 100644 rust/.sqlx/query-a90a25b72f9fb6269a4943fda3da3c8cc58b2d692fb79e9445e070d949afc9ab.json create mode 100644 rust/.sqlx/query-ac89da5b76bdd44d6c3c8901b2d99ff9352ab497a2abb687a1b8ef3cb09701ab.json create mode 100644 rust/.sqlx/query-ad8ff2a03d99759d40d59f0a4371d57b779c3fd5c8daf1094fd66a147aea973d.json create mode 100644 rust/.sqlx/query-ae3320f8a4626eb44579f1319e448e905059918bd150b947d6341703ea9ee701.json create mode 100644 rust/.sqlx/query-b109c2c02d61f15cd9b1a06b7eafce94fa1490b234db5db72a3775894eb5b771.json create mode 100644 rust/.sqlx/query-b199f58b951b90331edbb5cbb378e016558d42c4b2ac93ce45a8eed0a16b9871.json create mode 100644 rust/.sqlx/query-b3242f4964184d187d818d2e112aa826377c5ce5e09749f780c3a6a9870157d9.json create mode 100644 rust/.sqlx/query-b375c5808bf4526a65d96c6262cf03bdb10c4c2dd00a4a57ab49c27ff7fa8d05.json create mode 100644 rust/.sqlx/query-b3da8b1a7a6985e1df47fe590ae60255f76c7bacdd0adce57faba9fa46bce96b.json create mode 100644 rust/.sqlx/query-b400f362374072f8974fae82ed2a41a7f3445ea2c041dd058312564ac21af3ae.json create mode 100644 rust/.sqlx/query-c0b385573c3b8b67aeabad4027eb0fe4a4b9a8630f32dd70d6397d5d0fa8d199.json create mode 100644 rust/.sqlx/query-c1993cbf0d0255b2e68d0bf4ffc80d37fdec7b2d995b2553e534e9dc6e6db195.json create mode 100644 rust/.sqlx/query-c57017d2c9c2b4b2282f89010ddd4250b566cda50870d09ecedc7e8eb81c5c26.json create mode 100644 rust/.sqlx/query-c5cbd0af68a108e02fb8c269760c3f323a44b7d69bee0e5f85e82a731011893f.json create mode 100644 rust/.sqlx/query-c915911be387a4e7b49e184e55fdad0a83e9f7164736bdd1669650e52f879ed5.json create mode 100644 rust/.sqlx/query-ca2e9b5cabae7722b0ad8fe3d5d807b1a28de31a62f9d8f78c2e447f79a14f98.json create mode 100644 rust/.sqlx/query-cd9f690447bdbe9dc52534194400781076ed1d3ca59dd52030c892dc4dc15224.json create mode 100644 rust/.sqlx/query-d1b24b806f4071108ba198504845cfeeeddc63628ed9ace6db3107ab2a4262ec.json create mode 100644 rust/.sqlx/query-d50a6ee6ff69408981408585348040d91c2e430c936b91bd02a3f7f562edf658.json create mode 100644 rust/.sqlx/query-d8a8544c1d3eafe0b9c6f0ebd1b7102e792e3d27722294bdc626acde1b46ba2e.json create mode 100644 rust/.sqlx/query-da86a64379fbe00301ccdd82c721b99f3c78465544975d85e9e753337b2fce3e.json create mode 100644 rust/.sqlx/query-da9609bbcd1676a863f4612f19a24cfdf518d18dca57ba1693b0d51345a24ce1.json create mode 100644 rust/.sqlx/query-db998a6faae4968d03efb6496202c07be4b7c353d12372842b6cd5eb80fb7fd3.json create mode 100644 rust/.sqlx/query-e2a5bdab035e10052f1091991d249407492b87fc3fe4fd56dac858a77a8f6845.json create mode 100644 rust/.sqlx/query-e52ee5a2c980f153a9b3b901ab74e5646485b1a600c159dc7ac13370d8bba9d9.json create mode 100644 rust/.sqlx/query-e5cad0116aeef8212079f07546f156eabc8050171ad47073f389df070f767815.json create mode 100644 rust/.sqlx/query-e71ab6c0bf78b31953f11acc15663b9dd81cf5cf1545c7545c8a7ab4070d44a5.json create mode 100644 rust/.sqlx/query-e9233f4bcd226a8536865b908ee9e36b6e8ebfd52fb3828c948693e9ca5c7dbb.json create mode 100644 rust/.sqlx/query-eefe20114e1b151c48f308f2f197c9a81dfe0f348725c57f7e01205d68b687a9.json create mode 100644 rust/.sqlx/query-ef4732ab68de219079b3f2a80bebbf5587148d472b5ae7d1b4db79fe4a2c0489.json create mode 100644 rust/.sqlx/query-f14a47bb8c173ad393d4822151e129471217ba0bff76ae265b0e0d5b6f7c73fe.json create mode 100644 rust/.sqlx/query-f435816601215c495454f2641a01972b70885339387e1017cd2013e5a35c60b0.json create mode 100644 rust/.sqlx/query-f50e328e40de6f4f886a524118b6ef21dacefd2a248a2a8256cd100451d9889a.json create mode 100644 rust/.sqlx/query-f579d5670f62ff8523d668a7789fca0fabae27202f09976f1c0159cc7c3b4329.json create mode 100644 rust/.sqlx/query-f93ff998e086686be070b8d377dfd051f2adb7f7225fa97eae53172b0f5b19fa.json create mode 100644 rust/.sqlx/query-fdc73a53a58eea5df4925e2a11ce1befa52f1dc0f709de124d9c6f5ec29c88f0.json create mode 100644 rust/.sqlx/query-febc50ab89aa3a9bbb2e9333d8547cb700bc3039c178e133def8a2dc2e8aabb6.json create mode 100644 rust/src/api/groups.rs create mode 100644 rust/src/api/messages/incoming/client2client/additional_data.rs create mode 100644 rust/src/api/messages/incoming/client2client/contact.rs create mode 100644 rust/src/api/messages/incoming/client2client/errors.rs create mode 100644 rust/src/api/messages/incoming/client2client/groups.rs create mode 100644 rust/src/api/messages/incoming/client2client/media.rs create mode 100644 rust/src/api/messages/incoming/client2client/messages.rs create mode 100644 rust/src/api/messages/incoming/client2client/mod.rs create mode 100644 rust/src/api/messages/incoming/client2client/pushkeys.rs create mode 100644 rust/src/api/messages/incoming/client2client/reaction.rs create mode 100644 rust/src/api/messages/incoming/client2client/recovery.rs create mode 100644 rust/src/api/messages/incoming/client2client/text_message.rs create mode 100644 rust/src/api/messages/incoming/client2client/typing_indicator.rs create mode 100644 rust/src/api/messages/incoming/client2client/user_discovery.rs create mode 100644 rust/src/api/messages/incoming/client2client/verification.rs create mode 100644 rust/src/api/messages/incoming/mod.rs create mode 100644 rust/src/api/messages/mod.rs create mode 100644 rust/src/api/messages/outgoing.rs create mode 100644 rust/src/api/mod.rs create mode 100644 rust/src/api/proto/api/http/http_requests.proto create mode 100644 rust/src/api/proto/api/websocket/client_to_server.proto create mode 100644 rust/src/api/proto/api/websocket/error.proto create mode 100644 rust/src/api/proto/api/websocket/server_to_client.proto create mode 100644 rust/src/api/proto/mod.rs create mode 100644 rust/src/api/runtime.rs create mode 100644 rust/src/api/runtime/auth.rs create mode 100644 rust/src/api/runtime/client.rs create mode 100644 rust/src/api/runtime/helpers.rs create mode 100644 rust/src/api/runtime/request.rs create mode 100644 rust/src/api/server.rs create mode 100644 rust/src/api/server/account.rs create mode 100644 rust/src/api/server/contacts.rs create mode 100644 rust/src/api/server/memories.rs create mode 100644 rust/src/api/server/passwordless.rs create mode 100644 rust/src/api/server/prekeys.rs create mode 100644 rust/src/api/server/purchases.rs create mode 100644 rust/src/api/server/transport.rs create mode 100644 rust/src/bridge/api.rs create mode 100644 rust/src/bridge/groups.rs create mode 100644 rust/src/database/app/migrations/0002_api_state.sql create mode 100644 rust/src/database/app/tables/contact.rs create mode 100644 rust/src/database/app/tables/group_history.rs create mode 100644 rust/src/database/app/tables/groups.rs create mode 100644 rust/src/database/app/tables/key_verification.rs create mode 100644 rust/src/database/app/tables/message.rs create mode 100644 rust/src/database/app/tables/mod.rs create mode 100644 rust/src/database/app/tables/receipt.rs create mode 100644 rust/src/services/contacts.rs create mode 100644 rust/src/services/groups/crypto.rs create mode 100644 rust/src/services/groups/mod.rs create mode 100644 rust/src/services/groups/model.rs create mode 100644 rust/src/services/messages.rs create mode 100644 rust/src/services/mod.rs create mode 100644 rust/src/user_config.rs create mode 100644 rust/tests/api.rs create mode 100644 rust/tests/api_testing/mod.rs create mode 100644 rust/tests/api_testing/tester.rs delete mode 100644 rust/tests/pqxdh_tests.rs delete mode 100644 test/services/group_service_test.dart diff --git a/lib/core/api/api_helper.dart b/lib/core/api/api_helper.dart new file mode 100644 index 00000000..9d30b052 --- /dev/null +++ b/lib/core/api/api_helper.dart @@ -0,0 +1,42 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class PqcPreKeyInput { + final PlatformInt64 eccPreKeyId; + final Uint8List eccPreKey; + final PlatformInt64 kyberPreKeyId; + final Uint8List kyberPreKey; + final Uint8List kyberPreKeySignature; + + const PqcPreKeyInput({ + required this.eccPreKeyId, + required this.eccPreKey, + required this.kyberPreKeyId, + required this.kyberPreKey, + required this.kyberPreKeySignature, + }); + + @override + int get hashCode => + eccPreKeyId.hashCode ^ + eccPreKey.hashCode ^ + kyberPreKeyId.hashCode ^ + kyberPreKey.hashCode ^ + kyberPreKeySignature.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PqcPreKeyInput && + runtimeType == other.runtimeType && + eccPreKeyId == other.eccPreKeyId && + eccPreKey == other.eccPreKey && + kyberPreKeyId == other.kyberPreKeyId && + kyberPreKey == other.kyberPreKey && + kyberPreKeySignature == other.kyberPreKeySignature; +} diff --git a/lib/core/api/operations.dart b/lib/core/api/operations.dart new file mode 100644 index 00000000..9d30b052 --- /dev/null +++ b/lib/core/api/operations.dart @@ -0,0 +1,42 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class PqcPreKeyInput { + final PlatformInt64 eccPreKeyId; + final Uint8List eccPreKey; + final PlatformInt64 kyberPreKeyId; + final Uint8List kyberPreKey; + final Uint8List kyberPreKeySignature; + + const PqcPreKeyInput({ + required this.eccPreKeyId, + required this.eccPreKey, + required this.kyberPreKeyId, + required this.kyberPreKey, + required this.kyberPreKeySignature, + }); + + @override + int get hashCode => + eccPreKeyId.hashCode ^ + eccPreKey.hashCode ^ + kyberPreKeyId.hashCode ^ + kyberPreKey.hashCode ^ + kyberPreKeySignature.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PqcPreKeyInput && + runtimeType == other.runtimeType && + eccPreKeyId == other.eccPreKeyId && + eccPreKey == other.eccPreKey && + kyberPreKeyId == other.kyberPreKeyId && + kyberPreKey == other.kyberPreKey && + kyberPreKeySignature == other.kyberPreKeySignature; +} diff --git a/lib/core/api/proto/server_to_client/response.dart b/lib/core/api/proto/server_to_client/response.dart new file mode 100644 index 00000000..da8a4b8a --- /dev/null +++ b/lib/core/api/proto/server_to_client/response.dart @@ -0,0 +1,28 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../../../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class ProofOfWork { + final String prefix; + final PlatformInt64 difficulty; + + const ProofOfWork({ + required this.prefix, + required this.difficulty, + }); + + @override + int get hashCode => prefix.hashCode ^ difficulty.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ProofOfWork && + runtimeType == other.runtimeType && + prefix == other.prefix && + difficulty == other.difficulty; +} diff --git a/lib/core/api/server/prekeys.dart b/lib/core/api/server/prekeys.dart new file mode 100644 index 00000000..a918f4c9 --- /dev/null +++ b/lib/core/api/server/prekeys.dart @@ -0,0 +1,42 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class PqcPreKeyInput { + final PlatformInt64 eccPreKeyId; + final Uint8List eccPreKey; + final PlatformInt64 kyberPreKeyId; + final Uint8List kyberPreKey; + final Uint8List kyberPreKeySignature; + + const PqcPreKeyInput({ + required this.eccPreKeyId, + required this.eccPreKey, + required this.kyberPreKeyId, + required this.kyberPreKey, + required this.kyberPreKeySignature, + }); + + @override + int get hashCode => + eccPreKeyId.hashCode ^ + eccPreKey.hashCode ^ + kyberPreKeyId.hashCode ^ + kyberPreKey.hashCode ^ + kyberPreKeySignature.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PqcPreKeyInput && + runtimeType == other.runtimeType && + eccPreKeyId == other.eccPreKeyId && + eccPreKey == other.eccPreKey && + kyberPreKeyId == other.kyberPreKeyId && + kyberPreKey == other.kyberPreKey && + kyberPreKeySignature == other.kyberPreKeySignature; +} diff --git a/lib/core/bridge.dart b/lib/core/bridge.dart index 487fa706..221c3e17 100644 --- a/lib/core/bridge.dart +++ b/lib/core/bridge.dart @@ -12,6 +12,11 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; Future initializeTwonlyFlutter({required InitConfig config}) => RustLib.instance.api.crateBridgeInitializeTwonlyFlutter(config: config); +/// Initializes the complete Rust runtime without Flutter or callback setup. +/// Background executables should call this before using `RustApi`. +Future initializeTwonlyStandalone({required InitConfig config}) => + RustLib.instance.api.crateBridgeInitializeTwonlyStandalone(config: config); + class InitConfig { final String databaseDir; final String dataDir; diff --git a/lib/core/bridge/api.dart b/lib/core/bridge/api.dart new file mode 100644 index 00000000..9ce3f7cf --- /dev/null +++ b/lib/core/bridge/api.dart @@ -0,0 +1,489 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../api/server/prekeys.dart'; +import '../frb_generated.dart'; + +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:freezed_annotation/freezed_annotation.dart' hide protected; +part 'api.freezed.dart'; + +// These functions are ignored because they are not marked as `pub`: `from_rust_state` +// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `ApiConfig`, `ServerResult` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_fields_are_eq`, `assert_fields_are_eq`, `clone`, `clone`, `clone`, `clone`, `eq`, `eq`, `fmt`, `fmt`, `fmt`, `fmt` +// These functions are ignored (category: IgnoreBecauseOwnerTyShouldIgnore): `into_bridge`, `into_bridge`, `into_bridge`, `into_bridge`, `into_bridge`, `into_bridge`, `into_bridge`, `into_bridge` + +enum ApiConnectionState { + stopped, + connecting, + connected, + authenticating, + authenticated, + reconnecting, + suspended, + permanentlyRejected, +} + +class ApiEvent { + final ApiEventKind kind; + final ApiConnectionState? state; + final String? message; + + const ApiEvent({ + required this.kind, + this.state, + this.message, + }); + + @override + int get hashCode => kind.hashCode ^ state.hashCode ^ message.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ApiEvent && + runtimeType == other.runtimeType && + kind == other.kind && + state == other.state && + message == other.message; +} + +enum ApiEventKind { + connectionStateChanged, + transportError, + authenticated, + planUpdated, + appOutdated, + newDeviceRegistered, + loginTokenMigrated, +} + +class PreparedOutgoingMessage { + final Uint8List message; + final Uint8List? pushData; + + const PreparedOutgoingMessage({ + required this.message, + this.pushData, + }); + + @override + int get hashCode => message.hashCode ^ pushData.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PreparedOutgoingMessage && + runtimeType == other.runtimeType && + message == other.message && + pushData == other.pushData; +} + +/// Flutter-facing facade for the Rust-owned API runtime. +class RustApi { + const RustApi(); + + static Future addAdditionalUser({ + required PlatformInt64 userId, + }) => RustLib.instance.api.crateBridgeApiRustApiAddAdditionalUser( + userId: userId, + ); + + static Future allocateSequence() => + RustLib.instance.api.crateBridgeApiRustApiAllocateSequence(); + + static String apiBaseUrl({required String protocol}) => + RustLib.instance.api.crateBridgeApiRustApiApiBaseUrl(protocol: protocol); + + static Future changeUsername({required String username}) => + RustLib.instance.api.crateBridgeApiRustApiChangeUsername( + username: username, + ); + + static Future checkForDeletedUsernames() => + RustLib.instance.api.crateBridgeApiRustApiCheckForDeletedUsernames(); + + static Future checkForPasswordlessNotification({ + required String notificationId, + required List downloadAuthToken, + required Int64List alreadyReceivedMessageIds, + }) => RustLib.instance.api + .crateBridgeApiRustApiCheckForPasswordlessNotification( + notificationId: notificationId, + downloadAuthToken: downloadAuthToken, + alreadyReceivedMessageIds: alreadyReceivedMessageIds, + ); + + static Future close() => + RustLib.instance.api.crateBridgeApiRustApiClose(); + + static Future confirmMemoriesUpload({ + required String mediaId, + }) => RustLib.instance.api.crateBridgeApiRustApiConfirmMemoriesUpload( + mediaId: mediaId, + ); + + static Future connect() => + RustLib.instance.api.crateBridgeApiRustApiConnect(); + + static Future connectionState() => + RustLib.instance.api.crateBridgeApiRustApiConnectionState(); + + static Future deleteAccount() => + RustLib.instance.api.crateBridgeApiRustApiDeleteAccount(); + + static Future deleteMemory({required String mediaId}) => + RustLib.instance.api.crateBridgeApiRustApiDeleteMemory(mediaId: mediaId); + + static Future disableMemoriesBackup() => + RustLib.instance.api.crateBridgeApiRustApiDisableMemoriesBackup(); + + static Future downloadDone({required List token}) => + RustLib.instance.api.crateBridgeApiRustApiDownloadDone(token: token); + + static Stream events() => + RustLib.instance.api.crateBridgeApiRustApiEvents(); + + static Future forceIpaCheck() => + RustLib.instance.api.crateBridgeApiRustApiForceIpaCheck(); + + static Future getMemoriesUrl({ + required String mediaId, + required bool thumbnail, + }) => RustLib.instance.api.crateBridgeApiRustApiGetMemoriesUrl( + mediaId: mediaId, + thumbnail: thumbnail, + ); + + static Future getMemoriesUsage() => + RustLib.instance.api.crateBridgeApiRustApiGetMemoriesUsage(); + + static Future getPlanBalance() => + RustLib.instance.api.crateBridgeApiRustApiGetPlanBalance(); + + static Future getProofOfWork() => + RustLib.instance.api.crateBridgeApiRustApiGetProofOfWork(); + + static Future getServerKeyForPasswordlessRecovery({ + required PlatformInt64 userId, + required List serverKeyProtection, + Uint8List? pinUnlockToken, + Uint8List? pinProtectionKey, + String? email, + }) => RustLib.instance.api + .crateBridgeApiRustApiGetServerKeyForPasswordlessRecovery( + userId: userId, + serverKeyProtection: serverKeyProtection, + pinUnlockToken: pinUnlockToken, + pinProtectionKey: pinProtectionKey, + email: email, + ); + + static Future getUserById({ + required PlatformInt64 userId, + }) => RustLib.instance.api.crateBridgeApiRustApiGetUserById(userId: userId); + + static Future getUserData({required String username}) => + RustLib.instance.api.crateBridgeApiRustApiGetUserData(username: username); + + static Future getUserIdFromUsername({ + required String username, + }) => RustLib.instance.api.crateBridgeApiRustApiGetUserIdFromUsername( + username: username, + ); + + static Future insertAndSendAdditionalData({ + required String groupId, + required String messageType, + required List additionalData, + }) => RustLib.instance.api.crateBridgeApiRustApiInsertAndSendAdditionalData( + groupId: groupId, + messageType: messageType, + additionalData: additionalData, + ); + + static Future insertAndSendAskAboutUser({ + required PlatformInt64 contactId, + required PlatformInt64 askAboutUserId, + }) => RustLib.instance.api.crateBridgeApiRustApiInsertAndSendAskAboutUser( + contactId: contactId, + askAboutUserId: askAboutUserId, + ); + + static Future insertAndSendContactShare({ + required String groupId, + required Int64List contactIds, + }) => RustLib.instance.api.crateBridgeApiRustApiInsertAndSendContactShare( + groupId: groupId, + contactIds: contactIds, + ); + + static Future insertAndSendText({ + required String groupId, + required String text, + String? quoteMessageId, + }) => RustLib.instance.api.crateBridgeApiRustApiInsertAndSendText( + groupId: groupId, + text: text, + quoteMessageId: quoteMessageId, + ); + + static Future ipaPurchase({ + required String productId, + required String source, + required String verificationData, + }) => RustLib.instance.api.crateBridgeApiRustApiIpaPurchase( + productId: productId, + source: source, + verificationData: verificationData, + ); + + static Future loadPlanBalance({required bool useCache}) => RustLib + .instance + .api + .crateBridgeApiRustApiLoadPlanBalance(useCache: useCache); + + static Future notifyMessagesOpened({ + required PlatformInt64 contactId, + required List messageIds, + }) => RustLib.instance.api.crateBridgeApiRustApiNotifyMessagesOpened( + contactId: contactId, + messageIds: messageIds, + ); + + static Future prepareQueuedMessage({ + required String receiptId, + }) => RustLib.instance.api.crateBridgeApiRustApiPrepareQueuedMessage( + receiptId: receiptId, + ); + + static Future register({ + required String username, + required PlatformInt64 proofOfWork, + required String langCode, + required bool isIos, + }) => RustLib.instance.api.crateBridgeApiRustApiRegister( + username: username, + proofOfWork: proofOfWork, + langCode: langCode, + isIos: isIos, + ); + + static Future registerPasswordlessNotification({ + required String notificationId, + required List downloadAuthToken, + required String langCode, + String? googleFcm, + }) => RustLib.instance.api + .crateBridgeApiRustApiRegisterPasswordlessNotification( + notificationId: notificationId, + downloadAuthToken: downloadAuthToken, + langCode: langCode, + googleFcm: googleFcm, + ); + + static Future registerPasswordlessRecovery({ + required List encryptedServerKey, + Uint8List? pinUnlockToken, + }) => RustLib.instance.api.crateBridgeApiRustApiRegisterPasswordlessRecovery( + encryptedServerKey: encryptedServerKey, + pinUnlockToken: pinUnlockToken, + ); + + /// Reloads identity and authentication settings from Rust-owned storage. + /// No configuration values are accepted from the caller. + static Future reloadConfiguration() => + RustLib.instance.api.crateBridgeApiRustApiReloadConfiguration(); + + static Future removeAdditionalUser({ + required PlatformInt64 userId, + }) => RustLib.instance.api.crateBridgeApiRustApiRemoveAdditionalUser( + userId: userId, + ); + + static Future reportUser({ + required PlatformInt64 userId, + required String reason, + }) => RustLib.instance.api.crateBridgeApiRustApiReportUser( + userId: userId, + reason: reason, + ); + + /// Sends a protobuf ClientToServer envelope. Rust assigns the sequence ID + /// and resolves this future with the matching ServerToClient response. + static Future requestBinary({required List bytes}) => + RustLib.instance.api.crateBridgeApiRustApiRequestBinary(bytes: bytes); + + static Future requestContactByUsername({required String username}) => + RustLib.instance.api.crateBridgeApiRustApiRequestContactByUsername( + username: username, + ); + + static Future requestMemoriesUpload({ + required PlatformInt64 size, + required PlatformInt64 originalDate, + required String mediaId, + }) => RustLib.instance.api.crateBridgeApiRustApiRequestMemoriesUpload( + size: size, + originalDate: originalDate, + mediaId: mediaId, + ); + + static Future retransmitAllMessages() => + RustLib.instance.api.crateBridgeApiRustApiRetransmitAllMessages(); + + static Future sendBinary({required List bytes}) => + RustLib.instance.api.crateBridgeApiRustApiSendBinary(bytes: bytes); + + static Future sendContactProfile({required PlatformInt64 contactId}) => + RustLib.instance.api.crateBridgeApiRustApiSendContactProfile( + contactId: contactId, + ); + + static Future sendEncryptedContent({ + required PlatformInt64 contactId, + required List content, + String? messageId, + required bool onlySendIfNoReceiptsAreOpen, + required bool onlyReturnEncryptedData, + required bool blocking, + }) => RustLib.instance.api.crateBridgeApiRustApiSendEncryptedContent( + contactId: contactId, + content: content, + messageId: messageId, + onlySendIfNoReceiptsAreOpen: onlySendIfNoReceiptsAreOpen, + onlyReturnEncryptedData: onlyReturnEncryptedData, + blocking: blocking, + ); + + static Future sendEncryptedContentToGroup({ + required String groupId, + required List content, + String? messageId, + required bool onlySendIfNoReceiptsAreOpen, + }) => RustLib.instance.api.crateBridgeApiRustApiSendEncryptedContentToGroup( + groupId: groupId, + content: content, + messageId: messageId, + onlySendIfNoReceiptsAreOpen: onlySendIfNoReceiptsAreOpen, + ); + + static Future sendQueuedMessage({required String receiptId}) => RustLib + .instance + .api + .crateBridgeApiRustApiSendQueuedMessage(receiptId: receiptId); + + static Future sendTextMessage({ + required PlatformInt64 userId, + required List body, + Uint8List? pushData, + }) => RustLib.instance.api.crateBridgeApiRustApiSendTextMessage( + userId: userId, + body: body, + pushData: pushData, + ); + + static Future sendTyping({ + required String groupId, + required bool isTyping, + }) => RustLib.instance.api.crateBridgeApiRustApiSendTyping( + groupId: groupId, + isTyping: isTyping, + ); + + static Future setBackground({required bool inBackground}) => RustLib + .instance + .api + .crateBridgeApiRustApiSetBackground(inBackground: inBackground); + + static Future setLoginToken({required List token}) => + RustLib.instance.api.crateBridgeApiRustApiSetLoginToken(token: token); + + static Future setNetworkAvailable({required bool available}) => RustLib + .instance + .api + .crateBridgeApiRustApiSetNetworkAvailable(available: available); + + static Future submitRecoveryShare({ + required String notificationId, + required List encryptedMessage, + }) => RustLib.instance.api.crateBridgeApiRustApiSubmitRecoveryShare( + notificationId: notificationId, + encryptedMessage: encryptedMessage, + ); + + static Future updateFcmToken({required String token}) => + RustLib.instance.api.crateBridgeApiRustApiUpdateFcmToken(token: token); + + static Future updateSignedPreKey({ + required PlatformInt64 id, + required List key, + required List signature, + }) => RustLib.instance.api.crateBridgeApiRustApiUpdateSignedPreKey( + id: id, + key: key, + signature: signature, + ); + + static Future uploadPqcPreKeys({ + required PlatformInt64 eccSignedPrekeyId, + required List eccSignedPrekey, + required List eccSignedPrekeySignature, + required PlatformInt64 kyberSignedPrekeyId, + required List kyberSignedPrekey, + required List kyberSignedPrekeySignature, + required List prekeys, + }) => RustLib.instance.api.crateBridgeApiRustApiUploadPqcPreKeys( + eccSignedPrekeyId: eccSignedPrekeyId, + eccSignedPrekey: eccSignedPrekey, + eccSignedPrekeySignature: eccSignedPrekeySignature, + kyberSignedPrekeyId: kyberSignedPrekeyId, + kyberSignedPrekey: kyberSignedPrekey, + kyberSignedPrekeySignature: kyberSignedPrekeySignature, + prekeys: prekeys, + ); + + @override + int get hashCode => 0; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RustApi && runtimeType == other.runtimeType; +} + +@freezed +sealed class ServerResultEmpty with _$ServerResultEmpty { + const ServerResultEmpty._(); + + const factory ServerResultEmpty.ok() = ServerResultEmpty_Ok; + const factory ServerResultEmpty.errorCode( + int field0, + ) = ServerResultEmpty_ErrorCode; +} + +@freezed +sealed class ServerResultI64 with _$ServerResultI64 { + const ServerResultI64._(); + + const factory ServerResultI64.ok( + PlatformInt64 field0, + ) = ServerResultI64_Ok; + const factory ServerResultI64.errorCode( + int field0, + ) = ServerResultI64_ErrorCode; +} + +@freezed +sealed class ServerResultVecU8 with _$ServerResultVecU8 { + const ServerResultVecU8._(); + + const factory ServerResultVecU8.ok( + Uint8List field0, + ) = ServerResultVecU8_Ok; + const factory ServerResultVecU8.errorCode( + int field0, + ) = ServerResultVecU8_ErrorCode; +} diff --git a/lib/core/bridge/api.freezed.dart b/lib/core/bridge/api.freezed.dart new file mode 100644 index 00000000..609b5bd3 --- /dev/null +++ b/lib/core/bridge/api.freezed.dart @@ -0,0 +1,888 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'api.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ServerResultEmpty { + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerResultEmpty); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'ServerResultEmpty()'; +} + + +} + +/// @nodoc +class $ServerResultEmptyCopyWith<$Res> { +$ServerResultEmptyCopyWith(ServerResultEmpty _, $Res Function(ServerResultEmpty) __); +} + + +/// Adds pattern-matching-related methods to [ServerResultEmpty]. +extension ServerResultEmptyPatterns on ServerResultEmpty { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( ServerResultEmpty_Ok value)? ok,TResult Function( ServerResultEmpty_ErrorCode value)? errorCode,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case ServerResultEmpty_Ok() when ok != null: +return ok(_that);case ServerResultEmpty_ErrorCode() when errorCode != null: +return errorCode(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( ServerResultEmpty_Ok value) ok,required TResult Function( ServerResultEmpty_ErrorCode value) errorCode,}){ +final _that = this; +switch (_that) { +case ServerResultEmpty_Ok(): +return ok(_that);case ServerResultEmpty_ErrorCode(): +return errorCode(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( ServerResultEmpty_Ok value)? ok,TResult? Function( ServerResultEmpty_ErrorCode value)? errorCode,}){ +final _that = this; +switch (_that) { +case ServerResultEmpty_Ok() when ok != null: +return ok(_that);case ServerResultEmpty_ErrorCode() when errorCode != null: +return errorCode(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function()? ok,TResult Function( int field0)? errorCode,required TResult orElse(),}) {final _that = this; +switch (_that) { +case ServerResultEmpty_Ok() when ok != null: +return ok();case ServerResultEmpty_ErrorCode() when errorCode != null: +return errorCode(_that.field0);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function() ok,required TResult Function( int field0) errorCode,}) {final _that = this; +switch (_that) { +case ServerResultEmpty_Ok(): +return ok();case ServerResultEmpty_ErrorCode(): +return errorCode(_that.field0);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function()? ok,TResult? Function( int field0)? errorCode,}) {final _that = this; +switch (_that) { +case ServerResultEmpty_Ok() when ok != null: +return ok();case ServerResultEmpty_ErrorCode() when errorCode != null: +return errorCode(_that.field0);case _: + return null; + +} +} + +} + +/// @nodoc + + +class ServerResultEmpty_Ok extends ServerResultEmpty { + const ServerResultEmpty_Ok(): super._(); + + + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerResultEmpty_Ok); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'ServerResultEmpty.ok()'; +} + + +} + + + + +/// @nodoc + + +class ServerResultEmpty_ErrorCode extends ServerResultEmpty { + const ServerResultEmpty_ErrorCode(this.field0): super._(); + + + final int field0; + +/// Create a copy of ServerResultEmpty +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ServerResultEmpty_ErrorCodeCopyWith get copyWith => _$ServerResultEmpty_ErrorCodeCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerResultEmpty_ErrorCode&&(identical(other.field0, field0) || other.field0 == field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,field0); + +@override +String toString() { + return 'ServerResultEmpty.errorCode(field0: $field0)'; +} + + +} + +/// @nodoc +abstract mixin class $ServerResultEmpty_ErrorCodeCopyWith<$Res> implements $ServerResultEmptyCopyWith<$Res> { + factory $ServerResultEmpty_ErrorCodeCopyWith(ServerResultEmpty_ErrorCode value, $Res Function(ServerResultEmpty_ErrorCode) _then) = _$ServerResultEmpty_ErrorCodeCopyWithImpl; +@useResult +$Res call({ + int field0 +}); + + + + +} +/// @nodoc +class _$ServerResultEmpty_ErrorCodeCopyWithImpl<$Res> + implements $ServerResultEmpty_ErrorCodeCopyWith<$Res> { + _$ServerResultEmpty_ErrorCodeCopyWithImpl(this._self, this._then); + + final ServerResultEmpty_ErrorCode _self; + final $Res Function(ServerResultEmpty_ErrorCode) _then; + +/// Create a copy of ServerResultEmpty +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) { + return _then(ServerResultEmpty_ErrorCode( +null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +/// @nodoc +mixin _$ServerResultI64 { + + int get field0; +/// Create a copy of ServerResultI64 +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ServerResultI64CopyWith get copyWith => _$ServerResultI64CopyWithImpl(this as ServerResultI64, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerResultI64&&(identical(other.field0, field0) || other.field0 == field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,field0); + +@override +String toString() { + return 'ServerResultI64(field0: $field0)'; +} + + +} + +/// @nodoc +abstract mixin class $ServerResultI64CopyWith<$Res> { + factory $ServerResultI64CopyWith(ServerResultI64 value, $Res Function(ServerResultI64) _then) = _$ServerResultI64CopyWithImpl; +@useResult +$Res call({ + int field0 +}); + + + + +} +/// @nodoc +class _$ServerResultI64CopyWithImpl<$Res> + implements $ServerResultI64CopyWith<$Res> { + _$ServerResultI64CopyWithImpl(this._self, this._then); + + final ServerResultI64 _self; + final $Res Function(ServerResultI64) _then; + +/// Create a copy of ServerResultI64 +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? field0 = null,}) { + return _then(_self.copyWith( +field0: null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ServerResultI64]. +extension ServerResultI64Patterns on ServerResultI64 { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( ServerResultI64_Ok value)? ok,TResult Function( ServerResultI64_ErrorCode value)? errorCode,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case ServerResultI64_Ok() when ok != null: +return ok(_that);case ServerResultI64_ErrorCode() when errorCode != null: +return errorCode(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( ServerResultI64_Ok value) ok,required TResult Function( ServerResultI64_ErrorCode value) errorCode,}){ +final _that = this; +switch (_that) { +case ServerResultI64_Ok(): +return ok(_that);case ServerResultI64_ErrorCode(): +return errorCode(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( ServerResultI64_Ok value)? ok,TResult? Function( ServerResultI64_ErrorCode value)? errorCode,}){ +final _that = this; +switch (_that) { +case ServerResultI64_Ok() when ok != null: +return ok(_that);case ServerResultI64_ErrorCode() when errorCode != null: +return errorCode(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function( PlatformInt64 field0)? ok,TResult Function( int field0)? errorCode,required TResult orElse(),}) {final _that = this; +switch (_that) { +case ServerResultI64_Ok() when ok != null: +return ok(_that.field0);case ServerResultI64_ErrorCode() when errorCode != null: +return errorCode(_that.field0);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function( PlatformInt64 field0) ok,required TResult Function( int field0) errorCode,}) {final _that = this; +switch (_that) { +case ServerResultI64_Ok(): +return ok(_that.field0);case ServerResultI64_ErrorCode(): +return errorCode(_that.field0);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function( PlatformInt64 field0)? ok,TResult? Function( int field0)? errorCode,}) {final _that = this; +switch (_that) { +case ServerResultI64_Ok() when ok != null: +return ok(_that.field0);case ServerResultI64_ErrorCode() when errorCode != null: +return errorCode(_that.field0);case _: + return null; + +} +} + +} + +/// @nodoc + + +class ServerResultI64_Ok extends ServerResultI64 { + const ServerResultI64_Ok(this.field0): super._(); + + +@override final PlatformInt64 field0; + +/// Create a copy of ServerResultI64 +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ServerResultI64_OkCopyWith get copyWith => _$ServerResultI64_OkCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerResultI64_Ok&&(identical(other.field0, field0) || other.field0 == field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,field0); + +@override +String toString() { + return 'ServerResultI64.ok(field0: $field0)'; +} + + +} + +/// @nodoc +abstract mixin class $ServerResultI64_OkCopyWith<$Res> implements $ServerResultI64CopyWith<$Res> { + factory $ServerResultI64_OkCopyWith(ServerResultI64_Ok value, $Res Function(ServerResultI64_Ok) _then) = _$ServerResultI64_OkCopyWithImpl; +@override @useResult +$Res call({ + PlatformInt64 field0 +}); + + + + +} +/// @nodoc +class _$ServerResultI64_OkCopyWithImpl<$Res> + implements $ServerResultI64_OkCopyWith<$Res> { + _$ServerResultI64_OkCopyWithImpl(this._self, this._then); + + final ServerResultI64_Ok _self; + final $Res Function(ServerResultI64_Ok) _then; + +/// Create a copy of ServerResultI64 +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) { + return _then(ServerResultI64_Ok( +null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable +as PlatformInt64, + )); +} + + +} + +/// @nodoc + + +class ServerResultI64_ErrorCode extends ServerResultI64 { + const ServerResultI64_ErrorCode(this.field0): super._(); + + +@override final int field0; + +/// Create a copy of ServerResultI64 +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ServerResultI64_ErrorCodeCopyWith get copyWith => _$ServerResultI64_ErrorCodeCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerResultI64_ErrorCode&&(identical(other.field0, field0) || other.field0 == field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,field0); + +@override +String toString() { + return 'ServerResultI64.errorCode(field0: $field0)'; +} + + +} + +/// @nodoc +abstract mixin class $ServerResultI64_ErrorCodeCopyWith<$Res> implements $ServerResultI64CopyWith<$Res> { + factory $ServerResultI64_ErrorCodeCopyWith(ServerResultI64_ErrorCode value, $Res Function(ServerResultI64_ErrorCode) _then) = _$ServerResultI64_ErrorCodeCopyWithImpl; +@override @useResult +$Res call({ + int field0 +}); + + + + +} +/// @nodoc +class _$ServerResultI64_ErrorCodeCopyWithImpl<$Res> + implements $ServerResultI64_ErrorCodeCopyWith<$Res> { + _$ServerResultI64_ErrorCodeCopyWithImpl(this._self, this._then); + + final ServerResultI64_ErrorCode _self; + final $Res Function(ServerResultI64_ErrorCode) _then; + +/// Create a copy of ServerResultI64 +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) { + return _then(ServerResultI64_ErrorCode( +null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +/// @nodoc +mixin _$ServerResultVecU8 { + + Object get field0; + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerResultVecU8&&const DeepCollectionEquality().equals(other.field0, field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(field0)); + +@override +String toString() { + return 'ServerResultVecU8(field0: $field0)'; +} + + +} + +/// @nodoc +class $ServerResultVecU8CopyWith<$Res> { +$ServerResultVecU8CopyWith(ServerResultVecU8 _, $Res Function(ServerResultVecU8) __); +} + + +/// Adds pattern-matching-related methods to [ServerResultVecU8]. +extension ServerResultVecU8Patterns on ServerResultVecU8 { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( ServerResultVecU8_Ok value)? ok,TResult Function( ServerResultVecU8_ErrorCode value)? errorCode,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case ServerResultVecU8_Ok() when ok != null: +return ok(_that);case ServerResultVecU8_ErrorCode() when errorCode != null: +return errorCode(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( ServerResultVecU8_Ok value) ok,required TResult Function( ServerResultVecU8_ErrorCode value) errorCode,}){ +final _that = this; +switch (_that) { +case ServerResultVecU8_Ok(): +return ok(_that);case ServerResultVecU8_ErrorCode(): +return errorCode(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( ServerResultVecU8_Ok value)? ok,TResult? Function( ServerResultVecU8_ErrorCode value)? errorCode,}){ +final _that = this; +switch (_that) { +case ServerResultVecU8_Ok() when ok != null: +return ok(_that);case ServerResultVecU8_ErrorCode() when errorCode != null: +return errorCode(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function( Uint8List field0)? ok,TResult Function( int field0)? errorCode,required TResult orElse(),}) {final _that = this; +switch (_that) { +case ServerResultVecU8_Ok() when ok != null: +return ok(_that.field0);case ServerResultVecU8_ErrorCode() when errorCode != null: +return errorCode(_that.field0);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function( Uint8List field0) ok,required TResult Function( int field0) errorCode,}) {final _that = this; +switch (_that) { +case ServerResultVecU8_Ok(): +return ok(_that.field0);case ServerResultVecU8_ErrorCode(): +return errorCode(_that.field0);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function( Uint8List field0)? ok,TResult? Function( int field0)? errorCode,}) {final _that = this; +switch (_that) { +case ServerResultVecU8_Ok() when ok != null: +return ok(_that.field0);case ServerResultVecU8_ErrorCode() when errorCode != null: +return errorCode(_that.field0);case _: + return null; + +} +} + +} + +/// @nodoc + + +class ServerResultVecU8_Ok extends ServerResultVecU8 { + const ServerResultVecU8_Ok(this.field0): super._(); + + +@override final Uint8List field0; + +/// Create a copy of ServerResultVecU8 +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ServerResultVecU8_OkCopyWith get copyWith => _$ServerResultVecU8_OkCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerResultVecU8_Ok&&const DeepCollectionEquality().equals(other.field0, field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(field0)); + +@override +String toString() { + return 'ServerResultVecU8.ok(field0: $field0)'; +} + + +} + +/// @nodoc +abstract mixin class $ServerResultVecU8_OkCopyWith<$Res> implements $ServerResultVecU8CopyWith<$Res> { + factory $ServerResultVecU8_OkCopyWith(ServerResultVecU8_Ok value, $Res Function(ServerResultVecU8_Ok) _then) = _$ServerResultVecU8_OkCopyWithImpl; +@useResult +$Res call({ + Uint8List field0 +}); + + + + +} +/// @nodoc +class _$ServerResultVecU8_OkCopyWithImpl<$Res> + implements $ServerResultVecU8_OkCopyWith<$Res> { + _$ServerResultVecU8_OkCopyWithImpl(this._self, this._then); + + final ServerResultVecU8_Ok _self; + final $Res Function(ServerResultVecU8_Ok) _then; + +/// Create a copy of ServerResultVecU8 +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) { + return _then(ServerResultVecU8_Ok( +null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable +as Uint8List, + )); +} + + +} + +/// @nodoc + + +class ServerResultVecU8_ErrorCode extends ServerResultVecU8 { + const ServerResultVecU8_ErrorCode(this.field0): super._(); + + +@override final int field0; + +/// Create a copy of ServerResultVecU8 +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ServerResultVecU8_ErrorCodeCopyWith get copyWith => _$ServerResultVecU8_ErrorCodeCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerResultVecU8_ErrorCode&&(identical(other.field0, field0) || other.field0 == field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,field0); + +@override +String toString() { + return 'ServerResultVecU8.errorCode(field0: $field0)'; +} + + +} + +/// @nodoc +abstract mixin class $ServerResultVecU8_ErrorCodeCopyWith<$Res> implements $ServerResultVecU8CopyWith<$Res> { + factory $ServerResultVecU8_ErrorCodeCopyWith(ServerResultVecU8_ErrorCode value, $Res Function(ServerResultVecU8_ErrorCode) _then) = _$ServerResultVecU8_ErrorCodeCopyWithImpl; +@useResult +$Res call({ + int field0 +}); + + + + +} +/// @nodoc +class _$ServerResultVecU8_ErrorCodeCopyWithImpl<$Res> + implements $ServerResultVecU8_ErrorCodeCopyWith<$Res> { + _$ServerResultVecU8_ErrorCodeCopyWithImpl(this._self, this._then); + + final ServerResultVecU8_ErrorCode _self; + final $Res Function(ServerResultVecU8_ErrorCode) _then; + +/// Create a copy of ServerResultVecU8 +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) { + return _then(ServerResultVecU8_ErrorCode( +null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/lib/core/bridge/callbacks.dart b/lib/core/bridge/callbacks.dart index 4dd5845c..2e07f276 100644 --- a/lib/core/bridge/callbacks.dart +++ b/lib/core/bridge/callbacks.dart @@ -4,16 +4,125 @@ // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import import '../frb_generated.dart'; + import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; // These functions are ignored because they are not marked as `pub`: `get_callbacks` -// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `FlutterCallbacks`, `Logging` -// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone` +// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `Api`, `FlutterCallbacks`, `LegacySignal`, `Logging` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt` Future initFlutterCallbacks({ required int callbackId, required FutureOr> Function() loggingGetStreamSink, + required FutureOr Function( + PlatformInt64, + Uint8List, + int, + ) + legacySignalDecrypt, + required FutureOr Function( + PlatformInt64, + Uint8List, + ) + legacySignalEncrypt, + required FutureOr> Function() + legacySignalGeneratePrekeys, + required FutureOr Function(PlatformInt64) apiResyncSignalSession, + required FutureOr Function(PlatformInt64) apiPushKeyRequested, + required FutureOr Function(PlatformInt64, String, String) + apiGroupMembershipError, + required FutureOr Function(String, String, PlatformInt64, String) + apiMediaAction, + required FutureOr Function(PlatformInt64, Uint8List) + apiVerificationProof, + required FutureOr Function(PlatformInt64, String?, Uint8List, int) + apiCreatePushData, + required FutureOr Function(PlatformInt64) apiCreatePushAvatars, + required FutureOr Function() apiRecoveryChanged, + required FutureOr Function(String, PlatformInt64) apiMediaReceived, + required FutureOr Function(String, bool) apiGroupStateRefresh, }) => RustLib.instance.api.crateBridgeCallbacksInitFlutterCallbacks( callbackId: callbackId, loggingGetStreamSink: loggingGetStreamSink, + legacySignalDecrypt: legacySignalDecrypt, + legacySignalEncrypt: legacySignalEncrypt, + legacySignalGeneratePrekeys: legacySignalGeneratePrekeys, + apiResyncSignalSession: apiResyncSignalSession, + apiPushKeyRequested: apiPushKeyRequested, + apiGroupMembershipError: apiGroupMembershipError, + apiMediaAction: apiMediaAction, + apiVerificationProof: apiVerificationProof, + apiCreatePushData: apiCreatePushData, + apiCreatePushAvatars: apiCreatePushAvatars, + apiRecoveryChanged: apiRecoveryChanged, + apiMediaReceived: apiMediaReceived, + apiGroupStateRefresh: apiGroupStateRefresh, ); + +class LegacySignalDecryptResult { + /// Serialized `EncryptedContent` when legacy Signal decryption succeeded. + final Uint8List? plaintext; + + /// Serialized protobuf enum value for `DecryptionErrorMessage.Type`. + final int? decryptionErrorType; + + const LegacySignalDecryptResult({ + this.plaintext, + this.decryptionErrorType, + }); + + @override + int get hashCode => plaintext.hashCode ^ decryptionErrorType.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is LegacySignalDecryptResult && + runtimeType == other.runtimeType && + plaintext == other.plaintext && + decryptionErrorType == other.decryptionErrorType; +} + +class LegacySignalEncryptResult { + final Uint8List ciphertext; + + /// `Message.Type.CIPHERTEXT` or `Message.Type.PREKEY_BUNDLE`. + final int messageType; + + const LegacySignalEncryptResult({ + required this.ciphertext, + required this.messageType, + }); + + @override + int get hashCode => ciphertext.hashCode ^ messageType.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is LegacySignalEncryptResult && + runtimeType == other.runtimeType && + ciphertext == other.ciphertext && + messageType == other.messageType; +} + +class LegacySignalPreKey { + final PlatformInt64 id; + final Uint8List publicKey; + + const LegacySignalPreKey({ + required this.id, + required this.publicKey, + }); + + @override + int get hashCode => id.hashCode ^ publicKey.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is LegacySignalPreKey && + runtimeType == other.runtimeType && + id == other.id && + publicKey == other.publicKey; +} diff --git a/lib/core/bridge/groups.dart b/lib/core/bridge/groups.dart new file mode 100644 index 00000000..11b73bae --- /dev/null +++ b/lib/core/bridge/groups.dart @@ -0,0 +1,79 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; + +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +Future createNewGroup({ + required String groupName, + required Int64List memberIds, +}) => RustLib.instance.api.crateBridgeGroupsCreateNewGroup( + groupName: groupName, + memberIds: memberIds, +); + +Future fetchGroupState({required String groupId}) => + RustLib.instance.api.crateBridgeGroupsFetchGroupState(groupId: groupId); + +Future addHiddenContact({required PlatformInt64 contactId}) => RustLib + .instance + .api + .crateBridgeGroupsAddHiddenContact(contactId: contactId); + +Future fetchGroupStatesForUnjoinedGroups() => + RustLib.instance.api.crateBridgeGroupsFetchGroupStatesForUnjoinedGroups(); + +Future fetchMissingGroupPublicKeys() => + RustLib.instance.api.crateBridgeGroupsFetchMissingGroupPublicKeys(); + +Future manageAdminState({ + required String groupId, + required List groupPublicKey, + required PlatformInt64 contactId, + required bool remove, +}) => RustLib.instance.api.crateBridgeGroupsManageAdminState( + groupId: groupId, + groupPublicKey: groupPublicKey, + contactId: contactId, + remove: remove, +); + +Future updateGroupName({ + required String groupId, + required String groupName, +}) => RustLib.instance.api.crateBridgeGroupsUpdateGroupName( + groupId: groupId, + groupName: groupName, +); + +Future updateChatDeletionTime({ + required String groupId, + required PlatformInt64 deleteMessagesAfterMilliseconds, +}) => RustLib.instance.api.crateBridgeGroupsUpdateChatDeletionTime( + groupId: groupId, + deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds, +); + +Future addNewGroupMembers({ + required String groupId, + required Int64List memberIds, +}) => RustLib.instance.api.crateBridgeGroupsAddNewGroupMembers( + groupId: groupId, + memberIds: memberIds, +); + +Future removeMemberFromGroup({ + required String groupId, + required List groupPublicKey, + required PlatformInt64 contactId, +}) => RustLib.instance.api.crateBridgeGroupsRemoveMemberFromGroup( + groupId: groupId, + groupPublicKey: groupPublicKey, + contactId: contactId, +); + +Future leaveGroup({required String groupId}) => + RustLib.instance.api.crateBridgeGroupsLeaveGroup(groupId: groupId); diff --git a/lib/core/frb_generated.dart b/lib/core/frb_generated.dart index 9dd15541..520c5e0c 100644 --- a/lib/core/frb_generated.dart +++ b/lib/core/frb_generated.dart @@ -3,8 +3,11 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/server/prekeys.dart'; import 'bridge.dart'; +import 'bridge/api.dart'; import 'bridge/callbacks.dart'; +import 'bridge/groups.dart'; import 'bridge/wrapper.dart'; import 'bridge/wrapper/app_database.dart'; import 'bridge/wrapper/backup.dart'; @@ -79,7 +82,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.12.0'; @override - int get rustContentHash => -1155052375; + int get rustContentHash => -392823520; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -91,6 +94,26 @@ class RustLib extends BaseEntrypoint { } abstract class RustLibApi extends BaseApi { + Future crateBridgeGroupsAddHiddenContact({ + required PlatformInt64 contactId, + }); + + Future crateBridgeGroupsAddNewGroupMembers({ + required String groupId, + required Int64List memberIds, + }); + + Future crateBridgeGroupsCreateNewGroup({ + required String groupName, + required Int64List memberIds, + }); + + Future crateBridgeGroupsFetchGroupState({required String groupId}); + + Future crateBridgeGroupsFetchGroupStatesForUnjoinedGroups(); + + Future crateBridgeGroupsFetchMissingGroupPublicKeys(); + Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion({ required int callbackId, @@ -137,10 +160,300 @@ abstract class RustLibApi extends BaseApi { Future crateBridgeCallbacksInitFlutterCallbacks({ required int callbackId, required FutureOr> Function() loggingGetStreamSink, + required FutureOr Function( + PlatformInt64, + Uint8List, + int, + ) + legacySignalDecrypt, + required FutureOr Function( + PlatformInt64, + Uint8List, + ) + legacySignalEncrypt, + required FutureOr> Function() + legacySignalGeneratePrekeys, + required FutureOr Function(PlatformInt64) apiResyncSignalSession, + required FutureOr Function(PlatformInt64) apiPushKeyRequested, + required FutureOr Function(PlatformInt64, String, String) + apiGroupMembershipError, + required FutureOr Function(String, String, PlatformInt64, String) + apiMediaAction, + required FutureOr Function(PlatformInt64, Uint8List) + apiVerificationProof, + required FutureOr Function( + PlatformInt64, + String?, + Uint8List, + int, + ) + apiCreatePushData, + required FutureOr Function(PlatformInt64) apiCreatePushAvatars, + required FutureOr Function() apiRecoveryChanged, + required FutureOr Function(String, PlatformInt64) apiMediaReceived, + required FutureOr Function(String, bool) apiGroupStateRefresh, }); Future crateBridgeInitializeTwonlyFlutter({required InitConfig config}); + Future crateBridgeInitializeTwonlyStandalone({ + required InitConfig config, + }); + + Future crateBridgeGroupsLeaveGroup({required String groupId}); + + Future crateBridgeGroupsManageAdminState({ + required String groupId, + required List groupPublicKey, + required PlatformInt64 contactId, + required bool remove, + }); + + Future crateBridgeGroupsRemoveMemberFromGroup({ + required String groupId, + required List groupPublicKey, + required PlatformInt64 contactId, + }); + + Future crateBridgeApiRustApiAddAdditionalUser({ + required PlatformInt64 userId, + }); + + Future crateBridgeApiRustApiAllocateSequence(); + + String crateBridgeApiRustApiApiBaseUrl({required String protocol}); + + Future crateBridgeApiRustApiChangeUsername({ + required String username, + }); + + Future crateBridgeApiRustApiCheckForDeletedUsernames(); + + Future + crateBridgeApiRustApiCheckForPasswordlessNotification({ + required String notificationId, + required List downloadAuthToken, + required Int64List alreadyReceivedMessageIds, + }); + + Future crateBridgeApiRustApiClose(); + + Future crateBridgeApiRustApiConfirmMemoriesUpload({ + required String mediaId, + }); + + Future crateBridgeApiRustApiConnect(); + + Future crateBridgeApiRustApiConnectionState(); + + Future crateBridgeApiRustApiDeleteAccount(); + + Future crateBridgeApiRustApiDeleteMemory({ + required String mediaId, + }); + + Future crateBridgeApiRustApiDisableMemoriesBackup(); + + Future crateBridgeApiRustApiDownloadDone({ + required List token, + }); + + Stream crateBridgeApiRustApiEvents(); + + Future crateBridgeApiRustApiForceIpaCheck(); + + Future crateBridgeApiRustApiGetMemoriesUrl({ + required String mediaId, + required bool thumbnail, + }); + + Future crateBridgeApiRustApiGetMemoriesUsage(); + + Future crateBridgeApiRustApiGetPlanBalance(); + + Future crateBridgeApiRustApiGetProofOfWork(); + + Future + crateBridgeApiRustApiGetServerKeyForPasswordlessRecovery({ + required PlatformInt64 userId, + required List serverKeyProtection, + Uint8List? pinUnlockToken, + Uint8List? pinProtectionKey, + String? email, + }); + + Future crateBridgeApiRustApiGetUserById({ + required PlatformInt64 userId, + }); + + Future crateBridgeApiRustApiGetUserData({ + required String username, + }); + + Future crateBridgeApiRustApiGetUserIdFromUsername({ + required String username, + }); + + Future crateBridgeApiRustApiInsertAndSendAdditionalData({ + required String groupId, + required String messageType, + required List additionalData, + }); + + Future crateBridgeApiRustApiInsertAndSendAskAboutUser({ + required PlatformInt64 contactId, + required PlatformInt64 askAboutUserId, + }); + + Future crateBridgeApiRustApiInsertAndSendContactShare({ + required String groupId, + required Int64List contactIds, + }); + + Future crateBridgeApiRustApiInsertAndSendText({ + required String groupId, + required String text, + String? quoteMessageId, + }); + + Future crateBridgeApiRustApiIpaPurchase({ + required String productId, + required String source, + required String verificationData, + }); + + Future crateBridgeApiRustApiLoadPlanBalance({ + required bool useCache, + }); + + Future crateBridgeApiRustApiNotifyMessagesOpened({ + required PlatformInt64 contactId, + required List messageIds, + }); + + Future crateBridgeApiRustApiPrepareQueuedMessage({ + required String receiptId, + }); + + Future crateBridgeApiRustApiRegister({ + required String username, + required PlatformInt64 proofOfWork, + required String langCode, + required bool isIos, + }); + + Future + crateBridgeApiRustApiRegisterPasswordlessNotification({ + required String notificationId, + required List downloadAuthToken, + required String langCode, + String? googleFcm, + }); + + Future crateBridgeApiRustApiRegisterPasswordlessRecovery({ + required List encryptedServerKey, + Uint8List? pinUnlockToken, + }); + + Future crateBridgeApiRustApiReloadConfiguration(); + + Future crateBridgeApiRustApiRemoveAdditionalUser({ + required PlatformInt64 userId, + }); + + Future crateBridgeApiRustApiReportUser({ + required PlatformInt64 userId, + required String reason, + }); + + Future crateBridgeApiRustApiRequestBinary({ + required List bytes, + }); + + Future crateBridgeApiRustApiRequestContactByUsername({ + required String username, + }); + + Future crateBridgeApiRustApiRequestMemoriesUpload({ + required PlatformInt64 size, + required PlatformInt64 originalDate, + required String mediaId, + }); + + Future crateBridgeApiRustApiRetransmitAllMessages(); + + Future crateBridgeApiRustApiSendBinary({required List bytes}); + + Future crateBridgeApiRustApiSendContactProfile({ + required PlatformInt64 contactId, + }); + + Future crateBridgeApiRustApiSendEncryptedContent({ + required PlatformInt64 contactId, + required List content, + String? messageId, + required bool onlySendIfNoReceiptsAreOpen, + required bool onlyReturnEncryptedData, + required bool blocking, + }); + + Future crateBridgeApiRustApiSendEncryptedContentToGroup({ + required String groupId, + required List content, + String? messageId, + required bool onlySendIfNoReceiptsAreOpen, + }); + + Future crateBridgeApiRustApiSendQueuedMessage({ + required String receiptId, + }); + + Future crateBridgeApiRustApiSendTextMessage({ + required PlatformInt64 userId, + required List body, + Uint8List? pushData, + }); + + Future crateBridgeApiRustApiSendTyping({ + required String groupId, + required bool isTyping, + }); + + Future crateBridgeApiRustApiSetBackground({required bool inBackground}); + + Future crateBridgeApiRustApiSetLoginToken({ + required List token, + }); + + Future crateBridgeApiRustApiSetNetworkAvailable({ + required bool available, + }); + + Future crateBridgeApiRustApiSubmitRecoveryShare({ + required String notificationId, + required List encryptedMessage, + }); + + Future crateBridgeApiRustApiUpdateFcmToken({ + required String token, + }); + + Future crateBridgeApiRustApiUpdateSignedPreKey({ + required PlatformInt64 id, + required List key, + required List signature, + }); + + Future crateBridgeApiRustApiUploadPqcPreKeys({ + required PlatformInt64 eccSignedPrekeyId, + required List eccSignedPrekey, + required List eccSignedPrekeySignature, + required PlatformInt64 kyberSignedPrekeyId, + required List kyberSignedPrekey, + required List kyberSignedPrekeySignature, + required List prekeys, + }); + Future crateBridgeWrapperAppDatabaseRustAppDatabaseExecute({ required String statement, @@ -282,6 +595,16 @@ abstract class RustLibApi extends BaseApi { required List shares, required int threshold, }); + + Future crateBridgeGroupsUpdateChatDeletionTime({ + required String groupId, + required PlatformInt64 deleteMessagesAfterMilliseconds, + }); + + Future crateBridgeGroupsUpdateGroupName({ + required String groupId, + required String groupName, + }); } class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @@ -292,6 +615,201 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { required super.portManager, }); + @override + Future crateBridgeGroupsAddHiddenContact({ + required PlatformInt64 contactId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(contactId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 1, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsAddHiddenContactConstMeta, + argValues: [contactId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsAddHiddenContactConstMeta => + const TaskConstMeta( + debugName: "add_hidden_contact", + argNames: ["contactId"], + ); + + @override + Future crateBridgeGroupsAddNewGroupMembers({ + required String groupId, + required Int64List memberIds, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_list_prim_i_64_strict(memberIds, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 2, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsAddNewGroupMembersConstMeta, + argValues: [groupId, memberIds], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsAddNewGroupMembersConstMeta => + const TaskConstMeta( + debugName: "add_new_group_members", + argNames: ["groupId", "memberIds"], + ); + + @override + Future crateBridgeGroupsCreateNewGroup({ + required String groupName, + required Int64List memberIds, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupName, serializer); + sse_encode_list_prim_i_64_strict(memberIds, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 3, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsCreateNewGroupConstMeta, + argValues: [groupName, memberIds], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsCreateNewGroupConstMeta => + const TaskConstMeta( + debugName: "create_new_group", + argNames: ["groupName", "memberIds"], + ); + + @override + Future crateBridgeGroupsFetchGroupState({required String groupId}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 4, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsFetchGroupStateConstMeta, + argValues: [groupId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsFetchGroupStateConstMeta => + const TaskConstMeta( + debugName: "fetch_group_state", + argNames: ["groupId"], + ); + + @override + Future crateBridgeGroupsFetchGroupStatesForUnjoinedGroups() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 5, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsFetchGroupStatesForUnjoinedGroupsConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeGroupsFetchGroupStatesForUnjoinedGroupsConstMeta => + const TaskConstMeta( + debugName: "fetch_group_states_for_unjoined_groups", + argNames: [], + ); + + @override + Future crateBridgeGroupsFetchMissingGroupPublicKeys() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 6, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsFetchMissingGroupPublicKeysConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsFetchMissingGroupPublicKeysConstMeta => + const TaskConstMeta( + debugName: "fetch_missing_group_public_keys", + argNames: [], + ); + @override Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion({ @@ -305,7 +823,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 1, + funcId: 7, port: port_, ); }, @@ -345,7 +863,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 2, + funcId: 8, port: port_, ); }, @@ -390,7 +908,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 3, + funcId: 9, port: port_, ); }, @@ -444,7 +962,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 4, + funcId: 10, port: port_, ); }, @@ -490,7 +1008,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 5, + funcId: 11, port: port_, ); }, @@ -533,7 +1051,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 6, + funcId: 12, port: port_, ); }, @@ -560,6 +1078,38 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { Future crateBridgeCallbacksInitFlutterCallbacks({ required int callbackId, required FutureOr> Function() loggingGetStreamSink, + required FutureOr Function( + PlatformInt64, + Uint8List, + int, + ) + legacySignalDecrypt, + required FutureOr Function( + PlatformInt64, + Uint8List, + ) + legacySignalEncrypt, + required FutureOr> Function() + legacySignalGeneratePrekeys, + required FutureOr Function(PlatformInt64) apiResyncSignalSession, + required FutureOr Function(PlatformInt64) apiPushKeyRequested, + required FutureOr Function(PlatformInt64, String, String) + apiGroupMembershipError, + required FutureOr Function(String, String, PlatformInt64, String) + apiMediaAction, + required FutureOr Function(PlatformInt64, Uint8List) + apiVerificationProof, + required FutureOr Function( + PlatformInt64, + String?, + Uint8List, + int, + ) + apiCreatePushData, + required FutureOr Function(PlatformInt64) apiCreatePushAvatars, + required FutureOr Function() apiRecoveryChanged, + required FutureOr Function(String, PlatformInt64) apiMediaReceived, + required FutureOr Function(String, bool) apiGroupStateRefresh, }) { return handler.executeNormal( NormalTask( @@ -570,10 +1120,62 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { loggingGetStreamSink, serializer, ); + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + legacySignalDecrypt, + serializer, + ); + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + legacySignalEncrypt, + serializer, + ); + sse_encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + legacySignalGeneratePrekeys, + serializer, + ); + sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException( + apiResyncSignalSession, + serializer, + ); + sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException( + apiPushKeyRequested, + serializer, + ); + sse_encode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException( + apiGroupMembershipError, + serializer, + ); + sse_encode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + apiMediaAction, + serializer, + ); + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + apiVerificationProof, + serializer, + ); + sse_encode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + apiCreatePushData, + serializer, + ); + sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException( + apiCreatePushAvatars, + serializer, + ); + sse_encode_DartFn_Inputs__Output_unit_AnyhowException( + apiRecoveryChanged, + serializer, + ); + sse_encode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException( + apiMediaReceived, + serializer, + ); + sse_encode_DartFn_Inputs_String_bool_Output_unit_AnyhowException( + apiGroupStateRefresh, + serializer, + ); pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 7, + funcId: 13, port: port_, ); }, @@ -582,7 +1184,23 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { decodeErrorData: null, ), constMeta: kCrateBridgeCallbacksInitFlutterCallbacksConstMeta, - argValues: [callbackId, loggingGetStreamSink], + argValues: [ + callbackId, + loggingGetStreamSink, + legacySignalDecrypt, + legacySignalEncrypt, + legacySignalGeneratePrekeys, + apiResyncSignalSession, + apiPushKeyRequested, + apiGroupMembershipError, + apiMediaAction, + apiVerificationProof, + apiCreatePushData, + apiCreatePushAvatars, + apiRecoveryChanged, + apiMediaReceived, + apiGroupStateRefresh, + ], apiImpl: this, ), ); @@ -591,7 +1209,23 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateBridgeCallbacksInitFlutterCallbacksConstMeta => const TaskConstMeta( debugName: "init_flutter_callbacks", - argNames: ["callbackId", "loggingGetStreamSink"], + argNames: [ + "callbackId", + "loggingGetStreamSink", + "legacySignalDecrypt", + "legacySignalEncrypt", + "legacySignalGeneratePrekeys", + "apiResyncSignalSession", + "apiPushKeyRequested", + "apiGroupMembershipError", + "apiMediaAction", + "apiVerificationProof", + "apiCreatePushData", + "apiCreatePushAvatars", + "apiRecoveryChanged", + "apiMediaReceived", + "apiGroupStateRefresh", + ], ); @override @@ -606,7 +1240,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 8, + funcId: 14, port: port_, ); }, @@ -627,6 +1261,2119 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["config"], ); + @override + Future crateBridgeInitializeTwonlyStandalone({ + required InitConfig config, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_box_autoadd_init_config(config, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 15, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeInitializeTwonlyStandaloneConstMeta, + argValues: [config], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeInitializeTwonlyStandaloneConstMeta => + const TaskConstMeta( + debugName: "initialize_twonly_standalone", + argNames: ["config"], + ); + + @override + Future crateBridgeGroupsLeaveGroup({required String groupId}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 16, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsLeaveGroupConstMeta, + argValues: [groupId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsLeaveGroupConstMeta => + const TaskConstMeta( + debugName: "leave_group", + argNames: ["groupId"], + ); + + @override + Future crateBridgeGroupsManageAdminState({ + required String groupId, + required List groupPublicKey, + required PlatformInt64 contactId, + required bool remove, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_list_prim_u_8_loose(groupPublicKey, serializer); + sse_encode_i_64(contactId, serializer); + sse_encode_bool(remove, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 17, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsManageAdminStateConstMeta, + argValues: [groupId, groupPublicKey, contactId, remove], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsManageAdminStateConstMeta => + const TaskConstMeta( + debugName: "manage_admin_state", + argNames: ["groupId", "groupPublicKey", "contactId", "remove"], + ); + + @override + Future crateBridgeGroupsRemoveMemberFromGroup({ + required String groupId, + required List groupPublicKey, + required PlatformInt64 contactId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_list_prim_u_8_loose(groupPublicKey, serializer); + sse_encode_i_64(contactId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 18, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsRemoveMemberFromGroupConstMeta, + argValues: [groupId, groupPublicKey, contactId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsRemoveMemberFromGroupConstMeta => + const TaskConstMeta( + debugName: "remove_member_from_group", + argNames: ["groupId", "groupPublicKey", "contactId"], + ); + + @override + Future crateBridgeApiRustApiAddAdditionalUser({ + required PlatformInt64 userId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(userId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 19, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiAddAdditionalUserConstMeta, + argValues: [userId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiAddAdditionalUserConstMeta => + const TaskConstMeta( + debugName: "rust_api_add_additional_user", + argNames: ["userId"], + ); + + @override + Future crateBridgeApiRustApiAllocateSequence() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 20, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_u_64, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiAllocateSequenceConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiAllocateSequenceConstMeta => + const TaskConstMeta( + debugName: "rust_api_allocate_sequence", + argNames: [], + ); + + @override + String crateBridgeApiRustApiApiBaseUrl({required String protocol}) { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(protocol, serializer); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 21)!; + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: null, + ), + constMeta: kCrateBridgeApiRustApiApiBaseUrlConstMeta, + argValues: [protocol], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiApiBaseUrlConstMeta => + const TaskConstMeta( + debugName: "rust_api_api_base_url", + argNames: ["protocol"], + ); + + @override + Future crateBridgeApiRustApiChangeUsername({ + required String username, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(username, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 22, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiChangeUsernameConstMeta, + argValues: [username], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiChangeUsernameConstMeta => + const TaskConstMeta( + debugName: "rust_api_change_username", + argNames: ["username"], + ); + + @override + Future crateBridgeApiRustApiCheckForDeletedUsernames() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 23, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiCheckForDeletedUsernamesConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiCheckForDeletedUsernamesConstMeta => + const TaskConstMeta( + debugName: "rust_api_check_for_deleted_usernames", + argNames: [], + ); + + @override + Future + crateBridgeApiRustApiCheckForPasswordlessNotification({ + required String notificationId, + required List downloadAuthToken, + required Int64List alreadyReceivedMessageIds, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(notificationId, serializer); + sse_encode_list_prim_u_8_loose(downloadAuthToken, serializer); + sse_encode_list_prim_i_64_strict( + alreadyReceivedMessageIds, + serializer, + ); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 24, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_vec_u_8, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeApiRustApiCheckForPasswordlessNotificationConstMeta, + argValues: [ + notificationId, + downloadAuthToken, + alreadyReceivedMessageIds, + ], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeApiRustApiCheckForPasswordlessNotificationConstMeta => + const TaskConstMeta( + debugName: "rust_api_check_for_passwordless_notification", + argNames: [ + "notificationId", + "downloadAuthToken", + "alreadyReceivedMessageIds", + ], + ); + + @override + Future crateBridgeApiRustApiClose() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 25, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiCloseConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiCloseConstMeta => const TaskConstMeta( + debugName: "rust_api_close", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiConfirmMemoriesUpload({ + required String mediaId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(mediaId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 26, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiConfirmMemoriesUploadConstMeta, + argValues: [mediaId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiConfirmMemoriesUploadConstMeta => + const TaskConstMeta( + debugName: "rust_api_confirm_memories_upload", + argNames: ["mediaId"], + ); + + @override + Future crateBridgeApiRustApiConnect() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 27, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiConnectConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiConnectConstMeta => + const TaskConstMeta( + debugName: "rust_api_connect", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiConnectionState() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 28, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_api_connection_state, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiConnectionStateConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiConnectionStateConstMeta => + const TaskConstMeta( + debugName: "rust_api_connection_state", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiDeleteAccount() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 29, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiDeleteAccountConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiDeleteAccountConstMeta => + const TaskConstMeta( + debugName: "rust_api_delete_account", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiDeleteMemory({ + required String mediaId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(mediaId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 30, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiDeleteMemoryConstMeta, + argValues: [mediaId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiDeleteMemoryConstMeta => + const TaskConstMeta( + debugName: "rust_api_delete_memory", + argNames: ["mediaId"], + ); + + @override + Future crateBridgeApiRustApiDisableMemoriesBackup() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 31, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiDisableMemoriesBackupConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiDisableMemoriesBackupConstMeta => + const TaskConstMeta( + debugName: "rust_api_disable_memories_backup", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiDownloadDone({ + required List token, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(token, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 32, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiDownloadDoneConstMeta, + argValues: [token], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiDownloadDoneConstMeta => + const TaskConstMeta( + debugName: "rust_api_download_done", + argNames: ["token"], + ); + + @override + Stream crateBridgeApiRustApiEvents() { + final sink = RustStreamSink(); + unawaited( + handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_StreamSink_api_event_Sse(sink, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 33, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiEventsConstMeta, + argValues: [sink], + apiImpl: this, + ), + ), + ); + return sink.stream; + } + + TaskConstMeta get kCrateBridgeApiRustApiEventsConstMeta => + const TaskConstMeta( + debugName: "rust_api_events", + argNames: ["sink"], + ); + + @override + Future crateBridgeApiRustApiForceIpaCheck() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 34, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiForceIpaCheckConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiForceIpaCheckConstMeta => + const TaskConstMeta( + debugName: "rust_api_force_ipa_check", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiGetMemoriesUrl({ + required String mediaId, + required bool thumbnail, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(mediaId, serializer); + sse_encode_bool(thumbnail, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 35, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_vec_u_8, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiGetMemoriesUrlConstMeta, + argValues: [mediaId, thumbnail], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiGetMemoriesUrlConstMeta => + const TaskConstMeta( + debugName: "rust_api_get_memories_url", + argNames: ["mediaId", "thumbnail"], + ); + + @override + Future crateBridgeApiRustApiGetMemoriesUsage() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 36, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_vec_u_8, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiGetMemoriesUsageConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiGetMemoriesUsageConstMeta => + const TaskConstMeta( + debugName: "rust_api_get_memories_usage", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiGetPlanBalance() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 37, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiGetPlanBalanceConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiGetPlanBalanceConstMeta => + const TaskConstMeta( + debugName: "rust_api_get_plan_balance", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiGetProofOfWork() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 38, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_vec_u_8, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiGetProofOfWorkConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiGetProofOfWorkConstMeta => + const TaskConstMeta( + debugName: "rust_api_get_proof_of_work", + argNames: [], + ); + + @override + Future + crateBridgeApiRustApiGetServerKeyForPasswordlessRecovery({ + required PlatformInt64 userId, + required List serverKeyProtection, + Uint8List? pinUnlockToken, + Uint8List? pinProtectionKey, + String? email, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(userId, serializer); + sse_encode_list_prim_u_8_loose(serverKeyProtection, serializer); + sse_encode_opt_list_prim_u_8_strict(pinUnlockToken, serializer); + sse_encode_opt_list_prim_u_8_strict(pinProtectionKey, serializer); + sse_encode_opt_String(email, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 39, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_vec_u_8, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeApiRustApiGetServerKeyForPasswordlessRecoveryConstMeta, + argValues: [ + userId, + serverKeyProtection, + pinUnlockToken, + pinProtectionKey, + email, + ], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeApiRustApiGetServerKeyForPasswordlessRecoveryConstMeta => + const TaskConstMeta( + debugName: "rust_api_get_server_key_for_passwordless_recovery", + argNames: [ + "userId", + "serverKeyProtection", + "pinUnlockToken", + "pinProtectionKey", + "email", + ], + ); + + @override + Future crateBridgeApiRustApiGetUserById({ + required PlatformInt64 userId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(userId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 40, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_vec_u_8, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiGetUserByIdConstMeta, + argValues: [userId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiGetUserByIdConstMeta => + const TaskConstMeta( + debugName: "rust_api_get_user_by_id", + argNames: ["userId"], + ); + + @override + Future crateBridgeApiRustApiGetUserData({ + required String username, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(username, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 41, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_vec_u_8, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiGetUserDataConstMeta, + argValues: [username], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiGetUserDataConstMeta => + const TaskConstMeta( + debugName: "rust_api_get_user_data", + argNames: ["username"], + ); + + @override + Future crateBridgeApiRustApiGetUserIdFromUsername({ + required String username, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(username, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 42, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_i_64, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiGetUserIdFromUsernameConstMeta, + argValues: [username], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiGetUserIdFromUsernameConstMeta => + const TaskConstMeta( + debugName: "rust_api_get_user_id_from_username", + argNames: ["username"], + ); + + @override + Future crateBridgeApiRustApiInsertAndSendAdditionalData({ + required String groupId, + required String messageType, + required List additionalData, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_String(messageType, serializer); + sse_encode_list_prim_u_8_loose(additionalData, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 43, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiInsertAndSendAdditionalDataConstMeta, + argValues: [groupId, messageType, additionalData], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeApiRustApiInsertAndSendAdditionalDataConstMeta => + const TaskConstMeta( + debugName: "rust_api_insert_and_send_additional_data", + argNames: ["groupId", "messageType", "additionalData"], + ); + + @override + Future crateBridgeApiRustApiInsertAndSendAskAboutUser({ + required PlatformInt64 contactId, + required PlatformInt64 askAboutUserId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(contactId, serializer); + sse_encode_i_64(askAboutUserId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 44, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiInsertAndSendAskAboutUserConstMeta, + argValues: [contactId, askAboutUserId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiInsertAndSendAskAboutUserConstMeta => + const TaskConstMeta( + debugName: "rust_api_insert_and_send_ask_about_user", + argNames: ["contactId", "askAboutUserId"], + ); + + @override + Future crateBridgeApiRustApiInsertAndSendContactShare({ + required String groupId, + required Int64List contactIds, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_list_prim_i_64_strict(contactIds, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 45, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiInsertAndSendContactShareConstMeta, + argValues: [groupId, contactIds], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiInsertAndSendContactShareConstMeta => + const TaskConstMeta( + debugName: "rust_api_insert_and_send_contact_share", + argNames: ["groupId", "contactIds"], + ); + + @override + Future crateBridgeApiRustApiInsertAndSendText({ + required String groupId, + required String text, + String? quoteMessageId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_String(text, serializer); + sse_encode_opt_String(quoteMessageId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 46, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiInsertAndSendTextConstMeta, + argValues: [groupId, text, quoteMessageId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiInsertAndSendTextConstMeta => + const TaskConstMeta( + debugName: "rust_api_insert_and_send_text", + argNames: ["groupId", "text", "quoteMessageId"], + ); + + @override + Future crateBridgeApiRustApiIpaPurchase({ + required String productId, + required String source, + required String verificationData, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(productId, serializer); + sse_encode_String(source, serializer); + sse_encode_String(verificationData, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 47, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiIpaPurchaseConstMeta, + argValues: [productId, source, verificationData], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiIpaPurchaseConstMeta => + const TaskConstMeta( + debugName: "rust_api_ipa_purchase", + argNames: ["productId", "source", "verificationData"], + ); + + @override + Future crateBridgeApiRustApiLoadPlanBalance({ + required bool useCache, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_bool(useCache, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 48, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiLoadPlanBalanceConstMeta, + argValues: [useCache], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiLoadPlanBalanceConstMeta => + const TaskConstMeta( + debugName: "rust_api_load_plan_balance", + argNames: ["useCache"], + ); + + @override + Future crateBridgeApiRustApiNotifyMessagesOpened({ + required PlatformInt64 contactId, + required List messageIds, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(contactId, serializer); + sse_encode_list_String(messageIds, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 49, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiNotifyMessagesOpenedConstMeta, + argValues: [contactId, messageIds], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiNotifyMessagesOpenedConstMeta => + const TaskConstMeta( + debugName: "rust_api_notify_messages_opened", + argNames: ["contactId", "messageIds"], + ); + + @override + Future crateBridgeApiRustApiPrepareQueuedMessage({ + required String receiptId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(receiptId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 50, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_opt_box_autoadd_prepared_outgoing_message, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiPrepareQueuedMessageConstMeta, + argValues: [receiptId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiPrepareQueuedMessageConstMeta => + const TaskConstMeta( + debugName: "rust_api_prepare_queued_message", + argNames: ["receiptId"], + ); + + @override + Future crateBridgeApiRustApiRegister({ + required String username, + required PlatformInt64 proofOfWork, + required String langCode, + required bool isIos, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(username, serializer); + sse_encode_i_64(proofOfWork, serializer); + sse_encode_String(langCode, serializer); + sse_encode_bool(isIos, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 51, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_i_64, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiRegisterConstMeta, + argValues: [username, proofOfWork, langCode, isIos], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiRegisterConstMeta => + const TaskConstMeta( + debugName: "rust_api_register", + argNames: ["username", "proofOfWork", "langCode", "isIos"], + ); + + @override + Future + crateBridgeApiRustApiRegisterPasswordlessNotification({ + required String notificationId, + required List downloadAuthToken, + required String langCode, + String? googleFcm, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(notificationId, serializer); + sse_encode_list_prim_u_8_loose(downloadAuthToken, serializer); + sse_encode_String(langCode, serializer); + sse_encode_opt_String(googleFcm, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 52, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeApiRustApiRegisterPasswordlessNotificationConstMeta, + argValues: [notificationId, downloadAuthToken, langCode, googleFcm], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeApiRustApiRegisterPasswordlessNotificationConstMeta => + const TaskConstMeta( + debugName: "rust_api_register_passwordless_notification", + argNames: [ + "notificationId", + "downloadAuthToken", + "langCode", + "googleFcm", + ], + ); + + @override + Future crateBridgeApiRustApiRegisterPasswordlessRecovery({ + required List encryptedServerKey, + Uint8List? pinUnlockToken, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(encryptedServerKey, serializer); + sse_encode_opt_list_prim_u_8_strict(pinUnlockToken, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 53, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiRegisterPasswordlessRecoveryConstMeta, + argValues: [encryptedServerKey, pinUnlockToken], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeApiRustApiRegisterPasswordlessRecoveryConstMeta => + const TaskConstMeta( + debugName: "rust_api_register_passwordless_recovery", + argNames: ["encryptedServerKey", "pinUnlockToken"], + ); + + @override + Future crateBridgeApiRustApiReloadConfiguration() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 54, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiReloadConfigurationConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiReloadConfigurationConstMeta => + const TaskConstMeta( + debugName: "rust_api_reload_configuration", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiRemoveAdditionalUser({ + required PlatformInt64 userId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(userId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 55, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiRemoveAdditionalUserConstMeta, + argValues: [userId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiRemoveAdditionalUserConstMeta => + const TaskConstMeta( + debugName: "rust_api_remove_additional_user", + argNames: ["userId"], + ); + + @override + Future crateBridgeApiRustApiReportUser({ + required PlatformInt64 userId, + required String reason, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(userId, serializer); + sse_encode_String(reason, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 56, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiReportUserConstMeta, + argValues: [userId, reason], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiReportUserConstMeta => + const TaskConstMeta( + debugName: "rust_api_report_user", + argNames: ["userId", "reason"], + ); + + @override + Future crateBridgeApiRustApiRequestBinary({ + required List bytes, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(bytes, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 57, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiRequestBinaryConstMeta, + argValues: [bytes], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiRequestBinaryConstMeta => + const TaskConstMeta( + debugName: "rust_api_request_binary", + argNames: ["bytes"], + ); + + @override + Future crateBridgeApiRustApiRequestContactByUsername({ + required String username, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(username, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 58, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiRequestContactByUsernameConstMeta, + argValues: [username], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiRequestContactByUsernameConstMeta => + const TaskConstMeta( + debugName: "rust_api_request_contact_by_username", + argNames: ["username"], + ); + + @override + Future crateBridgeApiRustApiRequestMemoriesUpload({ + required PlatformInt64 size, + required PlatformInt64 originalDate, + required String mediaId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(size, serializer); + sse_encode_i_64(originalDate, serializer); + sse_encode_String(mediaId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 59, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_vec_u_8, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiRequestMemoriesUploadConstMeta, + argValues: [size, originalDate, mediaId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiRequestMemoriesUploadConstMeta => + const TaskConstMeta( + debugName: "rust_api_request_memories_upload", + argNames: ["size", "originalDate", "mediaId"], + ); + + @override + Future crateBridgeApiRustApiRetransmitAllMessages() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 60, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiRetransmitAllMessagesConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiRetransmitAllMessagesConstMeta => + const TaskConstMeta( + debugName: "rust_api_retransmit_all_messages", + argNames: [], + ); + + @override + Future crateBridgeApiRustApiSendBinary({required List bytes}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(bytes, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 61, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSendBinaryConstMeta, + argValues: [bytes], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSendBinaryConstMeta => + const TaskConstMeta( + debugName: "rust_api_send_binary", + argNames: ["bytes"], + ); + + @override + Future crateBridgeApiRustApiSendContactProfile({ + required PlatformInt64 contactId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(contactId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 62, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSendContactProfileConstMeta, + argValues: [contactId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSendContactProfileConstMeta => + const TaskConstMeta( + debugName: "rust_api_send_contact_profile", + argNames: ["contactId"], + ); + + @override + Future crateBridgeApiRustApiSendEncryptedContent({ + required PlatformInt64 contactId, + required List content, + String? messageId, + required bool onlySendIfNoReceiptsAreOpen, + required bool onlyReturnEncryptedData, + required bool blocking, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(contactId, serializer); + sse_encode_list_prim_u_8_loose(content, serializer); + sse_encode_opt_String(messageId, serializer); + sse_encode_bool(onlySendIfNoReceiptsAreOpen, serializer); + sse_encode_bool(onlyReturnEncryptedData, serializer); + sse_encode_bool(blocking, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 63, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_opt_box_autoadd_prepared_outgoing_message, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSendEncryptedContentConstMeta, + argValues: [ + contactId, + content, + messageId, + onlySendIfNoReceiptsAreOpen, + onlyReturnEncryptedData, + blocking, + ], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSendEncryptedContentConstMeta => + const TaskConstMeta( + debugName: "rust_api_send_encrypted_content", + argNames: [ + "contactId", + "content", + "messageId", + "onlySendIfNoReceiptsAreOpen", + "onlyReturnEncryptedData", + "blocking", + ], + ); + + @override + Future crateBridgeApiRustApiSendEncryptedContentToGroup({ + required String groupId, + required List content, + String? messageId, + required bool onlySendIfNoReceiptsAreOpen, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_list_prim_u_8_loose(content, serializer); + sse_encode_opt_String(messageId, serializer); + sse_encode_bool(onlySendIfNoReceiptsAreOpen, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 64, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSendEncryptedContentToGroupConstMeta, + argValues: [groupId, content, messageId, onlySendIfNoReceiptsAreOpen], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeApiRustApiSendEncryptedContentToGroupConstMeta => + const TaskConstMeta( + debugName: "rust_api_send_encrypted_content_to_group", + argNames: [ + "groupId", + "content", + "messageId", + "onlySendIfNoReceiptsAreOpen", + ], + ); + + @override + Future crateBridgeApiRustApiSendQueuedMessage({ + required String receiptId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(receiptId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 65, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSendQueuedMessageConstMeta, + argValues: [receiptId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSendQueuedMessageConstMeta => + const TaskConstMeta( + debugName: "rust_api_send_queued_message", + argNames: ["receiptId"], + ); + + @override + Future crateBridgeApiRustApiSendTextMessage({ + required PlatformInt64 userId, + required List body, + Uint8List? pushData, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(userId, serializer); + sse_encode_list_prim_u_8_loose(body, serializer); + sse_encode_opt_list_prim_u_8_strict(pushData, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 66, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSendTextMessageConstMeta, + argValues: [userId, body, pushData], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSendTextMessageConstMeta => + const TaskConstMeta( + debugName: "rust_api_send_text_message", + argNames: ["userId", "body", "pushData"], + ); + + @override + Future crateBridgeApiRustApiSendTyping({ + required String groupId, + required bool isTyping, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_bool(isTyping, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 67, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSendTypingConstMeta, + argValues: [groupId, isTyping], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSendTypingConstMeta => + const TaskConstMeta( + debugName: "rust_api_send_typing", + argNames: ["groupId", "isTyping"], + ); + + @override + Future crateBridgeApiRustApiSetBackground({ + required bool inBackground, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_bool(inBackground, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 68, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSetBackgroundConstMeta, + argValues: [inBackground], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSetBackgroundConstMeta => + const TaskConstMeta( + debugName: "rust_api_set_background", + argNames: ["inBackground"], + ); + + @override + Future crateBridgeApiRustApiSetLoginToken({ + required List token, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(token, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 69, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSetLoginTokenConstMeta, + argValues: [token], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSetLoginTokenConstMeta => + const TaskConstMeta( + debugName: "rust_api_set_login_token", + argNames: ["token"], + ); + + @override + Future crateBridgeApiRustApiSetNetworkAvailable({ + required bool available, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_bool(available, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 70, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSetNetworkAvailableConstMeta, + argValues: [available], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSetNetworkAvailableConstMeta => + const TaskConstMeta( + debugName: "rust_api_set_network_available", + argNames: ["available"], + ); + + @override + Future crateBridgeApiRustApiSubmitRecoveryShare({ + required String notificationId, + required List encryptedMessage, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(notificationId, serializer); + sse_encode_list_prim_u_8_loose(encryptedMessage, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 71, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSubmitRecoveryShareConstMeta, + argValues: [notificationId, encryptedMessage], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSubmitRecoveryShareConstMeta => + const TaskConstMeta( + debugName: "rust_api_submit_recovery_share", + argNames: ["notificationId", "encryptedMessage"], + ); + + @override + Future crateBridgeApiRustApiUpdateFcmToken({ + required String token, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(token, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 72, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_server_result_empty, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiUpdateFcmTokenConstMeta, + argValues: [token], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiUpdateFcmTokenConstMeta => + const TaskConstMeta( + debugName: "rust_api_update_fcm_token", + argNames: ["token"], + ); + + @override + Future crateBridgeApiRustApiUpdateSignedPreKey({ + required PlatformInt64 id, + required List key, + required List signature, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(id, serializer); + sse_encode_list_prim_u_8_loose(key, serializer); + sse_encode_list_prim_u_8_loose(signature, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 73, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiUpdateSignedPreKeyConstMeta, + argValues: [id, key, signature], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiUpdateSignedPreKeyConstMeta => + const TaskConstMeta( + debugName: "rust_api_update_signed_pre_key", + argNames: ["id", "key", "signature"], + ); + + @override + Future crateBridgeApiRustApiUploadPqcPreKeys({ + required PlatformInt64 eccSignedPrekeyId, + required List eccSignedPrekey, + required List eccSignedPrekeySignature, + required PlatformInt64 kyberSignedPrekeyId, + required List kyberSignedPrekey, + required List kyberSignedPrekeySignature, + required List prekeys, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(eccSignedPrekeyId, serializer); + sse_encode_list_prim_u_8_loose(eccSignedPrekey, serializer); + sse_encode_list_prim_u_8_loose(eccSignedPrekeySignature, serializer); + sse_encode_i_64(kyberSignedPrekeyId, serializer); + sse_encode_list_prim_u_8_loose(kyberSignedPrekey, serializer); + sse_encode_list_prim_u_8_loose( + kyberSignedPrekeySignature, + serializer, + ); + sse_encode_list_pqc_pre_key_input(prekeys, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 74, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiUploadPqcPreKeysConstMeta, + argValues: [ + eccSignedPrekeyId, + eccSignedPrekey, + eccSignedPrekeySignature, + kyberSignedPrekeyId, + kyberSignedPrekey, + kyberSignedPrekeySignature, + prekeys, + ], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiUploadPqcPreKeysConstMeta => + const TaskConstMeta( + debugName: "rust_api_upload_pqc_pre_keys", + argNames: [ + "eccSignedPrekeyId", + "eccSignedPrekey", + "eccSignedPrekeySignature", + "kyberSignedPrekeyId", + "kyberSignedPrekey", + "kyberSignedPrekeySignature", + "prekeys", + ], + ); + @override Future crateBridgeWrapperAppDatabaseRustAppDatabaseExecute({ @@ -642,7 +3389,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 9, + funcId: 75, port: port_, ); }, @@ -675,7 +3422,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 10, + funcId: 76, port: port_, ); }, @@ -708,7 +3455,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 11, + funcId: 77, port: port_, ); }, @@ -745,7 +3492,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 12, + funcId: 78, port: port_, ); }, @@ -777,7 +3524,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 13, + funcId: 79, port: port_, ); }, @@ -810,7 +3557,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 14, + funcId: 80, port: port_, ); }, @@ -845,7 +3592,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 15, + funcId: 81, port: port_, ); }, @@ -877,7 +3624,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 16, + funcId: 82, port: port_, ); }, @@ -915,7 +3662,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 17, + funcId: 83, port: port_, ); }, @@ -948,7 +3695,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 18, + funcId: 84, port: port_, ); }, @@ -986,7 +3733,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 19, + funcId: 85, port: port_, ); }, @@ -1023,7 +3770,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 20, + funcId: 86, port: port_, ); }, @@ -1060,7 +3807,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 21, + funcId: 87, port: port_, ); }, @@ -1098,7 +3845,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 22, + funcId: 88, port: port_, ); }, @@ -1136,7 +3883,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 23, + funcId: 89, port: port_, ); }, @@ -1168,7 +3915,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 24, + funcId: 90, port: port_, ); }, @@ -1201,7 +3948,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 25, + funcId: 91, port: port_, ); }, @@ -1233,7 +3980,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 26, + funcId: 92, port: port_, ); }, @@ -1268,7 +4015,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 27, + funcId: 93, port: port_, ); }, @@ -1310,7 +4057,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 28, + funcId: 94, port: port_, ); }, @@ -1354,7 +4101,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 29, + funcId: 95, port: port_, ); }, @@ -1387,7 +4134,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 30, + funcId: 96, port: port_, ); }, @@ -1419,7 +4166,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 31, + funcId: 97, port: port_, ); }, @@ -1454,7 +4201,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 32, + funcId: 98, port: port_, ); }, @@ -1486,7 +4233,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 33, + funcId: 99, port: port_, ); }, @@ -1521,7 +4268,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 34, + funcId: 100, port: port_, ); }, @@ -1558,7 +4305,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 35, + funcId: 101, port: port_, ); }, @@ -1597,7 +4344,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 36, + funcId: 102, port: port_, ); }, @@ -1634,7 +4381,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 37, + funcId: 103, port: port_, ); }, @@ -1664,7 +4411,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 38, + funcId: 104, port: port_, ); }, @@ -1696,7 +4443,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 39, + funcId: 105, port: port_, ); }, @@ -1735,7 +4482,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 40, + funcId: 106, port: port_, ); }, @@ -1774,7 +4521,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 41, + funcId: 107, port: port_, ); }, @@ -1809,7 +4556,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 42, + funcId: 108, port: port_, ); }, @@ -1830,6 +4577,186 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["shares", "threshold"], ); + @override + Future crateBridgeGroupsUpdateChatDeletionTime({ + required String groupId, + required PlatformInt64 deleteMessagesAfterMilliseconds, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_i_64(deleteMessagesAfterMilliseconds, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 109, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsUpdateChatDeletionTimeConstMeta, + argValues: [groupId, deleteMessagesAfterMilliseconds], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsUpdateChatDeletionTimeConstMeta => + const TaskConstMeta( + debugName: "update_chat_deletion_time", + argNames: ["groupId", "deleteMessagesAfterMilliseconds"], + ); + + @override + Future crateBridgeGroupsUpdateGroupName({ + required String groupId, + required String groupName, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(groupId, serializer); + sse_encode_String(groupName, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 110, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeGroupsUpdateGroupNameConstMeta, + argValues: [groupId, groupName], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeGroupsUpdateGroupNameConstMeta => + const TaskConstMeta( + debugName: "update_group_name", + argNames: ["groupId", "groupName"], + ); + + Future Function(int, dynamic, dynamic, dynamic, dynamic) + encode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + FutureOr Function(String, String, PlatformInt64, String) raw, + ) { + return (callId, rawArg0, rawArg1, rawArg2, rawArg3) async { + final arg0 = dco_decode_String(rawArg0); + final arg1 = dco_decode_String(rawArg1); + final arg2 = dco_decode_i_64(rawArg2); + final arg3 = dco_decode_String(rawArg3); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0, arg1, arg2, arg3)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic, dynamic) + encode_DartFn_Inputs_String_bool_Output_unit_AnyhowException( + FutureOr Function(String, bool) raw, + ) { + return (callId, rawArg0, rawArg1) async { + final arg0 = dco_decode_String(rawArg0); + final arg1 = dco_decode_bool(rawArg1); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0, arg1)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic, dynamic) + encode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException( + FutureOr Function(String, PlatformInt64) raw, + ) { + return (callId, rawArg0, rawArg1) async { + final arg0 = dco_decode_String(rawArg0); + final arg1 = dco_decode_i_64(rawArg1); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0, arg1)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + Future Function( int, ) @@ -1867,12 +4794,336 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { }; } + Future Function( + int, + ) + encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + FutureOr> Function() raw, + ) { + return ( + callId, + ) async { + Box>? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw()); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_list_legacy_signal_pre_key(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function( + int, + ) + encode_DartFn_Inputs__Output_unit_AnyhowException( + FutureOr Function() raw, + ) { + return ( + callId, + ) async { + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw()); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic) + encode_DartFn_Inputs_i_64_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64) raw, + ) { + return (callId, rawArg0) async { + final arg0 = dco_decode_i_64(rawArg0); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic, dynamic, dynamic) + encode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64, String, String) raw, + ) { + return (callId, rawArg0, rawArg1, rawArg2) async { + final arg0 = dco_decode_i_64(rawArg0); + final arg1 = dco_decode_String(rawArg1); + final arg2 = dco_decode_String(rawArg2); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0, arg1, arg2)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic, dynamic) + encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List) raw, + ) { + return (callId, rawArg0, rawArg1) async { + final arg0 = dco_decode_i_64(rawArg0); + final arg1 = dco_decode_list_prim_u_8_strict(rawArg1); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0, arg1)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_opt_box_autoadd_legacy_signal_encrypt_result( + rawOutput.value, + serializer, + ); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic, dynamic) + encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List) raw, + ) { + return (callId, rawArg0, rawArg1) async { + final arg0 = dco_decode_i_64(rawArg0); + final arg1 = dco_decode_list_prim_u_8_strict(rawArg1); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0, arg1)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic, dynamic, dynamic) + encode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List, int) + raw, + ) { + return (callId, rawArg0, rawArg1, rawArg2) async { + final arg0 = dco_decode_i_64(rawArg0); + final arg1 = dco_decode_list_prim_u_8_strict(rawArg1); + final arg2 = dco_decode_i_32(rawArg2); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0, arg1, arg2)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_legacy_signal_decrypt_result(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic, dynamic, dynamic, dynamic) + encode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + FutureOr Function(PlatformInt64, String?, Uint8List, int) raw, + ) { + return (callId, rawArg0, rawArg1, rawArg2, rawArg3) async { + final arg0 = dco_decode_i_64(rawArg0); + final arg1 = dco_decode_opt_String(rawArg1); + final arg2 = dco_decode_list_prim_u_8_strict(rawArg2); + final arg3 = dco_decode_i_32(rawArg3); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0, arg1, arg2, arg3)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_opt_list_prim_u_8_strict(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + @protected AnyhowException dco_decode_AnyhowException(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return AnyhowException(raw as String); } + @protected + FutureOr Function(String, String, PlatformInt64, String) + dco_decode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(String, bool) + dco_decode_DartFn_Inputs_String_bool_Output_unit_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(String, PlatformInt64) + dco_decode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + @protected FutureOr> Function() dco_decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( @@ -1882,6 +5133,74 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { throw UnimplementedError(''); } + @protected + FutureOr> Function() + dco_decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function() + dco_decode_DartFn_Inputs__Output_unit_AnyhowException(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(PlatformInt64) + dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(PlatformInt64, String, String) + dco_decode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(PlatformInt64, Uint8List) + dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(PlatformInt64, Uint8List) + dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(PlatformInt64, Uint8List, int) + dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(PlatformInt64, String?, Uint8List, int) + dco_decode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + @protected Object dco_decode_DartOpaque(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1906,12 +5225,43 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { throw UnimplementedError(); } + @protected + RustStreamSink dco_decode_StreamSink_api_event_Sse(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(); + } + @protected String dco_decode_String(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw as String; } + @protected + ApiConnectionState dco_decode_api_connection_state(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return ApiConnectionState.values[raw as int]; + } + + @protected + ApiEvent dco_decode_api_event(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return ApiEvent( + kind: dco_decode_api_event_kind(arr[0]), + state: dco_decode_opt_box_autoadd_api_connection_state(arr[1]), + message: dco_decode_opt_String(arr[2]), + ); + } + + @protected + ApiEventKind dco_decode_api_event_kind(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return ApiEventKind.values[raw as int]; + } + @protected BackupPasswordKeys dco_decode_backup_password_keys(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1930,6 +5280,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as bool; } + @protected + ApiConnectionState dco_decode_box_autoadd_api_connection_state(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_api_connection_state(raw); + } + @protected BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1948,6 +5304,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_frb_pre_key_bundle(raw); } + @protected + int dco_decode_box_autoadd_i_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + @protected PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1960,6 +5322,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_init_config(raw); } + @protected + LegacySignalEncryptResult dco_decode_box_autoadd_legacy_signal_encrypt_result( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_legacy_signal_encrypt_result(raw); + } + + @protected + PreparedOutgoingMessage dco_decode_box_autoadd_prepared_outgoing_message( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_prepared_outgoing_message(raw); + } + @protected int dco_decode_box_autoadd_u_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2017,6 +5395,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + int dco_decode_i_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + @protected PlatformInt64 dco_decode_i_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2053,6 +5437,46 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + LegacySignalDecryptResult dco_decode_legacy_signal_decrypt_result( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return LegacySignalDecryptResult( + plaintext: dco_decode_opt_list_prim_u_8_strict(arr[0]), + decryptionErrorType: dco_decode_opt_box_autoadd_i_32(arr[1]), + ); + } + + @protected + LegacySignalEncryptResult dco_decode_legacy_signal_encrypt_result( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return LegacySignalEncryptResult( + ciphertext: dco_decode_list_prim_u_8_strict(arr[0]), + messageType: dco_decode_i_32(arr[1]), + ); + } + + @protected + LegacySignalPreKey dco_decode_legacy_signal_pre_key(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return LegacySignalPreKey( + id: dco_decode_i_64(arr[0]), + publicKey: dco_decode_list_prim_u_8_strict(arr[1]), + ); + } + @protected LegacyTableMigrationCount dco_decode_legacy_table_migration_count( dynamic raw, @@ -2079,6 +5503,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (raw as List).map(dco_decode_frb_pqc_pre_key).toList(); } + @protected + List dco_decode_list_legacy_signal_pre_key(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List) + .map(dco_decode_legacy_signal_pre_key) + .toList(); + } + @protected List dco_decode_list_legacy_table_migration_count( dynamic raw, @@ -2095,6 +5527,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (raw as List).map(dco_decode_list_prim_u_8_strict).toList(); } + @protected + List dco_decode_list_pqc_pre_key_input(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_pqc_pre_key_input).toList(); + } + + @protected + Int64List dco_decode_list_prim_i_64_strict(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeInt64List(raw); + } + @protected List dco_decode_list_prim_u_8_loose(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2134,18 +5578,53 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw == null ? null : dco_decode_String(raw); } + @protected + ApiConnectionState? dco_decode_opt_box_autoadd_api_connection_state( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null + ? null + : dco_decode_box_autoadd_api_connection_state(raw); + } + @protected double? dco_decode_opt_box_autoadd_f_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw == null ? null : dco_decode_box_autoadd_f_64(raw); } + @protected + int? dco_decode_opt_box_autoadd_i_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_i_32(raw); + } + @protected PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw == null ? null : dco_decode_box_autoadd_i_64(raw); } + @protected + LegacySignalEncryptResult? + dco_decode_opt_box_autoadd_legacy_signal_encrypt_result(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null + ? null + : dco_decode_box_autoadd_legacy_signal_encrypt_result(raw); + } + + @protected + PreparedOutgoingMessage? dco_decode_opt_box_autoadd_prepared_outgoing_message( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null + ? null + : dco_decode_box_autoadd_prepared_outgoing_message(raw); + } + @protected int? dco_decode_opt_box_autoadd_u_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2158,6 +5637,33 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw == null ? null : dco_decode_list_prim_u_8_strict(raw); } + @protected + PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 5) + throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); + return PqcPreKeyInput( + eccPreKeyId: dco_decode_i_64(arr[0]), + eccPreKey: dco_decode_list_prim_u_8_strict(arr[1]), + kyberPreKeyId: dco_decode_i_64(arr[2]), + kyberPreKey: dco_decode_list_prim_u_8_strict(arr[3]), + kyberPreKeySignature: dco_decode_list_prim_u_8_strict(arr[4]), + ); + } + + @protected + PreparedOutgoingMessage dco_decode_prepared_outgoing_message(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return PreparedOutgoingMessage( + message: dco_decode_list_prim_u_8_strict(arr[0]), + pushData: dco_decode_opt_list_prim_u_8_strict(arr[1]), + ); + } + @protected (PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict( dynamic raw, @@ -2201,6 +5707,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + RustApi dco_decode_rust_api(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 0) + throw Exception('unexpected arr length: expect 0 but see ${arr.length}'); + return RustApi(); + } + @protected RustAppDatabase dco_decode_rust_app_database(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2255,6 +5770,55 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return RustUtils(); } + @protected + ServerResultEmpty dco_decode_server_result_empty(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return ServerResultEmpty_Ok(); + case 1: + return ServerResultEmpty_ErrorCode( + dco_decode_i_32(raw[1]), + ); + default: + throw Exception("unreachable"); + } + } + + @protected + ServerResultI64 dco_decode_server_result_i_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return ServerResultI64_Ok( + dco_decode_i_64(raw[1]), + ); + case 1: + return ServerResultI64_ErrorCode( + dco_decode_i_32(raw[1]), + ); + default: + throw Exception("unreachable"); + } + } + + @protected + ServerResultVecU8 dco_decode_server_result_vec_u_8(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return ServerResultVecU8_Ok( + dco_decode_list_prim_u_8_strict(raw[1]), + ); + case 1: + return ServerResultVecU8_ErrorCode( + dco_decode_i_32(raw[1]), + ); + default: + throw Exception("unreachable"); + } + } + @protected SqlExecutionResult dco_decode_sql_execution_result(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2311,6 +5875,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as int; } + @protected + BigInt dco_decode_u_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeU64(raw); + } + @protected int dco_decode_u_8(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2366,6 +5936,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { throw UnimplementedError('Unreachable ()'); } + @protected + RustStreamSink sse_decode_StreamSink_api_event_Sse( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + throw UnimplementedError('Unreachable ()'); + } + @protected String sse_decode_String(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2373,6 +5951,33 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return utf8.decoder.convert(inner); } + @protected + ApiConnectionState sse_decode_api_connection_state( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_i_32(deserializer); + return ApiConnectionState.values[inner]; + } + + @protected + ApiEvent sse_decode_api_event(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_kind = sse_decode_api_event_kind(deserializer); + var var_state = sse_decode_opt_box_autoadd_api_connection_state( + deserializer, + ); + var var_message = sse_decode_opt_String(deserializer); + return ApiEvent(kind: var_kind, state: var_state, message: var_message); + } + + @protected + ApiEventKind sse_decode_api_event_kind(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_i_32(deserializer); + return ApiEventKind.values[inner]; + } + @protected BackupPasswordKeys sse_decode_backup_password_keys( SseDeserializer deserializer, @@ -2392,6 +5997,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getUint8() != 0; } + @protected + ApiConnectionState sse_decode_box_autoadd_api_connection_state( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_api_connection_state(deserializer)); + } + @protected BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys( SseDeserializer deserializer, @@ -2414,6 +6027,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_frb_pre_key_bundle(deserializer)); } + @protected + int sse_decode_box_autoadd_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_i_32(deserializer)); + } + @protected PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2426,6 +6045,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_init_config(deserializer)); } + @protected + LegacySignalEncryptResult sse_decode_box_autoadd_legacy_signal_encrypt_result( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_legacy_signal_encrypt_result(deserializer)); + } + + @protected + PreparedOutgoingMessage sse_decode_box_autoadd_prepared_outgoing_message( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_prepared_outgoing_message(deserializer)); + } + @protected int sse_decode_box_autoadd_u_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2498,6 +6133,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + int sse_decode_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getInt32(); + } + @protected PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2531,6 +6172,42 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + LegacySignalDecryptResult sse_decode_legacy_signal_decrypt_result( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_plaintext = sse_decode_opt_list_prim_u_8_strict(deserializer); + var var_decryptionErrorType = sse_decode_opt_box_autoadd_i_32(deserializer); + return LegacySignalDecryptResult( + plaintext: var_plaintext, + decryptionErrorType: var_decryptionErrorType, + ); + } + + @protected + LegacySignalEncryptResult sse_decode_legacy_signal_encrypt_result( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_ciphertext = sse_decode_list_prim_u_8_strict(deserializer); + var var_messageType = sse_decode_i_32(deserializer); + return LegacySignalEncryptResult( + ciphertext: var_ciphertext, + messageType: var_messageType, + ); + } + + @protected + LegacySignalPreKey sse_decode_legacy_signal_pre_key( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_id = sse_decode_i_64(deserializer); + var var_publicKey = sse_decode_list_prim_u_8_strict(deserializer); + return LegacySignalPreKey(id: var_id, publicKey: var_publicKey); + } + @protected LegacyTableMigrationCount sse_decode_legacy_table_migration_count( SseDeserializer deserializer, @@ -2567,6 +6244,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ans_; } + @protected + List sse_decode_list_legacy_signal_pre_key( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_legacy_signal_pre_key(deserializer)); + } + return ans_; + } + @protected List sse_decode_list_legacy_table_migration_count( SseDeserializer deserializer, @@ -2595,6 +6286,27 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ans_; } + @protected + List sse_decode_list_pqc_pre_key_input( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_pqc_pre_key_input(deserializer)); + } + return ans_; + } + + @protected + Int64List sse_decode_list_prim_i_64_strict(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getInt64List(len_); + } + @protected List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2659,6 +6371,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + ApiConnectionState? sse_decode_opt_box_autoadd_api_connection_state( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_api_connection_state(deserializer)); + } else { + return null; + } + } + @protected double? sse_decode_opt_box_autoadd_f_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2670,6 +6395,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_i_32(deserializer)); + } else { + return null; + } + } + @protected PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2681,6 +6417,35 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + LegacySignalEncryptResult? + sse_decode_opt_box_autoadd_legacy_signal_encrypt_result( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_legacy_signal_encrypt_result( + deserializer, + )); + } else { + return null; + } + } + + @protected + PreparedOutgoingMessage? sse_decode_opt_box_autoadd_prepared_outgoing_message( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_prepared_outgoing_message(deserializer)); + } else { + return null; + } + } + @protected int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2703,6 +6468,38 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_eccPreKeyId = sse_decode_i_64(deserializer); + var var_eccPreKey = sse_decode_list_prim_u_8_strict(deserializer); + var var_kyberPreKeyId = sse_decode_i_64(deserializer); + var var_kyberPreKey = sse_decode_list_prim_u_8_strict(deserializer); + var var_kyberPreKeySignature = sse_decode_list_prim_u_8_strict( + deserializer, + ); + return PqcPreKeyInput( + eccPreKeyId: var_eccPreKeyId, + eccPreKey: var_eccPreKey, + kyberPreKeyId: var_kyberPreKeyId, + kyberPreKey: var_kyberPreKey, + kyberPreKeySignature: var_kyberPreKeySignature, + ); + } + + @protected + PreparedOutgoingMessage sse_decode_prepared_outgoing_message( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_message = sse_decode_list_prim_u_8_strict(deserializer); + var var_pushData = sse_decode_opt_list_prim_u_8_strict(deserializer); + return PreparedOutgoingMessage( + message: var_message, + pushData: var_pushData, + ); + } + @protected (PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict( SseDeserializer deserializer, @@ -2733,6 +6530,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (var_field0, var_field1); } + @protected + RustApi sse_decode_rust_api(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RustApi(); + } + @protected RustAppDatabase sse_decode_rust_app_database(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2773,6 +6576,60 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return RustUtils(); } + @protected + ServerResultEmpty sse_decode_server_result_empty( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + return ServerResultEmpty_Ok(); + case 1: + var var_field0 = sse_decode_i_32(deserializer); + return ServerResultEmpty_ErrorCode(var_field0); + default: + throw UnimplementedError(''); + } + } + + @protected + ServerResultI64 sse_decode_server_result_i_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + var var_field0 = sse_decode_i_64(deserializer); + return ServerResultI64_Ok(var_field0); + case 1: + var var_field0 = sse_decode_i_32(deserializer); + return ServerResultI64_ErrorCode(var_field0); + default: + throw UnimplementedError(''); + } + } + + @protected + ServerResultVecU8 sse_decode_server_result_vec_u_8( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + var var_field0 = sse_decode_list_prim_u_8_strict(deserializer); + return ServerResultVecU8_Ok(var_field0); + case 1: + var var_field0 = sse_decode_i_32(deserializer); + return ServerResultVecU8_ErrorCode(var_field0); + default: + throw UnimplementedError(''); + } + } + @protected SqlExecutionResult sse_decode_sql_execution_result( SseDeserializer deserializer, @@ -2824,6 +6681,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getUint32(); } + @protected + BigInt sse_decode_u_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getBigUint64(); + } + @protected int sse_decode_u_8(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2848,12 +6711,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getBigUint64(); } - @protected - int sse_decode_i_32(SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return deserializer.buffer.getInt32(); - } - @protected void sse_encode_AnyhowException( AnyhowException self, @@ -2863,6 +6720,45 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(self.message, serializer); } + @protected + void + sse_encode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + FutureOr Function(String, String, PlatformInt64, String) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + self, + ), + serializer, + ); + } + + @protected + void sse_encode_DartFn_Inputs_String_bool_Output_unit_AnyhowException( + FutureOr Function(String, bool) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_String_bool_Output_unit_AnyhowException(self), + serializer, + ); + } + + @protected + void sse_encode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException( + FutureOr Function(String, PlatformInt64) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException(self), + serializer, + ); + } + @protected void sse_encode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( FutureOr> Function() self, @@ -2875,6 +6771,119 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + void + sse_encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + FutureOr> Function() self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + self, + ), + serializer, + ); + } + + @protected + void sse_encode_DartFn_Inputs__Output_unit_AnyhowException( + FutureOr Function() self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs__Output_unit_AnyhowException(self), + serializer, + ); + } + + @protected + void sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(self), + serializer, + ); + } + + @protected + void sse_encode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64, String, String) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException(self), + serializer, + ); + } + + @protected + void + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List) + self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + self, + ), + serializer, + ); + } + + @protected + void + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + self, + ), + serializer, + ); + } + + @protected + void + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List, int) + self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + self, + ), + serializer, + ); + } + + @protected + void + sse_encode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + FutureOr Function(PlatformInt64, String?, Uint8List, int) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + self, + ), + serializer, + ); + } + @protected void sse_encode_DartOpaque(Object self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2919,12 +6928,52 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + void sse_encode_StreamSink_api_event_Sse( + RustStreamSink self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String( + self.setupAndSerialize( + codec: SseCodec( + decodeSuccessData: sse_decode_api_event, + decodeErrorData: sse_decode_AnyhowException, + ), + ), + serializer, + ); + } + @protected void sse_encode_String(String self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); } + @protected + void sse_encode_api_connection_state( + ApiConnectionState self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.index, serializer); + } + + @protected + void sse_encode_api_event(ApiEvent self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_api_event_kind(self.kind, serializer); + sse_encode_opt_box_autoadd_api_connection_state(self.state, serializer); + sse_encode_opt_String(self.message, serializer); + } + + @protected + void sse_encode_api_event_kind(ApiEventKind self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.index, serializer); + } + @protected void sse_encode_backup_password_keys( BackupPasswordKeys self, @@ -2941,6 +6990,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putUint8(self ? 1 : 0); } + @protected + void sse_encode_box_autoadd_api_connection_state( + ApiConnectionState self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_api_connection_state(self, serializer); + } + @protected void sse_encode_box_autoadd_backup_password_keys( BackupPasswordKeys self, @@ -2965,6 +7023,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_frb_pre_key_bundle(self, serializer); } + @protected + void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self, serializer); + } + @protected void sse_encode_box_autoadd_i_64( PlatformInt64 self, @@ -2983,6 +7047,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_init_config(self, serializer); } + @protected + void sse_encode_box_autoadd_legacy_signal_encrypt_result( + LegacySignalEncryptResult self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_legacy_signal_encrypt_result(self, serializer); + } + + @protected + void sse_encode_box_autoadd_prepared_outgoing_message( + PreparedOutgoingMessage self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_prepared_outgoing_message(self, serializer); + } + @protected void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3032,6 +7114,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_strict(self.identityKey, serializer); } + @protected + void sse_encode_i_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putInt32(self); + } + @protected void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3061,6 +7149,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_legacy_table_migration_count(self.tables, serializer); } + @protected + void sse_encode_legacy_signal_decrypt_result( + LegacySignalDecryptResult self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_list_prim_u_8_strict(self.plaintext, serializer); + sse_encode_opt_box_autoadd_i_32(self.decryptionErrorType, serializer); + } + + @protected + void sse_encode_legacy_signal_encrypt_result( + LegacySignalEncryptResult self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(self.ciphertext, serializer); + sse_encode_i_32(self.messageType, serializer); + } + + @protected + void sse_encode_legacy_signal_pre_key( + LegacySignalPreKey self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_64(self.id, serializer); + sse_encode_list_prim_u_8_strict(self.publicKey, serializer); + } + @protected void sse_encode_legacy_table_migration_count( LegacyTableMigrationCount self, @@ -3092,6 +7210,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_list_legacy_signal_pre_key( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_legacy_signal_pre_key(item, serializer); + } + } + @protected void sse_encode_list_legacy_table_migration_count( List self, @@ -3116,6 +7246,28 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_list_pqc_pre_key_input( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_pqc_pre_key_input(item, serializer); + } + } + + @protected + void sse_encode_list_prim_i_64_strict( + Int64List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer.putInt64List(self); + } + @protected void sse_encode_list_prim_u_8_loose( List self, @@ -3181,6 +7333,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_opt_box_autoadd_api_connection_state( + ApiConnectionState? self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_api_connection_state(self, serializer); + } + } + @protected void sse_encode_opt_box_autoadd_f_64(double? self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3191,6 +7356,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_i_32(self, serializer); + } + } + @protected void sse_encode_opt_box_autoadd_i_64( PlatformInt64? self, @@ -3204,6 +7379,32 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_opt_box_autoadd_legacy_signal_encrypt_result( + LegacySignalEncryptResult? self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_legacy_signal_encrypt_result(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_prepared_outgoing_message( + PreparedOutgoingMessage? self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_prepared_outgoing_message(self, serializer); + } + } + @protected void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3227,6 +7428,29 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_pqc_pre_key_input( + PqcPreKeyInput self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_64(self.eccPreKeyId, serializer); + sse_encode_list_prim_u_8_strict(self.eccPreKey, serializer); + sse_encode_i_64(self.kyberPreKeyId, serializer); + sse_encode_list_prim_u_8_strict(self.kyberPreKey, serializer); + sse_encode_list_prim_u_8_strict(self.kyberPreKeySignature, serializer); + } + + @protected + void sse_encode_prepared_outgoing_message( + PreparedOutgoingMessage self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(self.message, serializer); + sse_encode_opt_list_prim_u_8_strict(self.pushData, serializer); + } + @protected void sse_encode_record_i_64_list_prim_u_8_strict( (PlatformInt64, Uint8List) self, @@ -3257,6 +7481,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(self.$2, serializer); } + @protected + void sse_encode_rust_api(RustApi self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + @protected void sse_encode_rust_app_database( RustAppDatabase self, @@ -3299,6 +7528,53 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs } + @protected + void sse_encode_server_result_empty( + ServerResultEmpty self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case ServerResultEmpty_Ok(): + sse_encode_i_32(0, serializer); + case ServerResultEmpty_ErrorCode(field0: final field0): + sse_encode_i_32(1, serializer); + sse_encode_i_32(field0, serializer); + } + } + + @protected + void sse_encode_server_result_i_64( + ServerResultI64 self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case ServerResultI64_Ok(field0: final field0): + sse_encode_i_32(0, serializer); + sse_encode_i_64(field0, serializer); + case ServerResultI64_ErrorCode(field0: final field0): + sse_encode_i_32(1, serializer); + sse_encode_i_32(field0, serializer); + } + } + + @protected + void sse_encode_server_result_vec_u_8( + ServerResultVecU8 self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case ServerResultVecU8_Ok(field0: final field0): + sse_encode_i_32(0, serializer); + sse_encode_list_prim_u_8_strict(field0, serializer); + case ServerResultVecU8_ErrorCode(field0: final field0): + sse_encode_i_32(1, serializer); + sse_encode_i_32(field0, serializer); + } + } + @protected void sse_encode_sql_execution_result( SqlExecutionResult self, @@ -3338,6 +7614,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putUint32(self); } + @protected + void sse_encode_u_64(BigInt self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putBigUint64(self); + } + @protected void sse_encode_u_8(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3360,10 +7642,4 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putBigUint64(self); } - - @protected - void sse_encode_i_32(int self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - serializer.buffer.putInt32(self); - } } diff --git a/lib/core/frb_generated.io.dart b/lib/core/frb_generated.io.dart index 41f58771..eaf34ba3 100644 --- a/lib/core/frb_generated.io.dart +++ b/lib/core/frb_generated.io.dart @@ -3,8 +3,11 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/server/prekeys.dart'; import 'bridge.dart'; +import 'bridge/api.dart'; import 'bridge/callbacks.dart'; +import 'bridge/groups.dart'; import 'bridge/wrapper.dart'; import 'bridge/wrapper/app_database.dart'; import 'bridge/wrapper/backup.dart'; @@ -32,12 +35,70 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnyhowException dco_decode_AnyhowException(dynamic raw); + @protected + FutureOr Function(String, String, PlatformInt64, String) + dco_decode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(String, bool) + dco_decode_DartFn_Inputs_String_bool_Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(String, PlatformInt64) + dco_decode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException(dynamic raw); + @protected FutureOr> Function() dco_decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( dynamic raw, ); + @protected + FutureOr> Function() + dco_decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function() + dco_decode_DartFn_Inputs__Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(PlatformInt64) + dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(PlatformInt64, String, String) + dco_decode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(PlatformInt64, Uint8List) + dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(PlatformInt64, Uint8List) + dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(PlatformInt64, Uint8List, int) + dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(PlatformInt64, String?, Uint8List, int) + dco_decode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + dynamic raw, + ); + @protected Object dco_decode_DartOpaque(dynamic raw); @@ -49,15 +110,30 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected RustStreamSink dco_decode_StreamSink_String_Sse(dynamic raw); + @protected + RustStreamSink dco_decode_StreamSink_api_event_Sse(dynamic raw); + @protected String dco_decode_String(dynamic raw); + @protected + ApiConnectionState dco_decode_api_connection_state(dynamic raw); + + @protected + ApiEvent dco_decode_api_event(dynamic raw); + + @protected + ApiEventKind dco_decode_api_event_kind(dynamic raw); + @protected BackupPasswordKeys dco_decode_backup_password_keys(dynamic raw); @protected bool dco_decode_bool(dynamic raw); + @protected + ApiConnectionState dco_decode_box_autoadd_api_connection_state(dynamic raw); + @protected BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw); @@ -67,12 +143,25 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw); + @protected + int dco_decode_box_autoadd_i_32(dynamic raw); + @protected PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw); @protected InitConfig dco_decode_box_autoadd_init_config(dynamic raw); + @protected + LegacySignalEncryptResult dco_decode_box_autoadd_legacy_signal_encrypt_result( + dynamic raw, + ); + + @protected + PreparedOutgoingMessage dco_decode_box_autoadd_prepared_outgoing_message( + dynamic raw, + ); + @protected int dco_decode_box_autoadd_u_32(dynamic raw); @@ -88,6 +177,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected FrbPreKeyBundle dco_decode_frb_pre_key_bundle(dynamic raw); + @protected + int dco_decode_i_32(dynamic raw); + @protected PlatformInt64 dco_decode_i_64(dynamic raw); @@ -100,6 +192,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LegacyMigrationReport dco_decode_legacy_migration_report(dynamic raw); + @protected + LegacySignalDecryptResult dco_decode_legacy_signal_decrypt_result( + dynamic raw, + ); + + @protected + LegacySignalEncryptResult dco_decode_legacy_signal_encrypt_result( + dynamic raw, + ); + + @protected + LegacySignalPreKey dco_decode_legacy_signal_pre_key(dynamic raw); + @protected LegacyTableMigrationCount dco_decode_legacy_table_migration_count( dynamic raw, @@ -111,6 +216,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_frb_pqc_pre_key(dynamic raw); + @protected + List dco_decode_list_legacy_signal_pre_key(dynamic raw); + @protected List dco_decode_list_legacy_table_migration_count( dynamic raw, @@ -119,6 +227,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_list_prim_u_8_strict(dynamic raw); + @protected + List dco_decode_list_pqc_pre_key_input(dynamic raw); + + @protected + Int64List dco_decode_list_prim_i_64_strict(dynamic raw); + @protected List dco_decode_list_prim_u_8_loose(dynamic raw); @@ -138,18 +252,41 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String? dco_decode_opt_String(dynamic raw); + @protected + ApiConnectionState? dco_decode_opt_box_autoadd_api_connection_state( + dynamic raw, + ); + @protected double? dco_decode_opt_box_autoadd_f_64(dynamic raw); + @protected + int? dco_decode_opt_box_autoadd_i_32(dynamic raw); + @protected PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw); + @protected + LegacySignalEncryptResult? + dco_decode_opt_box_autoadd_legacy_signal_encrypt_result(dynamic raw); + + @protected + PreparedOutgoingMessage? dco_decode_opt_box_autoadd_prepared_outgoing_message( + dynamic raw, + ); + @protected int? dco_decode_opt_box_autoadd_u_32(dynamic raw); @protected Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw); + @protected + PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw); + + @protected + PreparedOutgoingMessage dco_decode_prepared_outgoing_message(dynamic raw); + @protected (PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict( dynamic raw, @@ -163,6 +300,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected (String, String) dco_decode_record_string_string(dynamic raw); + @protected + RustApi dco_decode_rust_api(dynamic raw); + @protected RustAppDatabase dco_decode_rust_app_database(dynamic raw); @@ -181,6 +321,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected RustUtils dco_decode_rust_utils(dynamic raw); + @protected + ServerResultEmpty dco_decode_server_result_empty(dynamic raw); + + @protected + ServerResultI64 dco_decode_server_result_i_64(dynamic raw); + + @protected + ServerResultVecU8 dco_decode_server_result_vec_u_8(dynamic raw); + @protected SqlExecutionResult dco_decode_sql_execution_result(dynamic raw); @@ -196,6 +345,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected int dco_decode_u_32(dynamic raw); + @protected + BigInt dco_decode_u_64(dynamic raw); + @protected int dco_decode_u_8(dynamic raw); @@ -224,9 +376,25 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + RustStreamSink sse_decode_StreamSink_api_event_Sse( + SseDeserializer deserializer, + ); + @protected String sse_decode_String(SseDeserializer deserializer); + @protected + ApiConnectionState sse_decode_api_connection_state( + SseDeserializer deserializer, + ); + + @protected + ApiEvent sse_decode_api_event(SseDeserializer deserializer); + + @protected + ApiEventKind sse_decode_api_event_kind(SseDeserializer deserializer); + @protected BackupPasswordKeys sse_decode_backup_password_keys( SseDeserializer deserializer, @@ -235,6 +403,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected bool sse_decode_bool(SseDeserializer deserializer); + @protected + ApiConnectionState sse_decode_box_autoadd_api_connection_state( + SseDeserializer deserializer, + ); + @protected BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys( SseDeserializer deserializer, @@ -248,12 +421,25 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + int sse_decode_box_autoadd_i_32(SseDeserializer deserializer); + @protected PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer); @protected InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer); + @protected + LegacySignalEncryptResult sse_decode_box_autoadd_legacy_signal_encrypt_result( + SseDeserializer deserializer, + ); + + @protected + PreparedOutgoingMessage sse_decode_box_autoadd_prepared_outgoing_message( + SseDeserializer deserializer, + ); + @protected int sse_decode_box_autoadd_u_32(SseDeserializer deserializer); @@ -271,6 +457,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected FrbPreKeyBundle sse_decode_frb_pre_key_bundle(SseDeserializer deserializer); + @protected + int sse_decode_i_32(SseDeserializer deserializer); + @protected PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); @@ -285,6 +474,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + LegacySignalDecryptResult sse_decode_legacy_signal_decrypt_result( + SseDeserializer deserializer, + ); + + @protected + LegacySignalEncryptResult sse_decode_legacy_signal_encrypt_result( + SseDeserializer deserializer, + ); + + @protected + LegacySignalPreKey sse_decode_legacy_signal_pre_key( + SseDeserializer deserializer, + ); + @protected LegacyTableMigrationCount sse_decode_legacy_table_migration_count( SseDeserializer deserializer, @@ -298,6 +502,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + List sse_decode_list_legacy_signal_pre_key( + SseDeserializer deserializer, + ); + @protected List sse_decode_list_legacy_table_migration_count( SseDeserializer deserializer, @@ -308,6 +517,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + List sse_decode_list_pqc_pre_key_input( + SseDeserializer deserializer, + ); + + @protected + Int64List sse_decode_list_prim_i_64_strict(SseDeserializer deserializer); + @protected List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); @@ -329,18 +546,45 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String? sse_decode_opt_String(SseDeserializer deserializer); + @protected + ApiConnectionState? sse_decode_opt_box_autoadd_api_connection_state( + SseDeserializer deserializer, + ); + @protected double? sse_decode_opt_box_autoadd_f_64(SseDeserializer deserializer); + @protected + int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer); + @protected PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer); + @protected + LegacySignalEncryptResult? + sse_decode_opt_box_autoadd_legacy_signal_encrypt_result( + SseDeserializer deserializer, + ); + + @protected + PreparedOutgoingMessage? sse_decode_opt_box_autoadd_prepared_outgoing_message( + SseDeserializer deserializer, + ); + @protected int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer); @protected Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer); + + @protected + PreparedOutgoingMessage sse_decode_prepared_outgoing_message( + SseDeserializer deserializer, + ); + @protected (PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict( SseDeserializer deserializer, @@ -356,6 +600,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + RustApi sse_decode_rust_api(SseDeserializer deserializer); + @protected RustAppDatabase sse_decode_rust_app_database(SseDeserializer deserializer); @@ -378,6 +625,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected RustUtils sse_decode_rust_utils(SseDeserializer deserializer); + @protected + ServerResultEmpty sse_decode_server_result_empty( + SseDeserializer deserializer, + ); + + @protected + ServerResultI64 sse_decode_server_result_i_64(SseDeserializer deserializer); + + @protected + ServerResultVecU8 sse_decode_server_result_vec_u_8( + SseDeserializer deserializer, + ); + @protected SqlExecutionResult sse_decode_sql_execution_result( SseDeserializer deserializer, @@ -395,6 +655,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected int sse_decode_u_32(SseDeserializer deserializer); + @protected + BigInt sse_decode_u_64(SseDeserializer deserializer); + @protected int sse_decode_u_8(SseDeserializer deserializer); @@ -407,21 +670,92 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt sse_decode_usize(SseDeserializer deserializer); - @protected - int sse_decode_i_32(SseDeserializer deserializer); - @protected void sse_encode_AnyhowException( AnyhowException self, SseSerializer serializer, ); + @protected + void + sse_encode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + FutureOr Function(String, String, PlatformInt64, String) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_String_bool_Output_unit_AnyhowException( + FutureOr Function(String, bool) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException( + FutureOr Function(String, PlatformInt64) self, + SseSerializer serializer, + ); + @protected void sse_encode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( FutureOr> Function() self, SseSerializer serializer, ); + @protected + void + sse_encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + FutureOr> Function() self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs__Output_unit_AnyhowException( + FutureOr Function() self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64, String, String) self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List) + self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List) self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List, int) + self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + FutureOr Function(PlatformInt64, String?, Uint8List, int) self, + SseSerializer serializer, + ); + @protected void sse_encode_DartOpaque(Object self, SseSerializer serializer); @@ -437,9 +771,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_StreamSink_api_event_Sse( + RustStreamSink self, + SseSerializer serializer, + ); + @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_api_connection_state( + ApiConnectionState self, + SseSerializer serializer, + ); + + @protected + void sse_encode_api_event(ApiEvent self, SseSerializer serializer); + + @protected + void sse_encode_api_event_kind(ApiEventKind self, SseSerializer serializer); + @protected void sse_encode_backup_password_keys( BackupPasswordKeys self, @@ -449,6 +801,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_bool(bool self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_api_connection_state( + ApiConnectionState self, + SseSerializer serializer, + ); + @protected void sse_encode_box_autoadd_backup_password_keys( BackupPasswordKeys self, @@ -464,6 +822,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer); + @protected void sse_encode_box_autoadd_i_64( PlatformInt64 self, @@ -476,6 +837,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_box_autoadd_legacy_signal_encrypt_result( + LegacySignalEncryptResult self, + SseSerializer serializer, + ); + + @protected + void sse_encode_box_autoadd_prepared_outgoing_message( + PreparedOutgoingMessage self, + SseSerializer serializer, + ); + @protected void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer); @@ -497,6 +870,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + @protected void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); @@ -512,6 +888,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_legacy_signal_decrypt_result( + LegacySignalDecryptResult self, + SseSerializer serializer, + ); + + @protected + void sse_encode_legacy_signal_encrypt_result( + LegacySignalEncryptResult self, + SseSerializer serializer, + ); + + @protected + void sse_encode_legacy_signal_pre_key( + LegacySignalPreKey self, + SseSerializer serializer, + ); + @protected void sse_encode_legacy_table_migration_count( LegacyTableMigrationCount self, @@ -527,6 +921,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_legacy_signal_pre_key( + List self, + SseSerializer serializer, + ); + @protected void sse_encode_list_legacy_table_migration_count( List self, @@ -539,6 +939,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_pqc_pre_key_input( + List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_prim_i_64_strict( + Int64List self, + SseSerializer serializer, + ); + @protected void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); @@ -563,15 +975,36 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_opt_String(String? self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_api_connection_state( + ApiConnectionState? self, + SseSerializer serializer, + ); + @protected void sse_encode_opt_box_autoadd_f_64(double? self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_i_64( PlatformInt64? self, SseSerializer serializer, ); + @protected + void sse_encode_opt_box_autoadd_legacy_signal_encrypt_result( + LegacySignalEncryptResult? self, + SseSerializer serializer, + ); + + @protected + void sse_encode_opt_box_autoadd_prepared_outgoing_message( + PreparedOutgoingMessage? self, + SseSerializer serializer, + ); + @protected void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer); @@ -581,6 +1014,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_pqc_pre_key_input( + PqcPreKeyInput self, + SseSerializer serializer, + ); + + @protected + void sse_encode_prepared_outgoing_message( + PreparedOutgoingMessage self, + SseSerializer serializer, + ); + @protected void sse_encode_record_i_64_list_prim_u_8_strict( (PlatformInt64, Uint8List) self, @@ -599,6 +1044,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_rust_api(RustApi self, SseSerializer serializer); + @protected void sse_encode_rust_app_database( RustAppDatabase self, @@ -629,6 +1077,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_rust_utils(RustUtils self, SseSerializer serializer); + @protected + void sse_encode_server_result_empty( + ServerResultEmpty self, + SseSerializer serializer, + ); + + @protected + void sse_encode_server_result_i_64( + ServerResultI64 self, + SseSerializer serializer, + ); + + @protected + void sse_encode_server_result_vec_u_8( + ServerResultVecU8 self, + SseSerializer serializer, + ); + @protected void sse_encode_sql_execution_result( SqlExecutionResult self, @@ -647,6 +1113,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_u_32(int self, SseSerializer serializer); + @protected + void sse_encode_u_64(BigInt self, SseSerializer serializer); + @protected void sse_encode_u_8(int self, SseSerializer serializer); @@ -658,9 +1127,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_usize(BigInt self, SseSerializer serializer); - - @protected - void sse_encode_i_32(int self, SseSerializer serializer); } // Section: wire_class diff --git a/lib/core/frb_generated.web.dart b/lib/core/frb_generated.web.dart index 6a9d0731..1f694051 100644 --- a/lib/core/frb_generated.web.dart +++ b/lib/core/frb_generated.web.dart @@ -6,8 +6,11 @@ // Static analysis wrongly picks the IO variant, thus ignore this // ignore_for_file: argument_type_not_assignable +import 'api/server/prekeys.dart'; import 'bridge.dart'; +import 'bridge/api.dart'; import 'bridge/callbacks.dart'; +import 'bridge/groups.dart'; import 'bridge/wrapper.dart'; import 'bridge/wrapper/app_database.dart'; import 'bridge/wrapper/backup.dart'; @@ -34,12 +37,70 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnyhowException dco_decode_AnyhowException(dynamic raw); + @protected + FutureOr Function(String, String, PlatformInt64, String) + dco_decode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(String, bool) + dco_decode_DartFn_Inputs_String_bool_Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(String, PlatformInt64) + dco_decode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException(dynamic raw); + @protected FutureOr> Function() dco_decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( dynamic raw, ); + @protected + FutureOr> Function() + dco_decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function() + dco_decode_DartFn_Inputs__Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(PlatformInt64) + dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(PlatformInt64, String, String) + dco_decode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(PlatformInt64, Uint8List) + dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(PlatformInt64, Uint8List) + dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(PlatformInt64, Uint8List, int) + dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function(PlatformInt64, String?, Uint8List, int) + dco_decode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + dynamic raw, + ); + @protected Object dco_decode_DartOpaque(dynamic raw); @@ -51,15 +112,30 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected RustStreamSink dco_decode_StreamSink_String_Sse(dynamic raw); + @protected + RustStreamSink dco_decode_StreamSink_api_event_Sse(dynamic raw); + @protected String dco_decode_String(dynamic raw); + @protected + ApiConnectionState dco_decode_api_connection_state(dynamic raw); + + @protected + ApiEvent dco_decode_api_event(dynamic raw); + + @protected + ApiEventKind dco_decode_api_event_kind(dynamic raw); + @protected BackupPasswordKeys dco_decode_backup_password_keys(dynamic raw); @protected bool dco_decode_bool(dynamic raw); + @protected + ApiConnectionState dco_decode_box_autoadd_api_connection_state(dynamic raw); + @protected BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw); @@ -69,12 +145,25 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw); + @protected + int dco_decode_box_autoadd_i_32(dynamic raw); + @protected PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw); @protected InitConfig dco_decode_box_autoadd_init_config(dynamic raw); + @protected + LegacySignalEncryptResult dco_decode_box_autoadd_legacy_signal_encrypt_result( + dynamic raw, + ); + + @protected + PreparedOutgoingMessage dco_decode_box_autoadd_prepared_outgoing_message( + dynamic raw, + ); + @protected int dco_decode_box_autoadd_u_32(dynamic raw); @@ -90,6 +179,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected FrbPreKeyBundle dco_decode_frb_pre_key_bundle(dynamic raw); + @protected + int dco_decode_i_32(dynamic raw); + @protected PlatformInt64 dco_decode_i_64(dynamic raw); @@ -102,6 +194,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LegacyMigrationReport dco_decode_legacy_migration_report(dynamic raw); + @protected + LegacySignalDecryptResult dco_decode_legacy_signal_decrypt_result( + dynamic raw, + ); + + @protected + LegacySignalEncryptResult dco_decode_legacy_signal_encrypt_result( + dynamic raw, + ); + + @protected + LegacySignalPreKey dco_decode_legacy_signal_pre_key(dynamic raw); + @protected LegacyTableMigrationCount dco_decode_legacy_table_migration_count( dynamic raw, @@ -113,6 +218,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_frb_pqc_pre_key(dynamic raw); + @protected + List dco_decode_list_legacy_signal_pre_key(dynamic raw); + @protected List dco_decode_list_legacy_table_migration_count( dynamic raw, @@ -121,6 +229,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_list_prim_u_8_strict(dynamic raw); + @protected + List dco_decode_list_pqc_pre_key_input(dynamic raw); + + @protected + Int64List dco_decode_list_prim_i_64_strict(dynamic raw); + @protected List dco_decode_list_prim_u_8_loose(dynamic raw); @@ -140,18 +254,41 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String? dco_decode_opt_String(dynamic raw); + @protected + ApiConnectionState? dco_decode_opt_box_autoadd_api_connection_state( + dynamic raw, + ); + @protected double? dco_decode_opt_box_autoadd_f_64(dynamic raw); + @protected + int? dco_decode_opt_box_autoadd_i_32(dynamic raw); + @protected PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw); + @protected + LegacySignalEncryptResult? + dco_decode_opt_box_autoadd_legacy_signal_encrypt_result(dynamic raw); + + @protected + PreparedOutgoingMessage? dco_decode_opt_box_autoadd_prepared_outgoing_message( + dynamic raw, + ); + @protected int? dco_decode_opt_box_autoadd_u_32(dynamic raw); @protected Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw); + @protected + PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw); + + @protected + PreparedOutgoingMessage dco_decode_prepared_outgoing_message(dynamic raw); + @protected (PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict( dynamic raw, @@ -165,6 +302,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected (String, String) dco_decode_record_string_string(dynamic raw); + @protected + RustApi dco_decode_rust_api(dynamic raw); + @protected RustAppDatabase dco_decode_rust_app_database(dynamic raw); @@ -183,6 +323,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected RustUtils dco_decode_rust_utils(dynamic raw); + @protected + ServerResultEmpty dco_decode_server_result_empty(dynamic raw); + + @protected + ServerResultI64 dco_decode_server_result_i_64(dynamic raw); + + @protected + ServerResultVecU8 dco_decode_server_result_vec_u_8(dynamic raw); + @protected SqlExecutionResult dco_decode_sql_execution_result(dynamic raw); @@ -198,6 +347,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected int dco_decode_u_32(dynamic raw); + @protected + BigInt dco_decode_u_64(dynamic raw); + @protected int dco_decode_u_8(dynamic raw); @@ -226,9 +378,25 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + RustStreamSink sse_decode_StreamSink_api_event_Sse( + SseDeserializer deserializer, + ); + @protected String sse_decode_String(SseDeserializer deserializer); + @protected + ApiConnectionState sse_decode_api_connection_state( + SseDeserializer deserializer, + ); + + @protected + ApiEvent sse_decode_api_event(SseDeserializer deserializer); + + @protected + ApiEventKind sse_decode_api_event_kind(SseDeserializer deserializer); + @protected BackupPasswordKeys sse_decode_backup_password_keys( SseDeserializer deserializer, @@ -237,6 +405,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected bool sse_decode_bool(SseDeserializer deserializer); + @protected + ApiConnectionState sse_decode_box_autoadd_api_connection_state( + SseDeserializer deserializer, + ); + @protected BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys( SseDeserializer deserializer, @@ -250,12 +423,25 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + int sse_decode_box_autoadd_i_32(SseDeserializer deserializer); + @protected PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer); @protected InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer); + @protected + LegacySignalEncryptResult sse_decode_box_autoadd_legacy_signal_encrypt_result( + SseDeserializer deserializer, + ); + + @protected + PreparedOutgoingMessage sse_decode_box_autoadd_prepared_outgoing_message( + SseDeserializer deserializer, + ); + @protected int sse_decode_box_autoadd_u_32(SseDeserializer deserializer); @@ -273,6 +459,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected FrbPreKeyBundle sse_decode_frb_pre_key_bundle(SseDeserializer deserializer); + @protected + int sse_decode_i_32(SseDeserializer deserializer); + @protected PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); @@ -287,6 +476,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + LegacySignalDecryptResult sse_decode_legacy_signal_decrypt_result( + SseDeserializer deserializer, + ); + + @protected + LegacySignalEncryptResult sse_decode_legacy_signal_encrypt_result( + SseDeserializer deserializer, + ); + + @protected + LegacySignalPreKey sse_decode_legacy_signal_pre_key( + SseDeserializer deserializer, + ); + @protected LegacyTableMigrationCount sse_decode_legacy_table_migration_count( SseDeserializer deserializer, @@ -300,6 +504,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + List sse_decode_list_legacy_signal_pre_key( + SseDeserializer deserializer, + ); + @protected List sse_decode_list_legacy_table_migration_count( SseDeserializer deserializer, @@ -310,6 +519,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + List sse_decode_list_pqc_pre_key_input( + SseDeserializer deserializer, + ); + + @protected + Int64List sse_decode_list_prim_i_64_strict(SseDeserializer deserializer); + @protected List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); @@ -331,18 +548,45 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String? sse_decode_opt_String(SseDeserializer deserializer); + @protected + ApiConnectionState? sse_decode_opt_box_autoadd_api_connection_state( + SseDeserializer deserializer, + ); + @protected double? sse_decode_opt_box_autoadd_f_64(SseDeserializer deserializer); + @protected + int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer); + @protected PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer); + @protected + LegacySignalEncryptResult? + sse_decode_opt_box_autoadd_legacy_signal_encrypt_result( + SseDeserializer deserializer, + ); + + @protected + PreparedOutgoingMessage? sse_decode_opt_box_autoadd_prepared_outgoing_message( + SseDeserializer deserializer, + ); + @protected int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer); @protected Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer); + + @protected + PreparedOutgoingMessage sse_decode_prepared_outgoing_message( + SseDeserializer deserializer, + ); + @protected (PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict( SseDeserializer deserializer, @@ -358,6 +602,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + RustApi sse_decode_rust_api(SseDeserializer deserializer); + @protected RustAppDatabase sse_decode_rust_app_database(SseDeserializer deserializer); @@ -380,6 +627,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected RustUtils sse_decode_rust_utils(SseDeserializer deserializer); + @protected + ServerResultEmpty sse_decode_server_result_empty( + SseDeserializer deserializer, + ); + + @protected + ServerResultI64 sse_decode_server_result_i_64(SseDeserializer deserializer); + + @protected + ServerResultVecU8 sse_decode_server_result_vec_u_8( + SseDeserializer deserializer, + ); + @protected SqlExecutionResult sse_decode_sql_execution_result( SseDeserializer deserializer, @@ -397,6 +657,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected int sse_decode_u_32(SseDeserializer deserializer); + @protected + BigInt sse_decode_u_64(SseDeserializer deserializer); + @protected int sse_decode_u_8(SseDeserializer deserializer); @@ -409,21 +672,92 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt sse_decode_usize(SseDeserializer deserializer); - @protected - int sse_decode_i_32(SseDeserializer deserializer); - @protected void sse_encode_AnyhowException( AnyhowException self, SseSerializer serializer, ); + @protected + void + sse_encode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + FutureOr Function(String, String, PlatformInt64, String) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_String_bool_Output_unit_AnyhowException( + FutureOr Function(String, bool) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException( + FutureOr Function(String, PlatformInt64) self, + SseSerializer serializer, + ); + @protected void sse_encode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( FutureOr> Function() self, SseSerializer serializer, ); + @protected + void + sse_encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + FutureOr> Function() self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs__Output_unit_AnyhowException( + FutureOr Function() self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64, String, String) self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List) + self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List) self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + FutureOr Function(PlatformInt64, Uint8List, int) + self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + FutureOr Function(PlatformInt64, String?, Uint8List, int) self, + SseSerializer serializer, + ); + @protected void sse_encode_DartOpaque(Object self, SseSerializer serializer); @@ -439,9 +773,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_StreamSink_api_event_Sse( + RustStreamSink self, + SseSerializer serializer, + ); + @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_api_connection_state( + ApiConnectionState self, + SseSerializer serializer, + ); + + @protected + void sse_encode_api_event(ApiEvent self, SseSerializer serializer); + + @protected + void sse_encode_api_event_kind(ApiEventKind self, SseSerializer serializer); + @protected void sse_encode_backup_password_keys( BackupPasswordKeys self, @@ -451,6 +803,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_bool(bool self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_api_connection_state( + ApiConnectionState self, + SseSerializer serializer, + ); + @protected void sse_encode_box_autoadd_backup_password_keys( BackupPasswordKeys self, @@ -466,6 +824,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer); + @protected void sse_encode_box_autoadd_i_64( PlatformInt64 self, @@ -478,6 +839,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_box_autoadd_legacy_signal_encrypt_result( + LegacySignalEncryptResult self, + SseSerializer serializer, + ); + + @protected + void sse_encode_box_autoadd_prepared_outgoing_message( + PreparedOutgoingMessage self, + SseSerializer serializer, + ); + @protected void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer); @@ -499,6 +872,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + @protected void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); @@ -514,6 +890,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_legacy_signal_decrypt_result( + LegacySignalDecryptResult self, + SseSerializer serializer, + ); + + @protected + void sse_encode_legacy_signal_encrypt_result( + LegacySignalEncryptResult self, + SseSerializer serializer, + ); + + @protected + void sse_encode_legacy_signal_pre_key( + LegacySignalPreKey self, + SseSerializer serializer, + ); + @protected void sse_encode_legacy_table_migration_count( LegacyTableMigrationCount self, @@ -529,6 +923,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_legacy_signal_pre_key( + List self, + SseSerializer serializer, + ); + @protected void sse_encode_list_legacy_table_migration_count( List self, @@ -541,6 +941,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_pqc_pre_key_input( + List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_prim_i_64_strict( + Int64List self, + SseSerializer serializer, + ); + @protected void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); @@ -565,15 +977,36 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_opt_String(String? self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_api_connection_state( + ApiConnectionState? self, + SseSerializer serializer, + ); + @protected void sse_encode_opt_box_autoadd_f_64(double? self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_i_64( PlatformInt64? self, SseSerializer serializer, ); + @protected + void sse_encode_opt_box_autoadd_legacy_signal_encrypt_result( + LegacySignalEncryptResult? self, + SseSerializer serializer, + ); + + @protected + void sse_encode_opt_box_autoadd_prepared_outgoing_message( + PreparedOutgoingMessage? self, + SseSerializer serializer, + ); + @protected void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer); @@ -583,6 +1016,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_pqc_pre_key_input( + PqcPreKeyInput self, + SseSerializer serializer, + ); + + @protected + void sse_encode_prepared_outgoing_message( + PreparedOutgoingMessage self, + SseSerializer serializer, + ); + @protected void sse_encode_record_i_64_list_prim_u_8_strict( (PlatformInt64, Uint8List) self, @@ -601,6 +1046,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_rust_api(RustApi self, SseSerializer serializer); + @protected void sse_encode_rust_app_database( RustAppDatabase self, @@ -631,6 +1079,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_rust_utils(RustUtils self, SseSerializer serializer); + @protected + void sse_encode_server_result_empty( + ServerResultEmpty self, + SseSerializer serializer, + ); + + @protected + void sse_encode_server_result_i_64( + ServerResultI64 self, + SseSerializer serializer, + ); + + @protected + void sse_encode_server_result_vec_u_8( + ServerResultVecU8 self, + SseSerializer serializer, + ); + @protected void sse_encode_sql_execution_result( SqlExecutionResult self, @@ -649,6 +1115,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_u_32(int self, SseSerializer serializer); + @protected + void sse_encode_u_64(BigInt self, SseSerializer serializer); + @protected void sse_encode_u_8(int self, SseSerializer serializer); @@ -660,9 +1129,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_usize(BigInt self, SseSerializer serializer); - - @protected - void sse_encode_i_32(int self, SseSerializer serializer); } // Section: wire_class diff --git a/lib/main.dart b/lib/main.dart index a73ce59a..5a98ad37 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -67,7 +67,9 @@ Future twonlyMinimumInitialization() async { final legacyDatabase = TwonlyDB(NativeDatabase(legacyFile)); // Opening the database applies every existing Drift migration up // to v25 before Rust copies the application tables. - await legacyDatabase.customSelect('PRAGMA user_version').getSingle(); + await legacyDatabase + .customSelect('PRAGMA user_version') + .getSingle(); await legacyDatabase.close(); } await RustAppDatabase.migrateLegacyDatabase(); diff --git a/lib/src/callbacks/callbacks.dart b/lib/src/callbacks/callbacks.dart index 46432540..29b237ba 100644 --- a/lib/src/callbacks/callbacks.dart +++ b/lib/src/callbacks/callbacks.dart @@ -1,10 +1,125 @@ +import 'dart:typed_data'; + +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:twonly/core/bridge/callbacks.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/callbacks/legacy_signal.callbacks.dart'; import 'package:twonly/src/callbacks/logging.callbacks.dart'; +import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' + as pb; +import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart' + as push_pb; +import 'package:twonly/src/services/api/client2client/errors.c2c.dart'; +import 'package:twonly/src/services/api/mediafiles/download.api.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; +import 'package:twonly/src/services/flame.service.dart'; +import 'package:twonly/src/services/group.service.dart'; +import 'package:twonly/src/services/key_verification.service.dart'; +import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; +import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; +import 'package:twonly/src/services/passwordless_recovery.service.dart'; +import 'package:twonly/src/services/signal/session.signal.dart'; +import 'package:twonly/src/utils/avatars.dart'; + +Future _apiCreatePushData( + int contactId, + String? messageId, + Uint8List plaintext, + int messageType, +) async { + final notification = messageType == pb.Message_Type.TEST_NOTIFICATION.value + ? push_pb.PushNotification(kind: push_pb.PushKind.TEST_NOTIFICATION) + : await getPushNotificationFromEncryptedContent( + contactId, + messageId, + pb.EncryptedContent.fromBuffer(plaintext), + ); + if (notification == null) return null; + return encryptPushNotification(contactId, notification); +} + +Future _apiMediaAction( + String kind, + String mediaId, + int contactId, + String messageId, +) async { + if (kind == 'response') { + await handleMediaRelatedResponseFromReceiver(messageId); + return; + } + if (kind == 'delete') { + final media = await MediaFileService.fromMediaId(mediaId); + media?.fullMediaRemoval(); + return; + } + final media = await twonlyDB.mediaFilesDao.getMediaFileById(mediaId); + if (media == null) return; + switch (kind) { + case 'download': + await startDownloadMedia(media, false); + case 'stored': + await MediaFileService(media).storeMediaFile(); + case 'reupload': + await reuploadMediaFile(contactId, media, messageId); + } +} + +Future _apiGroupStateRefresh(String groupId, bool created) async { + if (created) { + await fetchGroupStatesForUnjoinedGroups(); + final group = await twonlyDB.groupsDao.getGroup(groupId); + if (group?.myGroupPrivateKey == null) return; + final key = IdentityKeyPair.fromSerialized(group!.myGroupPrivateKey!); + await sendCipherTextToGroup( + groupId, + pb.EncryptedContent( + groupJoin: pb.EncryptedContent_GroupJoin( + groupPublicKey: key.getPublicKey().serialize(), + ), + ), + ); + return; + } + final group = await twonlyDB.groupsDao.getGroup(groupId); + if (group != null) await fetchGroupState(group); +} Future initFlutterCallbacksForRust() async { await initFlutterCallbacks( callbackId: isolateCallbackId, loggingGetStreamSink: LoggingCallbacks.getStreamSink, + legacySignalDecrypt: LegacySignalCallbacks.decrypt, + legacySignalEncrypt: LegacySignalCallbacks.encrypt, + legacySignalGeneratePrekeys: LegacySignalCallbacks.generatePrekeys, + apiResyncSignalSession: handleSessionResync, + apiPushKeyRequested: (contactId) => + setupNotificationWithUsers(forceContact: contactId), + apiGroupMembershipError: (contactId, groupId, relatedReceiptId) => + handleErrorMessage( + contactId, + pb.EncryptedContent_ErrorMessages( + type: pb + .EncryptedContent_ErrorMessages_Type + .GROUP_NOT_FOUND_OR_NOT_A_MEMBER, + relatedReceiptId: relatedReceiptId, + ), + relatedReceiptId, + groupId: groupId, + ), + apiMediaAction: _apiMediaAction, + apiVerificationProof: KeyVerificationService.handleVerificationProof, + apiCreatePushData: _apiCreatePushData, + apiCreatePushAvatars: (contactId) => + createPushAvatars(forceForUserId: contactId), + apiRecoveryChanged: PasswordlessRecoveryService.performHeartbeat, + apiMediaReceived: (groupId, timestamp) => incFlameCounter( + groupId, + true, + DateTime.fromMillisecondsSinceEpoch(timestamp), + ), + apiGroupStateRefresh: _apiGroupStateRefresh, ); } diff --git a/lib/src/callbacks/legacy_signal.callbacks.dart b/lib/src/callbacks/legacy_signal.callbacks.dart new file mode 100644 index 00000000..63a12887 --- /dev/null +++ b/lib/src/callbacks/legacy_signal.callbacks.dart @@ -0,0 +1,87 @@ +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:twonly/core/bridge/callbacks.dart'; +import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' + as pb; +import 'package:twonly/src/services/signal/encryption.signal.dart'; +import 'package:twonly/src/services/signal/identity.signal.dart'; +import 'package:twonly/src/utils/log.dart'; + +/// Flutter boundary for the legacy libsignal_protocol_dart implementation. +/// +/// Only V1 CIPHERTEXT and PREKEY_BUNDLE messages may cross this boundary. +/// CIPHERTEXT_V2 remains fully Rust-owned. +abstract final class LegacySignalCallbacks { + static Future decrypt( + PlatformInt64 fromUserId, + Uint8List ciphertext, + int messageType, + ) async { + if (!_isLegacyMessageType(messageType)) { + return const LegacySignalDecryptResult( + decryptionErrorType: 0, + ); + } + + try { + final (content, errorType) = await signalDecryptMessageV1( + fromUserId, + ciphertext, + messageType, + ); + return LegacySignalDecryptResult( + plaintext: content == null + ? null + : Uint8List.fromList(content.writeToBuffer()), + decryptionErrorType: errorType?.value, + ); + } catch (error) { + Log.error('Legacy Signal decryption callback failed: $error'); + return const LegacySignalDecryptResult(decryptionErrorType: 0); + } + } + + static Future encrypt( + PlatformInt64 targetUserId, + Uint8List plaintext, + ) async { + try { + final encrypted = await signalEncryptMessage( + targetUserId, + plaintext, + ); + if (encrypted == null || !_isLegacyMessageType(encrypted.type.value)) { + return null; + } + return LegacySignalEncryptResult( + ciphertext: encrypted.ciphertext, + messageType: encrypted.type.value, + ); + } catch (error) { + Log.error('Legacy Signal encryption callback failed: $error'); + return null; + } + } + + static Future> generatePrekeys() async { + try { + final prekeys = await signalGetPreKeys(); + return prekeys + .map( + (prekey) => LegacySignalPreKey( + id: prekey.id, + publicKey: Uint8List.fromList( + prekey.getKeyPair().publicKey.serialize(), + ), + ), + ) + .toList(growable: false); + } catch (error) { + Log.error('Legacy Signal prekey callback failed: $error'); + return const []; + } + } + + static bool _isLegacyMessageType(int messageType) => + messageType == pb.Message_Type.CIPHERTEXT.value || + messageType == pb.Message_Type.PREKEY_BUNDLE.value; +} diff --git a/lib/src/callbacks/logging.callbacks.dart b/lib/src/callbacks/logging.callbacks.dart index 3426c5df..6b1a9c67 100644 --- a/lib/src/callbacks/logging.callbacks.dart +++ b/lib/src/callbacks/logging.callbacks.dart @@ -16,7 +16,8 @@ class LoggingCallbacks { Log.info(log.split('INFO ')[1]); } else if (log.contains('DEBUG ')) { Log.info(log.split('DEBUG ')[1]); - } else if (kDebugMode && !Platform.environment.containsKey('FLUTTER_TEST')) { + } else if (kDebugMode && + !Platform.environment.containsKey('FLUTTER_TEST')) { // ignore: avoid_print print(log); } diff --git a/lib/src/channels/video_compression.channel.dart b/lib/src/channels/video_compression.channel.dart index 58790b59..c3dcbb35 100644 --- a/lib/src/channels/video_compression.channel.dart +++ b/lib/src/channels/video_compression.channel.dart @@ -2,8 +2,9 @@ import 'package:flutter/services.dart'; import 'package:twonly/src/utils/log.dart'; abstract class VideoCompressionChannel { - static const MethodChannel _channel = - MethodChannel('eu.twonly/videoCompression'); + static const MethodChannel _channel = MethodChannel( + 'eu.twonly/videoCompression', + ); static void Function(double)? _currentProgressCallback; static bool _handlerSetup = false; diff --git a/lib/src/database/drift_logging_interceptor.dart b/lib/src/database/drift_logging_interceptor.dart index 63ff94eb..3164eb5c 100644 --- a/lib/src/database/drift_logging_interceptor.dart +++ b/lib/src/database/drift_logging_interceptor.dart @@ -86,7 +86,12 @@ class DriftLoggingInterceptor extends QueryInterceptor { String statement, List args, ) { - return _run('INSERT', statement, args, () => executor.runInsert(statement, args)); + return _run( + 'INSERT', + statement, + args, + () => executor.runInsert(statement, args), + ); } @override @@ -95,7 +100,12 @@ class DriftLoggingInterceptor extends QueryInterceptor { String statement, List args, ) { - return _run('UPDATE', statement, args, () => executor.runUpdate(statement, args)); + return _run( + 'UPDATE', + statement, + args, + () => executor.runUpdate(statement, args), + ); } @override @@ -104,7 +114,12 @@ class DriftLoggingInterceptor extends QueryInterceptor { String statement, List args, ) { - return _run('DELETE', statement, args, () => executor.runDelete(statement, args)); + return _run( + 'DELETE', + statement, + args, + () => executor.runDelete(statement, args), + ); } @override @@ -113,7 +128,12 @@ class DriftLoggingInterceptor extends QueryInterceptor { String statement, List args, ) { - return _run('CUSTOM', statement, args, () => executor.runCustom(statement, args)); + return _run( + 'CUSTOM', + statement, + args, + () => executor.runCustom(statement, args), + ); } @override diff --git a/lib/src/database/rust_query_executor.dart b/lib/src/database/rust_query_executor.dart index e9d35f00..fe210906 100644 --- a/lib/src/database/rust_query_executor.dart +++ b/lib/src/database/rust_query_executor.dart @@ -8,9 +8,9 @@ import 'package:twonly/core/database/app.dart' as rust; /// This keeps the existing generated models, DAO methods and query watches /// while moving connection, encryption and storage ownership into Rust. QueryExecutor openRustAppDatabase() => DelegatedDatabase( - _RustDatabaseDelegate(), - isSequential: true, - ); + _RustDatabaseDelegate(), + isSequential: true, +); class _RustDatabaseDelegate extends DatabaseDelegate { @override diff --git a/lib/src/database/tables/contacts.table.dart b/lib/src/database/tables/contacts.table.dart index 820ba69d..0dda2ddb 100644 --- a/lib/src/database/tables/contacts.table.dart +++ b/lib/src/database/tables/contacts.table.dart @@ -1,6 +1,5 @@ import 'package:drift/drift.dart'; - enum SignalVersion { v1, v2 } @DataClassName('Contact') diff --git a/lib/src/database/tables/labels.table.dart b/lib/src/database/tables/labels.table.dart index 23a02fe5..4824de07 100644 --- a/lib/src/database/tables/labels.table.dart +++ b/lib/src/database/tables/labels.table.dart @@ -13,15 +13,15 @@ class Labels extends Table { @DataClassName('ContactLabel') class ContactLabels extends Table { IntColumn get contactId => integer().references( - Contacts, - #userId, - onDelete: KeyAction.cascade, - )(); + Contacts, + #userId, + onDelete: KeyAction.cascade, + )(); IntColumn get labelId => integer().references( - Labels, - #id, - onDelete: KeyAction.cascade, - )(); + Labels, + #id, + onDelete: KeyAction.cascade, + )(); @override Set get primaryKey => {contactId, labelId}; diff --git a/lib/src/database/tables/messages.table.dart b/lib/src/database/tables/messages.table.dart index 32cb4ea2..9729bcca 100644 --- a/lib/src/database/tables/messages.table.dart +++ b/lib/src/database/tables/messages.table.dart @@ -6,7 +6,10 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart'; enum MessageType { media, text, contacts, restoreFlameCounter, askAboutUser } @DataClassName('Message') -@TableIndex(name: 'idx_messages_group_id_created_at', columns: {#groupId, #createdAt}) +@TableIndex( + name: 'idx_messages_group_id_created_at', + columns: {#groupId, #createdAt}, +) class Messages extends Table { TextColumn get groupId => text().references(Groups, #groupId, onDelete: KeyAction.cascade)(); diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index b2decaaa..86d82cb6 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -12704,7 +12704,7 @@ final class $$ContactsTableReferences _$TwonlyDB db, ) => MultiTypedResultKey.fromTable( db.messages, - aliasName: $_aliasNameGenerator(db.contacts.userId, db.messages.senderId), + aliasName: 'contacts__user_id__messages__sender_id', ); $$MessagesTableProcessedTableManager get messagesRefs { @@ -12722,10 +12722,7 @@ final class $$ContactsTableReferences static MultiTypedResultKey<$MessageHistoriesTable, List> _messageHistoriesRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.messageHistories, - aliasName: $_aliasNameGenerator( - db.contacts.userId, - db.messageHistories.contactId, - ), + aliasName: 'contacts__user_id__message_histories__contact_id', ); $$MessageHistoriesTableProcessedTableManager get messageHistoriesRefs { @@ -12745,7 +12742,7 @@ final class $$ContactsTableReferences static MultiTypedResultKey<$ReactionsTable, List> _reactionsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.reactions, - aliasName: $_aliasNameGenerator(db.contacts.userId, db.reactions.senderId), + aliasName: 'contacts__user_id__reactions__sender_id', ); $$ReactionsTableProcessedTableManager get reactionsRefs { @@ -12763,10 +12760,7 @@ final class $$ContactsTableReferences static MultiTypedResultKey<$GroupMembersTable, List> _groupMembersRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.groupMembers, - aliasName: $_aliasNameGenerator( - db.contacts.userId, - db.groupMembers.contactId, - ), + aliasName: 'contacts__user_id__group_members__contact_id', ); $$GroupMembersTableProcessedTableManager get groupMembersRefs { @@ -12785,7 +12779,7 @@ final class $$ContactsTableReferences _$TwonlyDB db, ) => MultiTypedResultKey.fromTable( db.receipts, - aliasName: $_aliasNameGenerator(db.contacts.userId, db.receipts.contactId), + aliasName: 'contacts__user_id__receipts__contact_id', ); $$ReceiptsTableProcessedTableManager get receiptsRefs { @@ -12802,10 +12796,7 @@ final class $$ContactsTableReferences static MultiTypedResultKey<$MessageActionsTable, List> _messageActionsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.messageActions, - aliasName: $_aliasNameGenerator( - db.contacts.userId, - db.messageActions.contactId, - ), + aliasName: 'contacts__user_id__message_actions__contact_id', ); $$MessageActionsTableProcessedTableManager get messageActionsRefs { @@ -12823,10 +12814,7 @@ final class $$ContactsTableReferences static MultiTypedResultKey<$GroupHistoriesTable, List> _groupHistoriesRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.groupHistories, - aliasName: $_aliasNameGenerator( - db.contacts.userId, - db.groupHistories.contactId, - ), + aliasName: 'contacts__user_id__group_histories__contact_id', ); $$GroupHistoriesTableProcessedTableManager get groupHistoriesRefs { @@ -12848,10 +12836,8 @@ final class $$ContactsTableReferences _userDiscoveryUserRelationsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.userDiscoveryUserRelations, - aliasName: $_aliasNameGenerator( - db.contacts.userId, - db.userDiscoveryUserRelations.fromContactId, - ), + aliasName: + 'contacts__user_id__user_discovery_user_relations__from_contact_id', ); $$UserDiscoveryUserRelationsTableProcessedTableManager @@ -12877,14 +12863,13 @@ final class $$ContactsTableReferences $UserDiscoveryOtherPromotionsTable, List > - _userDiscoveryOtherPromotionsRefsTable(_$TwonlyDB db) => - MultiTypedResultKey.fromTable( - db.userDiscoveryOtherPromotions, - aliasName: $_aliasNameGenerator( - db.contacts.userId, - db.userDiscoveryOtherPromotions.fromContactId, - ), - ); + _userDiscoveryOtherPromotionsRefsTable( + _$TwonlyDB db, + ) => MultiTypedResultKey.fromTable( + db.userDiscoveryOtherPromotions, + aliasName: + 'contacts__user_id__user_discovery_other_promotions__from_contact_id', + ); $$UserDiscoveryOtherPromotionsTableProcessedTableManager get userDiscoveryOtherPromotionsRefs { @@ -12912,10 +12897,8 @@ final class $$ContactsTableReferences _userDiscoveryOwnPromotionsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.userDiscoveryOwnPromotions, - aliasName: $_aliasNameGenerator( - db.contacts.userId, - db.userDiscoveryOwnPromotions.contactId, - ), + aliasName: + 'contacts__user_id__user_discovery_own_promotions__contact_id', ); $$UserDiscoveryOwnPromotionsTableProcessedTableManager @@ -12942,10 +12925,7 @@ final class $$ContactsTableReferences > _userDiscoverySharesRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.userDiscoveryShares, - aliasName: $_aliasNameGenerator( - db.contacts.userId, - db.userDiscoveryShares.contactId, - ), + aliasName: 'contacts__user_id__user_discovery_shares__contact_id', ); $$UserDiscoverySharesTableProcessedTableManager get userDiscoverySharesRefs { @@ -12968,10 +12948,7 @@ final class $$ContactsTableReferences static MultiTypedResultKey<$ContactLabelsTable, List> _contactLabelsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.contactLabels, - aliasName: $_aliasNameGenerator( - db.contacts.userId, - db.contactLabels.contactId, - ), + aliasName: 'contacts__user_id__contact_labels__contact_id', ); $$ContactLabelsTableProcessedTableManager get contactLabelsRefs { @@ -14574,7 +14551,7 @@ final class $$GroupsTableReferences _$TwonlyDB db, ) => MultiTypedResultKey.fromTable( db.messages, - aliasName: $_aliasNameGenerator(db.groups.groupId, db.messages.groupId), + aliasName: 'groups__group_id__messages__group_id', ); $$MessagesTableProcessedTableManager get messagesRefs { @@ -14591,7 +14568,7 @@ final class $$GroupsTableReferences static MultiTypedResultKey<$GroupMembersTable, List> _groupMembersRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.groupMembers, - aliasName: $_aliasNameGenerator(db.groups.groupId, db.groupMembers.groupId), + aliasName: 'groups__group_id__group_members__group_id', ); $$GroupMembersTableProcessedTableManager get groupMembersRefs { @@ -14609,10 +14586,7 @@ final class $$GroupsTableReferences static MultiTypedResultKey<$GroupHistoriesTable, List> _groupHistoriesRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.groupHistories, - aliasName: $_aliasNameGenerator( - db.groups.groupId, - db.groupHistories.groupId, - ), + aliasName: 'groups__group_id__group_histories__group_id', ); $$GroupHistoriesTableProcessedTableManager get groupHistoriesRefs { @@ -14630,10 +14604,7 @@ final class $$GroupsTableReferences static MultiTypedResultKey<$ShortcutMembersTable, List> _shortcutMembersRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.shortcutMembers, - aliasName: $_aliasNameGenerator( - db.groups.groupId, - db.shortcutMembers.groupId, - ), + aliasName: 'groups__group_id__shortcut_members__group_id', ); $$ShortcutMembersTableProcessedTableManager get shortcutMembersRefs { @@ -15587,7 +15558,7 @@ final class $$MediaFilesTableReferences _$TwonlyDB db, ) => MultiTypedResultKey.fromTable( db.messages, - aliasName: $_aliasNameGenerator(db.mediaFiles.mediaId, db.messages.mediaId), + aliasName: 'media_files__media_id__messages__media_id', ); $$MessagesTableProcessedTableManager get messagesRefs { @@ -16282,9 +16253,8 @@ final class $$MessagesTableReferences extends BaseReferences<_$TwonlyDB, $MessagesTable, Message> { $$MessagesTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $GroupsTable _groupIdTable(_$TwonlyDB db) => db.groups.createAlias( - $_aliasNameGenerator(db.messages.groupId, db.groups.groupId), - ); + static $GroupsTable _groupIdTable(_$TwonlyDB db) => + db.groups.createAlias('messages__group_id__groups__group_id'); $$GroupsTableProcessedTableManager get groupId { final $_column = $_itemColumn('group_id')!; @@ -16301,9 +16271,7 @@ final class $$MessagesTableReferences } static $ContactsTable _senderIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator(db.messages.senderId, db.contacts.userId), - ); + db.contacts.createAlias('messages__sender_id__contacts__user_id'); $$ContactsTableProcessedTableManager? get senderId { final $_column = $_itemColumn('sender_id'); @@ -16320,9 +16288,7 @@ final class $$MessagesTableReferences } static $MediaFilesTable _mediaIdTable(_$TwonlyDB db) => - db.mediaFiles.createAlias( - $_aliasNameGenerator(db.messages.mediaId, db.mediaFiles.mediaId), - ); + db.mediaFiles.createAlias('messages__media_id__media_files__media_id'); $$MediaFilesTableProcessedTableManager? get mediaId { final $_column = $_itemColumn('media_id'); @@ -16341,10 +16307,7 @@ final class $$MessagesTableReferences static MultiTypedResultKey<$MessageHistoriesTable, List> _messageHistoriesRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.messageHistories, - aliasName: $_aliasNameGenerator( - db.messages.messageId, - db.messageHistories.messageId, - ), + aliasName: 'messages__message_id__message_histories__message_id', ); $$MessageHistoriesTableProcessedTableManager get messageHistoriesRefs { @@ -16366,10 +16329,7 @@ final class $$MessagesTableReferences static MultiTypedResultKey<$ReactionsTable, List> _reactionsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.reactions, - aliasName: $_aliasNameGenerator( - db.messages.messageId, - db.reactions.messageId, - ), + aliasName: 'messages__message_id__reactions__message_id', ); $$ReactionsTableProcessedTableManager get reactionsRefs { @@ -16388,10 +16348,7 @@ final class $$MessagesTableReferences _$TwonlyDB db, ) => MultiTypedResultKey.fromTable( db.receipts, - aliasName: $_aliasNameGenerator( - db.messages.messageId, - db.receipts.messageId, - ), + aliasName: 'messages__message_id__receipts__message_id', ); $$ReceiptsTableProcessedTableManager get receiptsRefs { @@ -16409,10 +16366,7 @@ final class $$MessagesTableReferences static MultiTypedResultKey<$MessageActionsTable, List> _messageActionsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.messageActions, - aliasName: $_aliasNameGenerator( - db.messages.messageId, - db.messageActions.messageId, - ), + aliasName: 'messages__message_id__message_actions__message_id', ); $$MessageActionsTableProcessedTableManager get messageActionsRefs { @@ -17423,13 +17377,8 @@ final class $$MessageHistoriesTableReferences super.$_typedResult, ); - static $MessagesTable _messageIdTable(_$TwonlyDB db) => - db.messages.createAlias( - $_aliasNameGenerator( - db.messageHistories.messageId, - db.messages.messageId, - ), - ); + static $MessagesTable _messageIdTable(_$TwonlyDB db) => db.messages + .createAlias('message_histories__message_id__messages__message_id'); $$MessagesTableProcessedTableManager get messageId { final $_column = $_itemColumn('message_id')!; @@ -17445,10 +17394,8 @@ final class $$MessageHistoriesTableReferences ); } - static $ContactsTable _contactIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator(db.messageHistories.contactId, db.contacts.userId), - ); + static $ContactsTable _contactIdTable(_$TwonlyDB db) => db.contacts + .createAlias('message_histories__contact_id__contacts__user_id'); $$ContactsTableProcessedTableManager? get contactId { final $_column = $_itemColumn('contact_id'); @@ -17834,9 +17781,7 @@ final class $$ReactionsTableReferences $$ReactionsTableReferences(super.$_db, super.$_table, super.$_typedResult); static $MessagesTable _messageIdTable(_$TwonlyDB db) => - db.messages.createAlias( - $_aliasNameGenerator(db.reactions.messageId, db.messages.messageId), - ); + db.messages.createAlias('reactions__message_id__messages__message_id'); $$MessagesTableProcessedTableManager get messageId { final $_column = $_itemColumn('message_id')!; @@ -17853,9 +17798,7 @@ final class $$ReactionsTableReferences } static $ContactsTable _senderIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator(db.reactions.senderId, db.contacts.userId), - ); + db.contacts.createAlias('reactions__sender_id__contacts__user_id'); $$ContactsTableProcessedTableManager? get senderId { final $_column = $_itemColumn('sender_id'); @@ -18229,9 +18172,8 @@ final class $$GroupMembersTableReferences extends BaseReferences<_$TwonlyDB, $GroupMembersTable, GroupMember> { $$GroupMembersTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $GroupsTable _groupIdTable(_$TwonlyDB db) => db.groups.createAlias( - $_aliasNameGenerator(db.groupMembers.groupId, db.groups.groupId), - ); + static $GroupsTable _groupIdTable(_$TwonlyDB db) => + db.groups.createAlias('group_members__group_id__groups__group_id'); $$GroupsTableProcessedTableManager get groupId { final $_column = $_itemColumn('group_id')!; @@ -18248,9 +18190,7 @@ final class $$GroupMembersTableReferences } static $ContactsTable _contactIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator(db.groupMembers.contactId, db.contacts.userId), - ); + db.contacts.createAlias('group_members__contact_id__contacts__user_id'); $$ContactsTableProcessedTableManager get contactId { final $_column = $_itemColumn('contact_id')!; @@ -18713,9 +18653,7 @@ final class $$ReceiptsTableReferences $$ReceiptsTableReferences(super.$_db, super.$_table, super.$_typedResult); static $ContactsTable _contactIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator(db.receipts.contactId, db.contacts.userId), - ); + db.contacts.createAlias('receipts__contact_id__contacts__user_id'); $$ContactsTableProcessedTableManager get contactId { final $_column = $_itemColumn('contact_id')!; @@ -18732,9 +18670,7 @@ final class $$ReceiptsTableReferences } static $MessagesTable _messageIdTable(_$TwonlyDB db) => - db.messages.createAlias( - $_aliasNameGenerator(db.receipts.messageId, db.messages.messageId), - ); + db.messages.createAlias('receipts__message_id__messages__message_id'); $$MessagesTableProcessedTableManager? get messageId { final $_column = $_itemColumn('message_id'); @@ -19399,13 +19335,8 @@ final class $$MessageActionsTableReferences super.$_typedResult, ); - static $MessagesTable _messageIdTable(_$TwonlyDB db) => - db.messages.createAlias( - $_aliasNameGenerator( - db.messageActions.messageId, - db.messages.messageId, - ), - ); + static $MessagesTable _messageIdTable(_$TwonlyDB db) => db.messages + .createAlias('message_actions__message_id__messages__message_id'); $$MessagesTableProcessedTableManager get messageId { final $_column = $_itemColumn('message_id')!; @@ -19422,9 +19353,7 @@ final class $$MessageActionsTableReferences } static $ContactsTable _contactIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator(db.messageActions.contactId, db.contacts.userId), - ); + db.contacts.createAlias('message_actions__contact_id__contacts__user_id'); $$ContactsTableProcessedTableManager get contactId { final $_column = $_itemColumn('contact_id')!; @@ -19807,9 +19736,8 @@ final class $$GroupHistoriesTableReferences super.$_typedResult, ); - static $GroupsTable _groupIdTable(_$TwonlyDB db) => db.groups.createAlias( - $_aliasNameGenerator(db.groupHistories.groupId, db.groups.groupId), - ); + static $GroupsTable _groupIdTable(_$TwonlyDB db) => + db.groups.createAlias('group_histories__group_id__groups__group_id'); $$GroupsTableProcessedTableManager get groupId { final $_column = $_itemColumn('group_id')!; @@ -19826,9 +19754,7 @@ final class $$GroupHistoriesTableReferences } static $ContactsTable _contactIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator(db.groupHistories.contactId, db.contacts.userId), - ); + db.contacts.createAlias('group_histories__contact_id__contacts__user_id'); $$ContactsTableProcessedTableManager? get contactId { final $_column = $_itemColumn('contact_id'); @@ -20304,10 +20230,8 @@ final class $$KeyVerificationsTableReferences super.$_typedResult, ); - static $ContactsTable _contactIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator(db.keyVerifications.contactId, db.contacts.userId), - ); + static $ContactsTable _contactIdTable(_$TwonlyDB db) => db.contacts + .createAlias('key_verifications__contact_id__contacts__user_id'); $$ContactsTableProcessedTableManager get contactId { final $_column = $_itemColumn('contact_id')!; @@ -20323,13 +20247,8 @@ final class $$KeyVerificationsTableReferences ); } - static $ContactsTable _verifiedByTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator( - db.keyVerifications.verifiedBy, - db.contacts.userId, - ), - ); + static $ContactsTable _verifiedByTable(_$TwonlyDB db) => db.contacts + .createAlias('key_verifications__verified_by__contacts__user_id'); $$ContactsTableProcessedTableManager? get verifiedBy { final $_column = $_itemColumn('verified_by'); @@ -20899,14 +20818,13 @@ final class $$UserDiscoveryAnnouncedUsersTableReferences $UserDiscoveryUserRelationsTable, List > - _userDiscoveryUserRelationsRefsTable(_$TwonlyDB db) => - MultiTypedResultKey.fromTable( - db.userDiscoveryUserRelations, - aliasName: $_aliasNameGenerator( - db.userDiscoveryAnnouncedUsers.announcedUserId, - db.userDiscoveryUserRelations.announcedUserId, - ), - ); + _userDiscoveryUserRelationsRefsTable( + _$TwonlyDB db, + ) => MultiTypedResultKey.fromTable( + db.userDiscoveryUserRelations, + aliasName: + 'user_discovery_announced_users__announced_user_id__user_discovery_user_relations__announced_user_id', + ); $$UserDiscoveryUserRelationsTableProcessedTableManager get userDiscoveryUserRelationsRefs { @@ -21286,10 +21204,7 @@ final class $$UserDiscoveryUserRelationsTableReferences static $UserDiscoveryAnnouncedUsersTable _announcedUserIdTable( _$TwonlyDB db, ) => db.userDiscoveryAnnouncedUsers.createAlias( - $_aliasNameGenerator( - db.userDiscoveryUserRelations.announcedUserId, - db.userDiscoveryAnnouncedUsers.announcedUserId, - ), + 'user_discovery_user_relations__announced_user_id__user_discovery_announced_users__announced_user_id', ); $$UserDiscoveryAnnouncedUsersTableProcessedTableManager get announcedUserId { @@ -21308,10 +21223,7 @@ final class $$UserDiscoveryUserRelationsTableReferences static $ContactsTable _fromContactIdTable(_$TwonlyDB db) => db.contacts.createAlias( - $_aliasNameGenerator( - db.userDiscoveryUserRelations.fromContactId, - db.contacts.userId, - ), + 'user_discovery_user_relations__from_contact_id__contacts__user_id', ); $$ContactsTableProcessedTableManager get fromContactId { @@ -21703,10 +21615,7 @@ final class $$UserDiscoveryOtherPromotionsTableReferences static $ContactsTable _fromContactIdTable(_$TwonlyDB db) => db.contacts.createAlias( - $_aliasNameGenerator( - db.userDiscoveryOtherPromotions.fromContactId, - db.contacts.userId, - ), + 'user_discovery_other_promotions__from_contact_id__contacts__user_id', ); $$ContactsTableProcessedTableManager get fromContactId { @@ -22074,10 +21983,7 @@ final class $$UserDiscoveryOwnPromotionsTableReferences static $ContactsTable _contactIdTable(_$TwonlyDB db) => db.contacts.createAlias( - $_aliasNameGenerator( - db.userDiscoveryOwnPromotions.contactId, - db.contacts.userId, - ), + 'user_discovery_own_promotions__contact_id__contacts__user_id', ); $$ContactsTableProcessedTableManager get contactId { @@ -22375,13 +22281,8 @@ final class $$UserDiscoverySharesTableReferences super.$_typedResult, ); - static $ContactsTable _contactIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator( - db.userDiscoveryShares.contactId, - db.contacts.userId, - ), - ); + static $ContactsTable _contactIdTable(_$TwonlyDB db) => db.contacts + .createAlias('user_discovery_shares__contact_id__contacts__user_id'); $$ContactsTableProcessedTableManager? get contactId { final $_column = $_itemColumn('contact_id'); @@ -22666,10 +22567,7 @@ final class $$ShortcutsTableReferences static MultiTypedResultKey<$ShortcutMembersTable, List> _shortcutMembersRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.shortcutMembers, - aliasName: $_aliasNameGenerator( - db.shortcuts.id, - db.shortcutMembers.shortcutId, - ), + aliasName: 'shortcuts__id__shortcut_members__shortcut_id', ); $$ShortcutMembersTableProcessedTableManager get shortcutMembersRefs { @@ -22934,9 +22832,7 @@ final class $$ShortcutMembersTableReferences ); static $ShortcutsTable _shortcutIdTable(_$TwonlyDB db) => - db.shortcuts.createAlias( - $_aliasNameGenerator(db.shortcutMembers.shortcutId, db.shortcuts.id), - ); + db.shortcuts.createAlias('shortcut_members__shortcut_id__shortcuts__id'); $$ShortcutsTableProcessedTableManager get shortcutId { final $_column = $_itemColumn('shortcut_id')!; @@ -22952,9 +22848,8 @@ final class $$ShortcutMembersTableReferences ); } - static $GroupsTable _groupIdTable(_$TwonlyDB db) => db.groups.createAlias( - $_aliasNameGenerator(db.shortcutMembers.groupId, db.groups.groupId), - ); + static $GroupsTable _groupIdTable(_$TwonlyDB db) => + db.groups.createAlias('shortcut_members__group_id__groups__group_id'); $$GroupsTableProcessedTableManager get groupId { final $_column = $_itemColumn('group_id')!; @@ -23293,7 +23188,7 @@ final class $$LabelsTableReferences static MultiTypedResultKey<$ContactLabelsTable, List> _contactLabelsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.contactLabels, - aliasName: $_aliasNameGenerator(db.labels.id, db.contactLabels.labelId), + aliasName: 'labels__id__contact_labels__label_id', ); $$ContactLabelsTableProcessedTableManager get contactLabelsRefs { @@ -23585,9 +23480,7 @@ final class $$ContactLabelsTableReferences ); static $ContactsTable _contactIdTable(_$TwonlyDB db) => - db.contacts.createAlias( - $_aliasNameGenerator(db.contactLabels.contactId, db.contacts.userId), - ); + db.contacts.createAlias('contact_labels__contact_id__contacts__user_id'); $$ContactsTableProcessedTableManager get contactId { final $_column = $_itemColumn('contact_id')!; @@ -23603,9 +23496,8 @@ final class $$ContactLabelsTableReferences ); } - static $LabelsTable _labelIdTable(_$TwonlyDB db) => db.labels.createAlias( - $_aliasNameGenerator(db.contactLabels.labelId, db.labels.id), - ); + static $LabelsTable _labelIdTable(_$TwonlyDB db) => + db.labels.createAlias('contact_labels__label_id__labels__id'); $$LabelsTableProcessedTableManager get labelId { final $_column = $_itemColumn('label_id')!; diff --git a/lib/src/model/protobuf/api/http/http_requests.pb.dart b/lib/src/model/protobuf/api/http/http_requests.pb.dart index c2283af1..c5ecea1f 100644 --- a/lib/src/model/protobuf/api/http/http_requests.pb.dart +++ b/lib/src/model/protobuf/api/http/http_requests.pb.dart @@ -167,6 +167,146 @@ class UploadRequest extends $pb.GeneratedMessage { $pb.PbList get messagesOnSuccess => $_getList(2); } +class SealedSenderMessageRequest extends $pb.GeneratedMessage { + factory SealedSenderMessageRequest({ + $fixnum.Int64? recipientUserId, + $core.List<$core.int>? sealedSenderMessage, + $core.List<$core.int>? privacyPassToken, + }) { + final result = create(); + if (recipientUserId != null) result.recipientUserId = recipientUserId; + if (sealedSenderMessage != null) + result.sealedSenderMessage = sealedSenderMessage; + if (privacyPassToken != null) result.privacyPassToken = privacyPassToken; + return result; + } + + SealedSenderMessageRequest._(); + + factory SealedSenderMessageRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SealedSenderMessageRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SealedSenderMessageRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), + createEmptyInstance: create) + ..aInt64(1, _omitFieldNames ? '' : 'recipientUserId') + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'sealedSenderMessage', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 3, _omitFieldNames ? '' : 'privacyPassToken', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SealedSenderMessageRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SealedSenderMessageRequest copyWith( + void Function(SealedSenderMessageRequest) updates) => + super.copyWith( + (message) => updates(message as SealedSenderMessageRequest)) + as SealedSenderMessageRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SealedSenderMessageRequest create() => SealedSenderMessageRequest._(); + @$core.override + SealedSenderMessageRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SealedSenderMessageRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SealedSenderMessageRequest? _defaultInstance; + + @$pb.TagNumber(1) + $fixnum.Int64 get recipientUserId => $_getI64(0); + @$pb.TagNumber(1) + set recipientUserId($fixnum.Int64 value) => $_setInt64(0, value); + @$pb.TagNumber(1) + $core.bool hasRecipientUserId() => $_has(0); + @$pb.TagNumber(1) + void clearRecipientUserId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get sealedSenderMessage => $_getN(1); + @$pb.TagNumber(2) + set sealedSenderMessage($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasSealedSenderMessage() => $_has(1); + @$pb.TagNumber(2) + void clearSealedSenderMessage() => $_clearField(2); + + @$pb.TagNumber(3) + $core.List<$core.int> get privacyPassToken => $_getN(2); + @$pb.TagNumber(3) + set privacyPassToken($core.List<$core.int> value) => $_setBytes(2, value); + @$pb.TagNumber(3) + $core.bool hasPrivacyPassToken() => $_has(2); + @$pb.TagNumber(3) + void clearPrivacyPassToken() => $_clearField(3); +} + +class SealedSenderMessageResponse extends $pb.GeneratedMessage { + factory SealedSenderMessageResponse({ + $core.String? messageId, + }) { + final result = create(); + if (messageId != null) result.messageId = messageId; + return result; + } + + SealedSenderMessageResponse._(); + + factory SealedSenderMessageResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SealedSenderMessageResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SealedSenderMessageResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'messageId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SealedSenderMessageResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SealedSenderMessageResponse copyWith( + void Function(SealedSenderMessageResponse) updates) => + super.copyWith( + (message) => updates(message as SealedSenderMessageResponse)) + as SealedSenderMessageResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SealedSenderMessageResponse create() => + SealedSenderMessageResponse._(); + @$core.override + SealedSenderMessageResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SealedSenderMessageResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SealedSenderMessageResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get messageId => $_getSZ(0); + @$pb.TagNumber(1) + set messageId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasMessageId() => $_has(0); + @$pb.TagNumber(1) + void clearMessageId() => $_clearField(1); +} + class UpdateGroupState_UpdateTBS extends $pb.GeneratedMessage { factory UpdateGroupState_UpdateTBS({ $fixnum.Int64? versionId, diff --git a/lib/src/model/protobuf/api/http/http_requests.pbjson.dart b/lib/src/model/protobuf/api/http/http_requests.pbjson.dart index bc6c1336..6319dd38 100644 --- a/lib/src/model/protobuf/api/http/http_requests.pbjson.dart +++ b/lib/src/model/protobuf/api/http/http_requests.pbjson.dart @@ -65,6 +65,52 @@ final $typed_data.Uint8List uploadRequestDescriptor = $convert.base64Decode( 'c3VjY2VzcxgDIAMoCzIaLmh0dHBfcmVxdWVzdHMuVGV4dE1lc3NhZ2VSEW1lc3NhZ2VzT25TdW' 'NjZXNz'); +@$core.Deprecated('Use sealedSenderMessageRequestDescriptor instead') +const SealedSenderMessageRequest$json = { + '1': 'SealedSenderMessageRequest', + '2': [ + {'1': 'recipient_user_id', '3': 1, '4': 1, '5': 3, '10': 'recipientUserId'}, + { + '1': 'sealed_sender_message', + '3': 2, + '4': 1, + '5': 12, + '10': 'sealedSenderMessage' + }, + { + '1': 'privacy_pass_token', + '3': 3, + '4': 1, + '5': 12, + '10': 'privacyPassToken' + }, + ], + '9': [ + {'1': 4, '2': 5}, + ], +}; + +/// Descriptor for `SealedSenderMessageRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sealedSenderMessageRequestDescriptor = $convert.base64Decode( + 'ChpTZWFsZWRTZW5kZXJNZXNzYWdlUmVxdWVzdBIqChFyZWNpcGllbnRfdXNlcl9pZBgBIAEoA1' + 'IPcmVjaXBpZW50VXNlcklkEjIKFXNlYWxlZF9zZW5kZXJfbWVzc2FnZRgCIAEoDFITc2VhbGVk' + 'U2VuZGVyTWVzc2FnZRIsChJwcml2YWN5X3Bhc3NfdG9rZW4YAyABKAxSEHByaXZhY3lQYXNzVG' + '9rZW5KBAgEEAU='); + +@$core.Deprecated('Use sealedSenderMessageResponseDescriptor instead') +const SealedSenderMessageResponse$json = { + '1': 'SealedSenderMessageResponse', + '2': [ + {'1': 'message_id', '3': 1, '4': 1, '5': 9, '10': 'messageId'}, + ], +}; + +/// Descriptor for `SealedSenderMessageResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sealedSenderMessageResponseDescriptor = + $convert.base64Decode( + 'ChtTZWFsZWRTZW5kZXJNZXNzYWdlUmVzcG9uc2USHQoKbWVzc2FnZV9pZBgBIAEoCVIJbWVzc2' + 'FnZUlk'); + @$core.Deprecated('Use updateGroupStateDescriptor instead') const UpdateGroupState$json = { '1': 'UpdateGroupState', diff --git a/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart b/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart index 51fb7fc9..5ad0fa3d 100644 --- a/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart +++ b/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart @@ -3301,6 +3301,105 @@ class ApplicationData_DisableMemoriesBackup extends $pb.GeneratedMessage { static ApplicationData_DisableMemoriesBackup? _defaultInstance; } +class ApplicationData_GetPrivacyPassParameters extends $pb.GeneratedMessage { + factory ApplicationData_GetPrivacyPassParameters() => create(); + + ApplicationData_GetPrivacyPassParameters._(); + + factory ApplicationData_GetPrivacyPassParameters.fromBuffer( + $core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ApplicationData_GetPrivacyPassParameters.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ApplicationData.GetPrivacyPassParameters', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ApplicationData_GetPrivacyPassParameters clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ApplicationData_GetPrivacyPassParameters copyWith( + void Function(ApplicationData_GetPrivacyPassParameters) updates) => + super.copyWith((message) => + updates(message as ApplicationData_GetPrivacyPassParameters)) + as ApplicationData_GetPrivacyPassParameters; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ApplicationData_GetPrivacyPassParameters create() => + ApplicationData_GetPrivacyPassParameters._(); + @$core.override + ApplicationData_GetPrivacyPassParameters createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ApplicationData_GetPrivacyPassParameters getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor< + ApplicationData_GetPrivacyPassParameters>(create); + static ApplicationData_GetPrivacyPassParameters? _defaultInstance; +} + +class ApplicationData_IssuePrivacyPassTokens extends $pb.GeneratedMessage { + factory ApplicationData_IssuePrivacyPassTokens({ + $core.Iterable<$core.List<$core.int>>? tokenRequests, + }) { + final result = create(); + if (tokenRequests != null) result.tokenRequests.addAll(tokenRequests); + return result; + } + + ApplicationData_IssuePrivacyPassTokens._(); + + factory ApplicationData_IssuePrivacyPassTokens.fromBuffer( + $core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ApplicationData_IssuePrivacyPassTokens.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ApplicationData.IssuePrivacyPassTokens', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), + createEmptyInstance: create) + ..p<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'tokenRequests', $pb.PbFieldType.PY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ApplicationData_IssuePrivacyPassTokens clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ApplicationData_IssuePrivacyPassTokens copyWith( + void Function(ApplicationData_IssuePrivacyPassTokens) updates) => + super.copyWith((message) => + updates(message as ApplicationData_IssuePrivacyPassTokens)) + as ApplicationData_IssuePrivacyPassTokens; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ApplicationData_IssuePrivacyPassTokens create() => + ApplicationData_IssuePrivacyPassTokens._(); + @$core.override + ApplicationData_IssuePrivacyPassTokens createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ApplicationData_IssuePrivacyPassTokens getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor< + ApplicationData_IssuePrivacyPassTokens>(create); + static ApplicationData_IssuePrivacyPassTokens? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList<$core.List<$core.int>> get tokenRequests => $_getList(0); +} + enum ApplicationData_ApplicationData { textMessage, getUserByUsername, @@ -3330,6 +3429,8 @@ enum ApplicationData_ApplicationData { deleteMemory, disableMemoriesBackup, uploadPqcPrekeys, + getPrivacyPassParameters, + issuePrivacyPassTokens, notSet } @@ -3363,6 +3464,8 @@ class ApplicationData extends $pb.GeneratedMessage { ApplicationData_DeleteMemory? deleteMemory, ApplicationData_DisableMemoriesBackup? disableMemoriesBackup, ApplicationData_UploadPqcPreKeys? uploadPqcPrekeys, + ApplicationData_GetPrivacyPassParameters? getPrivacyPassParameters, + ApplicationData_IssuePrivacyPassTokens? issuePrivacyPassTokens, }) { final result = create(); if (textMessage != null) result.textMessage = textMessage; @@ -3404,6 +3507,10 @@ class ApplicationData extends $pb.GeneratedMessage { if (disableMemoriesBackup != null) result.disableMemoriesBackup = disableMemoriesBackup; if (uploadPqcPrekeys != null) result.uploadPqcPrekeys = uploadPqcPrekeys; + if (getPrivacyPassParameters != null) + result.getPrivacyPassParameters = getPrivacyPassParameters; + if (issuePrivacyPassTokens != null) + result.issuePrivacyPassTokens = issuePrivacyPassTokens; return result; } @@ -3446,6 +3553,8 @@ class ApplicationData extends $pb.GeneratedMessage { 38: ApplicationData_ApplicationData.deleteMemory, 39: ApplicationData_ApplicationData.disableMemoriesBackup, 40: ApplicationData_ApplicationData.uploadPqcPrekeys, + 41: ApplicationData_ApplicationData.getPrivacyPassParameters, + 42: ApplicationData_ApplicationData.issuePrivacyPassTokens, 0: ApplicationData_ApplicationData.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo( @@ -3481,7 +3590,9 @@ class ApplicationData extends $pb.GeneratedMessage { 37, 38, 39, - 40 + 40, + 41, + 42 ]) ..aOM(1, _omitFieldNames ? '' : 'textMessage', protoName: 'textMessage', @@ -3579,6 +3690,12 @@ class ApplicationData extends $pb.GeneratedMessage { ..aOM( 40, _omitFieldNames ? '' : 'uploadPqcPrekeys', subBuilder: ApplicationData_UploadPqcPreKeys.create) + ..aOM( + 41, _omitFieldNames ? '' : 'getPrivacyPassParameters', + subBuilder: ApplicationData_GetPrivacyPassParameters.create) + ..aOM( + 42, _omitFieldNames ? '' : 'issuePrivacyPassTokens', + subBuilder: ApplicationData_IssuePrivacyPassTokens.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -3628,6 +3745,8 @@ class ApplicationData extends $pb.GeneratedMessage { @$pb.TagNumber(38) @$pb.TagNumber(39) @$pb.TagNumber(40) + @$pb.TagNumber(41) + @$pb.TagNumber(42) ApplicationData_ApplicationData whichApplicationData() => _ApplicationData_ApplicationDataByTag[$_whichOneof(0)]!; @$pb.TagNumber(1) @@ -3658,6 +3777,8 @@ class ApplicationData extends $pb.GeneratedMessage { @$pb.TagNumber(38) @$pb.TagNumber(39) @$pb.TagNumber(40) + @$pb.TagNumber(41) + @$pb.TagNumber(42) void clearApplicationData() => $_clearField($_whichOneof(0)); @$pb.TagNumber(1) @@ -4003,6 +4124,35 @@ class ApplicationData extends $pb.GeneratedMessage { void clearUploadPqcPrekeys() => $_clearField(40); @$pb.TagNumber(40) ApplicationData_UploadPqcPreKeys ensureUploadPqcPrekeys() => $_ensure(27); + + @$pb.TagNumber(41) + ApplicationData_GetPrivacyPassParameters get getPrivacyPassParameters => + $_getN(28); + @$pb.TagNumber(41) + set getPrivacyPassParameters( + ApplicationData_GetPrivacyPassParameters value) => + $_setField(41, value); + @$pb.TagNumber(41) + $core.bool hasGetPrivacyPassParameters() => $_has(28); + @$pb.TagNumber(41) + void clearGetPrivacyPassParameters() => $_clearField(41); + @$pb.TagNumber(41) + ApplicationData_GetPrivacyPassParameters ensureGetPrivacyPassParameters() => + $_ensure(28); + + @$pb.TagNumber(42) + ApplicationData_IssuePrivacyPassTokens get issuePrivacyPassTokens => + $_getN(29); + @$pb.TagNumber(42) + set issuePrivacyPassTokens(ApplicationData_IssuePrivacyPassTokens value) => + $_setField(42, value); + @$pb.TagNumber(42) + $core.bool hasIssuePrivacyPassTokens() => $_has(29); + @$pb.TagNumber(42) + void clearIssuePrivacyPassTokens() => $_clearField(42); + @$pb.TagNumber(42) + ApplicationData_IssuePrivacyPassTokens ensureIssuePrivacyPassTokens() => + $_ensure(29); } class Response_PreKey extends $pb.GeneratedMessage { diff --git a/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart b/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart index a7aa3f2c..8e106b8f 100644 --- a/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart +++ b/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart @@ -733,6 +733,24 @@ const ApplicationData$json = { '9': 0, '10': 'uploadPqcPrekeys' }, + { + '1': 'get_privacy_pass_parameters', + '3': 41, + '4': 1, + '5': 11, + '6': '.client_to_server.ApplicationData.GetPrivacyPassParameters', + '9': 0, + '10': 'getPrivacyPassParameters' + }, + { + '1': 'issue_privacy_pass_tokens', + '3': 42, + '4': 1, + '5': 11, + '6': '.client_to_server.ApplicationData.IssuePrivacyPassTokens', + '9': 0, + '10': 'issuePrivacyPassTokens' + }, ], '3': [ ApplicationData_TextMessage$json, @@ -763,7 +781,9 @@ const ApplicationData$json = { ApplicationData_GetMemoriesUrl$json, ApplicationData_GetMemoriesUsage$json, ApplicationData_DeleteMemory$json, - ApplicationData_DisableMemoriesBackup$json + ApplicationData_DisableMemoriesBackup$json, + ApplicationData_GetPrivacyPassParameters$json, + ApplicationData_IssuePrivacyPassTokens$json ], '8': [ {'1': 'ApplicationData'}, @@ -1108,6 +1128,19 @@ const ApplicationData_DisableMemoriesBackup$json = { '1': 'DisableMemoriesBackup', }; +@$core.Deprecated('Use applicationDataDescriptor instead') +const ApplicationData_GetPrivacyPassParameters$json = { + '1': 'GetPrivacyPassParameters', +}; + +@$core.Deprecated('Use applicationDataDescriptor instead') +const ApplicationData_IssuePrivacyPassTokens$json = { + '1': 'IssuePrivacyPassTokens', + '2': [ + {'1': 'token_requests', '3': 1, '4': 3, '5': 12, '10': 'tokenRequests'}, + ], +}; + /// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode( 'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm' @@ -1161,49 +1194,55 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode( 'c2VydmVyLkFwcGxpY2F0aW9uRGF0YS5EaXNhYmxlTWVtb3JpZXNCYWNrdXBIAFIVZGlzYWJsZU' '1lbW9yaWVzQmFja3VwEmIKEnVwbG9hZF9wcWNfcHJla2V5cxgoIAEoCzIyLmNsaWVudF90b19z' 'ZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlVwbG9hZFBxY1ByZUtleXNIAFIQdXBsb2FkUHFjUHJla2' - 'V5cxpqCgtUZXh0TWVzc2FnZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEo' - 'DFIEYm9keRIgCglwdXNoX2RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YR' - 'ovChFHZXRVc2VyQnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hh' - 'bmdlVXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZU' - 'ZjbVRva2VuEh0KCmdvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIX' - 'Cgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFQoTR2V0Q3Vycm' - 'VudFBsYW5JbmZvcxovChRSZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1' - 'c2VySWQaLQoSR2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyCh' - 'dHZXRTaWduZWRQcmVLZXlCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVw' - 'ZGF0ZVNpZ25lZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZX' - 'lJZBIjCg1zaWduZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtl' - 'eV9zaWduYXR1cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRrUAQoJUHFjUHJlS2V5Ei' - 'MKDmVjY19wcmVfa2V5X2lkGAEgASgDUgtlY2NQcmVLZXlJZBIeCgtlY2NfcHJlX2tleRgCIAEo' - 'DFIJZWNjUHJlS2V5EicKEGt5YmVyX3ByZV9rZXlfaWQYAyABKANSDWt5YmVyUHJlS2V5SWQSIg' - 'oNa3liZXJfcHJlX2tleRgEIAEoDFILa3liZXJQcmVLZXkSNQoXa3liZXJfcHJlX2tleV9zaWdu' - 'YXR1cmUYBSABKAxSFGt5YmVyUHJlS2V5U2lnbmF0dXJlGp0DChBVcGxvYWRQcWNQcmVLZXlzEi' - '8KFGVjY19zaWduZWRfcHJla2V5X2lkGAEgASgDUhFlY2NTaWduZWRQcmVrZXlJZBIqChFlY2Nf' - 'c2lnbmVkX3ByZWtleRgCIAEoDFIPZWNjU2lnbmVkUHJla2V5Ej0KG2VjY19zaWduZWRfcHJla2' - 'V5X3NpZ25hdHVyZRgDIAEoDFIYZWNjU2lnbmVkUHJla2V5U2lnbmF0dXJlEjMKFmt5YmVyX3Np' - 'Z25lZF9wcmVrZXlfaWQYBCABKANSE2t5YmVyU2lnbmVkUHJla2V5SWQSLgoTa3liZXJfc2lnbm' - 'VkX3ByZWtleRgFIAEoDFIRa3liZXJTaWduZWRQcmVrZXkSQQoda3liZXJfc2lnbmVkX3ByZWtl' - 'eV9zaWduYXR1cmUYBiABKAxSGmt5YmVyU2lnbmVkUHJla2V5U2lnbmF0dXJlEkUKB3ByZWtleX' - 'MYByADKAsyKy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5QcWNQcmVLZXlSB3By' - 'ZWtleXMaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZF' - 'Rva2VuGk4KClJlcG9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgBIAEoA1IOcmVwb3J0ZWRV' - 'c2VySWQSFgoGcmVhc29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2hhc2USHQoKcHJvZHVjdF' - '9pZBgBIAEoCVIJcHJvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291cmNlEisKEXZlcmlmaWNh' - 'dGlvbl9kYXRhGAMgASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQUZvcmNlQ2hlY2saDwoNRG' - 'VsZXRlQWNjb3VudBosChFBZGRBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2Vy' - 'SWQaMAoNU2V0TG9naW5Ub2tlbhIfCgtsb2dpbl90b2tlbhgBIAEoDFIKbG9naW5Ub2tlbhqOAQ' - 'ocUmVnaXN0ZXJQYXNzd29yZExlc3NSZWNvdmVyeRIuChJlbmNyeXB0ZWRTZXJ2ZXJLZXkYASAB' - 'KAxSEmVuY3J5cHRlZFNlcnZlcktleRIrCg5waW5VbmxvY2tUb2tlbhgCIAEoDEgAUg5waW5Vbm' - 'xvY2tUb2tlbogBAUIRCg9fcGluVW5sb2NrVG9rZW4acAoYUGFzc3dvcmRsZXNzTm90aWZpY2F0' - 'aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2F0aW9uSWQSKwoRZW5jcnlwdG' - 'VkX21lc3NhZ2UYAiABKAxSEGVuY3J5cHRlZE1lc3NhZ2UaawoVUmVxdWVzdE1lbW9yaWVzVXBs' - 'b2FkEhIKBHNpemUYASABKANSBHNpemUSIwoNb3JpZ2luYWxfZGF0ZRgCIAEoA1IMb3JpZ2luYW' - 'xEYXRlEhkKCG1lZGlhX2lkGAMgASgJUgdtZWRpYUlkGjIKFUNvbmZpcm1NZW1vcmllc1VwbG9h' - 'ZBIZCghtZWRpYV9pZBgBIAEoCVIHbWVkaWFJZBpICg9HZXRNZW1vcmllc0xpc3QSHwoLb2Zmc2' - 'V0X2RhdGUYASABKANSCm9mZnNldERhdGUSFAoFbGltaXQYAiABKANSBWxpbWl0GkkKDkdldE1l' - 'bW9yaWVzVXJsEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkEhwKCXRodW1ibmFpbBgCIAEoCF' - 'IJdGh1bWJuYWlsGhIKEEdldE1lbW9yaWVzVXNhZ2UaKQoMRGVsZXRlTWVtb3J5EhkKCG1lZGlh' - 'X2lkGAEgASgJUgdtZWRpYUlkGhcKFURpc2FibGVNZW1vcmllc0JhY2t1cEIRCg9BcHBsaWNhdG' - 'lvbkRhdGFKBAgJEApKBAgLEAxKBAgNEBJKBAgTEBQ='); + 'V5cxJ7ChtnZXRfcHJpdmFjeV9wYXNzX3BhcmFtZXRlcnMYKSABKAsyOi5jbGllbnRfdG9fc2Vy' + 'dmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRQcml2YWN5UGFzc1BhcmFtZXRlcnNIAFIYZ2V0UHJpdm' + 'FjeVBhc3NQYXJhbWV0ZXJzEnUKGWlzc3VlX3ByaXZhY3lfcGFzc190b2tlbnMYKiABKAsyOC5j' + 'bGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5Jc3N1ZVByaXZhY3lQYXNzVG9rZW5zSA' + 'BSFmlzc3VlUHJpdmFjeVBhc3NUb2tlbnMaagoLVGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEo' + 'A1IGdXNlcklkEhIKBGJvZHkYAyABKAxSBGJvZHkSIAoJcHVzaF9kYXRhGAQgASgMSABSCHB1c2' + 'hEYXRhiAEBQgwKCl9wdXNoX2RhdGEaLwoRR2V0VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUY' + 'ASABKAlSCHVzZXJuYW1lGiwKDkNoYW5nZVVzZXJuYW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2' + 'VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbhIdCgpnb29nbGVfZmNtGAEgASgJUglnb29n' + 'bGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGhMKEUdldEF2YW' + 'lsYWJsZVBsYW5zGhUKE0dldEN1cnJlbnRQbGFuSW5mb3MaLwoUUmVtb3ZlQWRkaXRpb25hbFVz' + 'ZXISFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2' + 'VyX2lkGAEgASgDUgZ1c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2VySWQSFwoHdXNlcl9p' + 'ZBgBIAEoA1IGdXNlcklkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQc2lnbmVkX3ByZWtleV' + '9pZBgBIAEoA1IOc2lnbmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleRgCIAEoDFIMc2lnbmVk' + 'UHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVzaWduZWRQcmVrZXlTaW' + 'duYXR1cmUa1AEKCVBxY1ByZUtleRIjCg5lY2NfcHJlX2tleV9pZBgBIAEoA1ILZWNjUHJlS2V5' + 'SWQSHgoLZWNjX3ByZV9rZXkYAiABKAxSCWVjY1ByZUtleRInChBreWJlcl9wcmVfa2V5X2lkGA' + 'MgASgDUg1reWJlclByZUtleUlkEiIKDWt5YmVyX3ByZV9rZXkYBCABKAxSC2t5YmVyUHJlS2V5' + 'EjUKF2t5YmVyX3ByZV9rZXlfc2lnbmF0dXJlGAUgASgMUhRreWJlclByZUtleVNpZ25hdHVyZR' + 'qdAwoQVXBsb2FkUHFjUHJlS2V5cxIvChRlY2Nfc2lnbmVkX3ByZWtleV9pZBgBIAEoA1IRZWNj' + 'U2lnbmVkUHJla2V5SWQSKgoRZWNjX3NpZ25lZF9wcmVrZXkYAiABKAxSD2VjY1NpZ25lZFByZW' + 'tleRI9ChtlY2Nfc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYAyABKAxSGGVjY1NpZ25lZFByZWtl' + 'eVNpZ25hdHVyZRIzChZreWJlcl9zaWduZWRfcHJla2V5X2lkGAQgASgDUhNreWJlclNpZ25lZF' + 'ByZWtleUlkEi4KE2t5YmVyX3NpZ25lZF9wcmVrZXkYBSABKAxSEWt5YmVyU2lnbmVkUHJla2V5' + 'EkEKHWt5YmVyX3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAYgASgMUhpreWJlclNpZ25lZFByZW' + 'tleVNpZ25hdHVyZRJFCgdwcmVrZXlzGAcgAygLMisuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNh' + 'dGlvbkRhdGEuUHFjUHJlS2V5UgdwcmVrZXlzGjUKDERvd25sb2FkRG9uZRIlCg5kb3dubG9hZF' + '90b2tlbhgBIAEoDFINZG93bmxvYWRUb2tlbhpOCgpSZXBvcnRVc2VyEigKEHJlcG9ydGVkX3Vz' + 'ZXJfaWQYASABKANSDnJlcG9ydGVkVXNlcklkEhYKBnJlYXNvbhgCIAEoCVIGcmVhc29uGnEKC0' + 'lQQVB1cmNoYXNlEh0KCnByb2R1Y3RfaWQYASABKAlSCXByb2R1Y3RJZBIWCgZzb3VyY2UYAiAB' + 'KAlSBnNvdXJjZRIrChF2ZXJpZmljYXRpb25fZGF0YRgDIAEoCVIQdmVyaWZpY2F0aW9uRGF0YR' + 'oPCg1JUEFGb3JjZUNoZWNrGg8KDURlbGV0ZUFjY291bnQaLAoRQWRkQWRkaXRpb25hbFVzZXIS' + 'FwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGjAKDVNldExvZ2luVG9rZW4SHwoLbG9naW5fdG9rZW' + '4YASABKAxSCmxvZ2luVG9rZW4ajgEKHFJlZ2lzdGVyUGFzc3dvcmRMZXNzUmVjb3ZlcnkSLgoS' + 'ZW5jcnlwdGVkU2VydmVyS2V5GAEgASgMUhJlbmNyeXB0ZWRTZXJ2ZXJLZXkSKwoOcGluVW5sb2' + 'NrVG9rZW4YAiABKAxIAFIOcGluVW5sb2NrVG9rZW6IAQFCEQoPX3BpblVubG9ja1Rva2VuGnAK' + 'GFBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbhInCg9ub3RpZmljYXRpb25faWQYASABKAlSDm5vdG' + 'lmaWNhdGlvbklkEisKEWVuY3J5cHRlZF9tZXNzYWdlGAIgASgMUhBlbmNyeXB0ZWRNZXNzYWdl' + 'GmsKFVJlcXVlc3RNZW1vcmllc1VwbG9hZBISCgRzaXplGAEgASgDUgRzaXplEiMKDW9yaWdpbm' + 'FsX2RhdGUYAiABKANSDG9yaWdpbmFsRGF0ZRIZCghtZWRpYV9pZBgDIAEoCVIHbWVkaWFJZBoy' + 'ChVDb25maXJtTWVtb3JpZXNVcGxvYWQSGQoIbWVkaWFfaWQYASABKAlSB21lZGlhSWQaSAoPR2' + 'V0TWVtb3JpZXNMaXN0Eh8KC29mZnNldF9kYXRlGAEgASgDUgpvZmZzZXREYXRlEhQKBWxpbWl0' + 'GAIgASgDUgVsaW1pdBpJCg5HZXRNZW1vcmllc1VybBIZCghtZWRpYV9pZBgBIAEoCVIHbWVkaW' + 'FJZBIcCgl0aHVtYm5haWwYAiABKAhSCXRodW1ibmFpbBoSChBHZXRNZW1vcmllc1VzYWdlGikK' + 'DERlbGV0ZU1lbW9yeRIZCghtZWRpYV9pZBgBIAEoCVIHbWVkaWFJZBoXChVEaXNhYmxlTWVtb3' + 'JpZXNCYWNrdXAaGgoYR2V0UHJpdmFjeVBhc3NQYXJhbWV0ZXJzGj8KFklzc3VlUHJpdmFjeVBh' + 'c3NUb2tlbnMSJQoOdG9rZW5fcmVxdWVzdHMYASADKAxSDXRva2VuUmVxdWVzdHNCEQoPQXBwbG' + 'ljYXRpb25EYXRhSgQICRAKSgQICxAMSgQIDRASSgQIExAU'); @$core.Deprecated('Use responseDescriptor instead') const Response$json = { diff --git a/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart b/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart index 0d1053aa..6acc1416 100644 --- a/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart +++ b/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart @@ -96,6 +96,8 @@ enum V0_Kind { error, newMessages, requestNewPqcPreKeys, + sealedSenderMessage, + sealedSenderMessages, notSet } @@ -108,6 +110,8 @@ class V0 extends $pb.GeneratedMessage { $0.ErrorCode? error, NewMessages? newMessages, $core.bool? requestNewPqcPreKeys, + SealedSenderMessage? sealedSenderMessage, + SealedSenderMessages? sealedSenderMessages, }) { final result = create(); if (seq != null) result.seq = seq; @@ -118,6 +122,10 @@ class V0 extends $pb.GeneratedMessage { if (newMessages != null) result.newMessages = newMessages; if (requestNewPqcPreKeys != null) result.requestNewPqcPreKeys = requestNewPqcPreKeys; + if (sealedSenderMessage != null) + result.sealedSenderMessage = sealedSenderMessage; + if (sealedSenderMessages != null) + result.sealedSenderMessages = sealedSenderMessages; return result; } @@ -137,6 +145,8 @@ class V0 extends $pb.GeneratedMessage { 6: V0_Kind.error, 7: V0_Kind.newMessages, 8: V0_Kind.requestNewPqcPreKeys, + 9: V0_Kind.sealedSenderMessage, + 10: V0_Kind.sealedSenderMessages, 0: V0_Kind.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo( @@ -144,7 +154,7 @@ class V0 extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), createEmptyInstance: create) - ..oo(0, [2, 3, 4, 6, 7, 8]) + ..oo(0, [2, 3, 4, 6, 7, 8, 9, 10]) ..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'seq', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) ..aOM(2, _omitFieldNames ? '' : 'response', @@ -159,6 +169,13 @@ class V0 extends $pb.GeneratedMessage { protoName: 'newMessages', subBuilder: NewMessages.create) ..aOB(8, _omitFieldNames ? '' : 'RequestNewPqcPreKeys', protoName: 'RequestNewPqcPreKeys') + ..aOM(9, _omitFieldNames ? '' : 'sealedSenderMessage', + protoName: 'sealedSenderMessage', + subBuilder: SealedSenderMessage.create) + ..aOM( + 10, _omitFieldNames ? '' : 'sealedSenderMessages', + protoName: 'sealedSenderMessages', + subBuilder: SealedSenderMessages.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -185,6 +202,8 @@ class V0 extends $pb.GeneratedMessage { @$pb.TagNumber(6) @$pb.TagNumber(7) @$pb.TagNumber(8) + @$pb.TagNumber(9) + @$pb.TagNumber(10) V0_Kind whichKind() => _V0_KindByTag[$_whichOneof(0)]!; @$pb.TagNumber(2) @$pb.TagNumber(3) @@ -192,6 +211,8 @@ class V0 extends $pb.GeneratedMessage { @$pb.TagNumber(6) @$pb.TagNumber(7) @$pb.TagNumber(8) + @$pb.TagNumber(9) + @$pb.TagNumber(10) void clearKind() => $_clearField($_whichOneof(0)); @$pb.TagNumber(1) @@ -262,6 +283,28 @@ class V0 extends $pb.GeneratedMessage { $core.bool hasRequestNewPqcPreKeys() => $_has(6); @$pb.TagNumber(8) void clearRequestNewPqcPreKeys() => $_clearField(8); + + @$pb.TagNumber(9) + SealedSenderMessage get sealedSenderMessage => $_getN(7); + @$pb.TagNumber(9) + set sealedSenderMessage(SealedSenderMessage value) => $_setField(9, value); + @$pb.TagNumber(9) + $core.bool hasSealedSenderMessage() => $_has(7); + @$pb.TagNumber(9) + void clearSealedSenderMessage() => $_clearField(9); + @$pb.TagNumber(9) + SealedSenderMessage ensureSealedSenderMessage() => $_ensure(7); + + @$pb.TagNumber(10) + SealedSenderMessages get sealedSenderMessages => $_getN(8); + @$pb.TagNumber(10) + set sealedSenderMessages(SealedSenderMessages value) => $_setField(10, value); + @$pb.TagNumber(10) + $core.bool hasSealedSenderMessages() => $_has(8); + @$pb.TagNumber(10) + void clearSealedSenderMessages() => $_clearField(10); + @$pb.TagNumber(10) + SealedSenderMessages ensureSealedSenderMessages() => $_ensure(8); } class NewMessage extends $pb.GeneratedMessage { @@ -381,6 +424,124 @@ class NewMessages extends $pb.GeneratedMessage { $pb.PbList get newMessages => $_getList(0); } +class SealedSenderMessage extends $pb.GeneratedMessage { + factory SealedSenderMessage({ + $core.String? messageId, + $core.List<$core.int>? body, + }) { + final result = create(); + if (messageId != null) result.messageId = messageId; + if (body != null) result.body = body; + return result; + } + + SealedSenderMessage._(); + + factory SealedSenderMessage.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SealedSenderMessage.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SealedSenderMessage', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'messageId') + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'body', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SealedSenderMessage clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SealedSenderMessage copyWith(void Function(SealedSenderMessage) updates) => + super.copyWith((message) => updates(message as SealedSenderMessage)) + as SealedSenderMessage; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SealedSenderMessage create() => SealedSenderMessage._(); + @$core.override + SealedSenderMessage createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SealedSenderMessage getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SealedSenderMessage? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get messageId => $_getSZ(0); + @$pb.TagNumber(1) + set messageId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasMessageId() => $_has(0); + @$pb.TagNumber(1) + void clearMessageId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get body => $_getN(1); + @$pb.TagNumber(2) + set body($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasBody() => $_has(1); + @$pb.TagNumber(2) + void clearBody() => $_clearField(2); +} + +class SealedSenderMessages extends $pb.GeneratedMessage { + factory SealedSenderMessages({ + $core.Iterable? messages, + }) { + final result = create(); + if (messages != null) result.messages.addAll(messages); + return result; + } + + SealedSenderMessages._(); + + factory SealedSenderMessages.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SealedSenderMessages.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SealedSenderMessages', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), + createEmptyInstance: create) + ..pPM(1, _omitFieldNames ? '' : 'messages', + subBuilder: SealedSenderMessage.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SealedSenderMessages clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SealedSenderMessages copyWith(void Function(SealedSenderMessages) updates) => + super.copyWith((message) => updates(message as SealedSenderMessages)) + as SealedSenderMessages; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SealedSenderMessages create() => SealedSenderMessages._(); + @$core.override + SealedSenderMessages createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SealedSenderMessages getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SealedSenderMessages? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList get messages => $_getList(0); +} + class Response_Authenticated extends $pb.GeneratedMessage { factory Response_Authenticated({ $core.String? plan, @@ -2307,6 +2468,186 @@ class Response_MemoriesUsage extends $pb.GeneratedMessage { void clearCount() => $_clearField(3); } +class Response_PrivacyPassParameters extends $pb.GeneratedMessage { + factory Response_PrivacyPassParameters({ + $core.List<$core.int>? tokenChallenge, + $core.List<$core.int>? publicKey, + $core.int? maxBatchSize, + $core.int? maxAgeSeconds, + $core.int? dailyTokenLimit, + $core.int? issuanceCooldownSeconds, + }) { + final result = create(); + if (tokenChallenge != null) result.tokenChallenge = tokenChallenge; + if (publicKey != null) result.publicKey = publicKey; + if (maxBatchSize != null) result.maxBatchSize = maxBatchSize; + if (maxAgeSeconds != null) result.maxAgeSeconds = maxAgeSeconds; + if (dailyTokenLimit != null) result.dailyTokenLimit = dailyTokenLimit; + if (issuanceCooldownSeconds != null) + result.issuanceCooldownSeconds = issuanceCooldownSeconds; + return result; + } + + Response_PrivacyPassParameters._(); + + factory Response_PrivacyPassParameters.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory Response_PrivacyPassParameters.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Response.PrivacyPassParameters', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'tokenChallenge', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY) + ..aI(3, _omitFieldNames ? '' : 'maxBatchSize', + fieldType: $pb.PbFieldType.OU3) + ..aI(4, _omitFieldNames ? '' : 'maxAgeSeconds', + fieldType: $pb.PbFieldType.OU3) + ..aI(5, _omitFieldNames ? '' : 'dailyTokenLimit', + fieldType: $pb.PbFieldType.OU3) + ..aI(6, _omitFieldNames ? '' : 'issuanceCooldownSeconds', + fieldType: $pb.PbFieldType.OU3) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Response_PrivacyPassParameters clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Response_PrivacyPassParameters copyWith( + void Function(Response_PrivacyPassParameters) updates) => + super.copyWith( + (message) => updates(message as Response_PrivacyPassParameters)) + as Response_PrivacyPassParameters; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Response_PrivacyPassParameters create() => + Response_PrivacyPassParameters._(); + @$core.override + Response_PrivacyPassParameters createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static Response_PrivacyPassParameters getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static Response_PrivacyPassParameters? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get tokenChallenge => $_getN(0); + @$pb.TagNumber(1) + set tokenChallenge($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasTokenChallenge() => $_has(0); + @$pb.TagNumber(1) + void clearTokenChallenge() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get publicKey => $_getN(1); + @$pb.TagNumber(2) + set publicKey($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasPublicKey() => $_has(1); + @$pb.TagNumber(2) + void clearPublicKey() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get maxBatchSize => $_getIZ(2); + @$pb.TagNumber(3) + set maxBatchSize($core.int value) => $_setUnsignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasMaxBatchSize() => $_has(2); + @$pb.TagNumber(3) + void clearMaxBatchSize() => $_clearField(3); + + @$pb.TagNumber(4) + $core.int get maxAgeSeconds => $_getIZ(3); + @$pb.TagNumber(4) + set maxAgeSeconds($core.int value) => $_setUnsignedInt32(3, value); + @$pb.TagNumber(4) + $core.bool hasMaxAgeSeconds() => $_has(3); + @$pb.TagNumber(4) + void clearMaxAgeSeconds() => $_clearField(4); + + @$pb.TagNumber(5) + $core.int get dailyTokenLimit => $_getIZ(4); + @$pb.TagNumber(5) + set dailyTokenLimit($core.int value) => $_setUnsignedInt32(4, value); + @$pb.TagNumber(5) + $core.bool hasDailyTokenLimit() => $_has(4); + @$pb.TagNumber(5) + void clearDailyTokenLimit() => $_clearField(5); + + @$pb.TagNumber(6) + $core.int get issuanceCooldownSeconds => $_getIZ(5); + @$pb.TagNumber(6) + set issuanceCooldownSeconds($core.int value) => $_setUnsignedInt32(5, value); + @$pb.TagNumber(6) + $core.bool hasIssuanceCooldownSeconds() => $_has(5); + @$pb.TagNumber(6) + void clearIssuanceCooldownSeconds() => $_clearField(6); +} + +class Response_PrivacyPassTokenResponses extends $pb.GeneratedMessage { + factory Response_PrivacyPassTokenResponses({ + $core.Iterable<$core.List<$core.int>>? tokenResponses, + }) { + final result = create(); + if (tokenResponses != null) result.tokenResponses.addAll(tokenResponses); + return result; + } + + Response_PrivacyPassTokenResponses._(); + + factory Response_PrivacyPassTokenResponses.fromBuffer( + $core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory Response_PrivacyPassTokenResponses.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Response.PrivacyPassTokenResponses', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), + createEmptyInstance: create) + ..p<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'tokenResponses', $pb.PbFieldType.PY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Response_PrivacyPassTokenResponses clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Response_PrivacyPassTokenResponses copyWith( + void Function(Response_PrivacyPassTokenResponses) updates) => + super.copyWith((message) => + updates(message as Response_PrivacyPassTokenResponses)) + as Response_PrivacyPassTokenResponses; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Response_PrivacyPassTokenResponses create() => + Response_PrivacyPassTokenResponses._(); + @$core.override + Response_PrivacyPassTokenResponses createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static Response_PrivacyPassTokenResponses getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor( + create); + static Response_PrivacyPassTokenResponses? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList<$core.List<$core.int>> get tokenResponses => $_getList(0); +} + enum Response_Ok_Ok { none, userid, @@ -2327,6 +2668,8 @@ enum Response_Ok_Ok { memoriesList, memoriesUrl, memoriesUsage, + privacyPassParameters, + privacyPassTokenResponses, notSet } @@ -2351,6 +2694,8 @@ class Response_Ok extends $pb.GeneratedMessage { Response_MemoriesList? memoriesList, Response_MemoriesUrl? memoriesUrl, Response_MemoriesUsage? memoriesUsage, + Response_PrivacyPassParameters? privacyPassParameters, + Response_PrivacyPassTokenResponses? privacyPassTokenResponses, }) { final result = create(); if (none != null) result.none = none; @@ -2377,6 +2722,10 @@ class Response_Ok extends $pb.GeneratedMessage { if (memoriesList != null) result.memoriesList = memoriesList; if (memoriesUrl != null) result.memoriesUrl = memoriesUrl; if (memoriesUsage != null) result.memoriesUsage = memoriesUsage; + if (privacyPassParameters != null) + result.privacyPassParameters = privacyPassParameters; + if (privacyPassTokenResponses != null) + result.privacyPassTokenResponses = privacyPassTokenResponses; return result; } @@ -2409,6 +2758,8 @@ class Response_Ok extends $pb.GeneratedMessage { 19: Response_Ok_Ok.memoriesList, 20: Response_Ok_Ok.memoriesUrl, 21: Response_Ok_Ok.memoriesUsage, + 22: Response_Ok_Ok.privacyPassParameters, + 23: Response_Ok_Ok.privacyPassTokenResponses, 0: Response_Ok_Ok.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo( @@ -2416,8 +2767,29 @@ class Response_Ok extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), createEmptyInstance: create) - ..oo( - 0, [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) + ..oo(0, [ + 1, + 2, + 3, + 4, + 5, + 6, + 8, + 9, + 10, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23 + ]) ..aOB(1, _omitFieldNames ? '' : 'None', protoName: 'None') ..aInt64(2, _omitFieldNames ? '' : 'userid') ..a<$core.List<$core.int>>( @@ -2459,6 +2831,12 @@ class Response_Ok extends $pb.GeneratedMessage { subBuilder: Response_MemoriesUrl.create) ..aOM(21, _omitFieldNames ? '' : 'memoriesUsage', subBuilder: Response_MemoriesUsage.create) + ..aOM( + 22, _omitFieldNames ? '' : 'privacyPassParameters', + subBuilder: Response_PrivacyPassParameters.create) + ..aOM( + 23, _omitFieldNames ? '' : 'privacyPassTokenResponses', + subBuilder: Response_PrivacyPassTokenResponses.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -2499,6 +2877,8 @@ class Response_Ok extends $pb.GeneratedMessage { @$pb.TagNumber(19) @$pb.TagNumber(20) @$pb.TagNumber(21) + @$pb.TagNumber(22) + @$pb.TagNumber(23) Response_Ok_Ok whichOk() => _Response_Ok_OkByTag[$_whichOneof(0)]!; @$pb.TagNumber(1) @$pb.TagNumber(2) @@ -2519,6 +2899,8 @@ class Response_Ok extends $pb.GeneratedMessage { @$pb.TagNumber(19) @$pb.TagNumber(20) @$pb.TagNumber(21) + @$pb.TagNumber(22) + @$pb.TagNumber(23) void clearOk() => $_clearField($_whichOneof(0)); @$pb.TagNumber(1) @@ -2726,6 +3108,32 @@ class Response_Ok extends $pb.GeneratedMessage { void clearMemoriesUsage() => $_clearField(21); @$pb.TagNumber(21) Response_MemoriesUsage ensureMemoriesUsage() => $_ensure(18); + + @$pb.TagNumber(22) + Response_PrivacyPassParameters get privacyPassParameters => $_getN(19); + @$pb.TagNumber(22) + set privacyPassParameters(Response_PrivacyPassParameters value) => + $_setField(22, value); + @$pb.TagNumber(22) + $core.bool hasPrivacyPassParameters() => $_has(19); + @$pb.TagNumber(22) + void clearPrivacyPassParameters() => $_clearField(22); + @$pb.TagNumber(22) + Response_PrivacyPassParameters ensurePrivacyPassParameters() => $_ensure(19); + + @$pb.TagNumber(23) + Response_PrivacyPassTokenResponses get privacyPassTokenResponses => + $_getN(20); + @$pb.TagNumber(23) + set privacyPassTokenResponses(Response_PrivacyPassTokenResponses value) => + $_setField(23, value); + @$pb.TagNumber(23) + $core.bool hasPrivacyPassTokenResponses() => $_has(20); + @$pb.TagNumber(23) + void clearPrivacyPassTokenResponses() => $_clearField(23); + @$pb.TagNumber(23) + Response_PrivacyPassTokenResponses ensurePrivacyPassTokenResponses() => + $_ensure(20); } enum Response_Response { ok, error, notSet } diff --git a/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart b/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart index 42b93650..311c88bc 100644 --- a/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart +++ b/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart @@ -96,6 +96,24 @@ const V0$json = { '9': 0, '10': 'error' }, + { + '1': 'sealedSenderMessage', + '3': 9, + '4': 1, + '5': 11, + '6': '.server_to_client.SealedSenderMessage', + '9': 0, + '10': 'sealedSenderMessage' + }, + { + '1': 'sealedSenderMessages', + '3': 10, + '4': 1, + '5': 11, + '6': '.server_to_client.SealedSenderMessages', + '9': 0, + '10': 'sealedSenderMessages' + }, ], '8': [ {'1': 'Kind'}, @@ -110,7 +128,10 @@ final $typed_data.Uint8List v0Descriptor = $convert.base64Decode( '0uc2VydmVyX3RvX2NsaWVudC5OZXdNZXNzYWdlc0gAUgtuZXdNZXNzYWdlcxIuChFSZXF1ZXN0' 'TmV3UHJlS2V5cxgEIAEoCEgAUhFSZXF1ZXN0TmV3UHJlS2V5cxI0ChRSZXF1ZXN0TmV3UHFjUH' 'JlS2V5cxgIIAEoCEgAUhRSZXF1ZXN0TmV3UHFjUHJlS2V5cxIoCgVlcnJvchgGIAEoDjIQLmVy' - 'cm9yLkVycm9yQ29kZUgAUgVlcnJvckIGCgRLaW5k'); + 'cm9yLkVycm9yQ29kZUgAUgVlcnJvchJZChNzZWFsZWRTZW5kZXJNZXNzYWdlGAkgASgLMiUuc2' + 'VydmVyX3RvX2NsaWVudC5TZWFsZWRTZW5kZXJNZXNzYWdlSABSE3NlYWxlZFNlbmRlck1lc3Nh' + 'Z2USXAoUc2VhbGVkU2VuZGVyTWVzc2FnZXMYCiABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlNlYW' + 'xlZFNlbmRlck1lc3NhZ2VzSABSFHNlYWxlZFNlbmRlck1lc3NhZ2VzQgYKBEtpbmQ='); @$core.Deprecated('Use newMessageDescriptor instead') const NewMessage$json = { @@ -146,6 +167,40 @@ final $typed_data.Uint8List newMessagesDescriptor = $convert.base64Decode( 'CgtOZXdNZXNzYWdlcxI+CgtuZXdNZXNzYWdlcxgBIAMoCzIcLnNlcnZlcl90b19jbGllbnQuTm' 'V3TWVzc2FnZVILbmV3TWVzc2FnZXM='); +@$core.Deprecated('Use sealedSenderMessageDescriptor instead') +const SealedSenderMessage$json = { + '1': 'SealedSenderMessage', + '2': [ + {'1': 'message_id', '3': 1, '4': 1, '5': 9, '10': 'messageId'}, + {'1': 'body', '3': 2, '4': 1, '5': 12, '10': 'body'}, + ], +}; + +/// Descriptor for `SealedSenderMessage`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sealedSenderMessageDescriptor = $convert.base64Decode( + 'ChNTZWFsZWRTZW5kZXJNZXNzYWdlEh0KCm1lc3NhZ2VfaWQYASABKAlSCW1lc3NhZ2VJZBISCg' + 'Rib2R5GAIgASgMUgRib2R5'); + +@$core.Deprecated('Use sealedSenderMessagesDescriptor instead') +const SealedSenderMessages$json = { + '1': 'SealedSenderMessages', + '2': [ + { + '1': 'messages', + '3': 1, + '4': 3, + '5': 11, + '6': '.server_to_client.SealedSenderMessage', + '10': 'messages' + }, + ], +}; + +/// Descriptor for `SealedSenderMessages`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sealedSenderMessagesDescriptor = $convert.base64Decode( + 'ChRTZWFsZWRTZW5kZXJNZXNzYWdlcxJBCghtZXNzYWdlcxgBIAMoCzIlLnNlcnZlcl90b19jbG' + 'llbnQuU2VhbGVkU2VuZGVyTWVzc2FnZVIIbWVzc2FnZXM='); + @$core.Deprecated('Use responseDescriptor instead') const Response$json = { '1': 'Response', @@ -194,6 +249,8 @@ const Response$json = { Response_MemoriesList$json, Response_MemoriesUrl$json, Response_MemoriesUsage$json, + Response_PrivacyPassParameters$json, + Response_PrivacyPassTokenResponses$json, Response_Ok$json ], '8': [ @@ -750,6 +807,39 @@ const Response_MemoriesUsage$json = { ], }; +@$core.Deprecated('Use responseDescriptor instead') +const Response_PrivacyPassParameters$json = { + '1': 'PrivacyPassParameters', + '2': [ + {'1': 'token_challenge', '3': 1, '4': 1, '5': 12, '10': 'tokenChallenge'}, + {'1': 'public_key', '3': 2, '4': 1, '5': 12, '10': 'publicKey'}, + {'1': 'max_batch_size', '3': 3, '4': 1, '5': 13, '10': 'maxBatchSize'}, + {'1': 'max_age_seconds', '3': 4, '4': 1, '5': 13, '10': 'maxAgeSeconds'}, + { + '1': 'daily_token_limit', + '3': 5, + '4': 1, + '5': 13, + '10': 'dailyTokenLimit' + }, + { + '1': 'issuance_cooldown_seconds', + '3': 6, + '4': 1, + '5': 13, + '10': 'issuanceCooldownSeconds' + }, + ], +}; + +@$core.Deprecated('Use responseDescriptor instead') +const Response_PrivacyPassTokenResponses$json = { + '1': 'PrivacyPassTokenResponses', + '2': [ + {'1': 'token_responses', '3': 1, '4': 3, '5': 12, '10': 'tokenResponses'}, + ], +}; + @$core.Deprecated('Use responseDescriptor instead') const Response_Ok$json = { '1': 'Ok', @@ -899,6 +989,24 @@ const Response_Ok$json = { '9': 0, '10': 'memoriesUsage' }, + { + '1': 'privacy_pass_parameters', + '3': 22, + '4': 1, + '5': 11, + '6': '.server_to_client.Response.PrivacyPassParameters', + '9': 0, + '10': 'privacyPassParameters' + }, + { + '1': 'privacy_pass_token_responses', + '3': 23, + '4': 1, + '5': 11, + '6': '.server_to_client.Response.PrivacyPassTokenResponses', + '9': 0, + '10': 'privacyPassTokenResponses' + }, ], '8': [ {'1': 'Ok'}, @@ -988,29 +1096,40 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode( 'ZGlhSXRlbVIFaXRlbXMaOQoLTWVtb3JpZXNVcmwSKgoRZnVsbF9kb3dubG9hZF91cmwYASABKA' 'lSD2Z1bGxEb3dubG9hZFVybBpnCg1NZW1vcmllc1VzYWdlEhsKCW1heF9ieXRlcxgBIAEoA1II' 'bWF4Qnl0ZXMSIwoNY3VycmVudF9ieXRlcxgCIAEoA1IMY3VycmVudEJ5dGVzEhQKBWNvdW50GA' - 'MgASgDUgVjb3VudBr1CgoCT2sSFAoETm9uZRgBIAEoCEgAUgROb25lEhgKBnVzZXJpZBgCIAEo' - 'A0gAUgZ1c2VyaWQSJgoNYXV0aGNoYWxsZW5nZRgDIAEoDEgAUg1hdXRoY2hhbGxlbmdlEkoKC3' - 'VwbG9hZHRva2VuGAQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5VcGxvYWRUb2tl' - 'bkgAUgt1cGxvYWR0b2tlbhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUm' - 'VzcG9uc2UuVXNlckRhdGFIAFIIdXNlcmRhdGESHgoJYXV0aHRva2VuGAYgASgMSABSCWF1dGh0' - 'b2tlbhJQCg1hdXRoZW50aWNhdGVkGAggASgLMiguc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS' - '5BdXRoZW50aWNhdGVkSABSDWF1dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSABKAsyIC5zZXJ2ZXJf' - 'dG9fY2xpZW50LlJlc3BvbnNlLlBsYW5zSABSBXBsYW5zEk0KDHBsYW5iYWxsYW5jZRgKIAEoCz' - 'InLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbkJhbGxhbmNlSABSDHBsYW5iYWxsYW5j' - 'ZRJfChJhZGRhY2NvdW50c2ludml0ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3Bvbn' - 'NlLkFkZEFjY291bnRzSW52aXRlc0gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0' - 'b2tlbnMYDSABKAsyKS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSA' - 'BSDmRvd25sb2FkdG9rZW5zEk0KDHNpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGll' - 'bnQuUmVzcG9uc2UuU2lnbmVkUHJlS2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yax' - 'gPIAEoCzImLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZP' - 'ZldvcmsSSQogcGFzc3dvcmRsZXNzX3JlY292ZXJ5X3NlcnZlcl9rZXkYECABKAxIAFIdcGFzc3' - 'dvcmRsZXNzUmVjb3ZlcnlTZXJ2ZXJLZXkSiwEKInBhc3N3b3JkbGVzc19ub3RpZmljYXRpb25f' - 'bWVzc2FnZXMYESABKAsyOy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBhc3N3b3JkbGVzc0' - '5vdGlmaWNhdGlvbk1lc3NhZ2VzSABSIHBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2Vz' - 'EmEKFG1lbW9yaWVzX3VwbG9hZF91cmxzGBIgASgLMi0uc2VydmVyX3RvX2NsaWVudC5SZXNwb2' - '5zZS5NZW1vcmllc1VwbG9hZFVybHNIAFISbWVtb3JpZXNVcGxvYWRVcmxzEk4KDW1lbW9yaWVz' - 'X2xpc3QYEyABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk1lbW9yaWVzTGlzdEgAUg' - 'xtZW1vcmllc0xpc3QSSwoMbWVtb3JpZXNfdXJsGBQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5S' - 'ZXNwb25zZS5NZW1vcmllc1VybEgAUgttZW1vcmllc1VybBJRCg5tZW1vcmllc191c2FnZRgVIA' - 'EoCzIoLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuTWVtb3JpZXNVc2FnZUgAUg1tZW1vcmll' - 'c1VzYWdlQgQKAk9rSgQIBxAISgQICxAMQgoKCFJlc3BvbnNl'); + 'MgASgDUgVjb3VudBqVAgoVUHJpdmFjeVBhc3NQYXJhbWV0ZXJzEicKD3Rva2VuX2NoYWxsZW5n' + 'ZRgBIAEoDFIOdG9rZW5DaGFsbGVuZ2USHQoKcHVibGljX2tleRgCIAEoDFIJcHVibGljS2V5Ei' + 'QKDm1heF9iYXRjaF9zaXplGAMgASgNUgxtYXhCYXRjaFNpemUSJgoPbWF4X2FnZV9zZWNvbmRz' + 'GAQgASgNUg1tYXhBZ2VTZWNvbmRzEioKEWRhaWx5X3Rva2VuX2xpbWl0GAUgASgNUg9kYWlseV' + 'Rva2VuTGltaXQSOgoZaXNzdWFuY2VfY29vbGRvd25fc2Vjb25kcxgGIAEoDVIXaXNzdWFuY2VD' + 'b29sZG93blNlY29uZHMaRAoZUHJpdmFjeVBhc3NUb2tlblJlc3BvbnNlcxInCg90b2tlbl9yZX' + 'Nwb25zZXMYASADKAxSDnRva2VuUmVzcG9uc2VzGtoMCgJPaxIUCgROb25lGAEgASgISABSBE5v' + 'bmUSGAoGdXNlcmlkGAIgASgDSABSBnVzZXJpZBImCg1hdXRoY2hhbGxlbmdlGAMgASgMSABSDW' + 'F1dGhjaGFsbGVuZ2USSgoLdXBsb2FkdG9rZW4YBCABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJl' + 'c3BvbnNlLlVwbG9hZFRva2VuSABSC3VwbG9hZHRva2VuEkEKCHVzZXJkYXRhGAUgASgLMiMuc2' + 'VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5Vc2VyRGF0YUgAUgh1c2VyZGF0YRIeCglhdXRodG9r' + 'ZW4YBiABKAxIAFIJYXV0aHRva2VuElAKDWF1dGhlbnRpY2F0ZWQYCCABKAsyKC5zZXJ2ZXJfdG' + '9fY2xpZW50LlJlc3BvbnNlLkF1dGhlbnRpY2F0ZWRIAFINYXV0aGVudGljYXRlZBI4CgVwbGFu' + 'cxgJIAEoCzIgLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbnNIAFIFcGxhbnMSTQoMcG' + 'xhbmJhbGxhbmNlGAogASgLMicuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QbGFuQmFsbGFu' + 'Y2VIAFIMcGxhbmJhbGxhbmNlEl8KEmFkZGFjY291bnRzaW52aXRlcxgMIAEoCzItLnNlcnZlcl' + '90b19jbGllbnQuUmVzcG9uc2UuQWRkQWNjb3VudHNJbnZpdGVzSABSEmFkZGFjY291bnRzaW52' + 'aXRlcxJTCg5kb3dubG9hZHRva2VucxgNIAEoCzIpLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2' + 'UuRG93bmxvYWRUb2tlbnNIAFIOZG93bmxvYWR0b2tlbnMSTQoMc2lnbmVkcHJla2V5GA4gASgL' + 'Micuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5TaWduZWRQcmVLZXlIAFIMc2lnbmVkcHJla2' + 'V5EkoKC3Byb29mT2ZXb3JrGA8gASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5Qcm9v' + 'Zk9mV29ya0gAUgtwcm9vZk9mV29yaxJJCiBwYXNzd29yZGxlc3NfcmVjb3Zlcnlfc2VydmVyX2' + 'tleRgQIAEoDEgAUh1wYXNzd29yZGxlc3NSZWNvdmVyeVNlcnZlcktleRKLAQoicGFzc3dvcmRs' + 'ZXNzX25vdGlmaWNhdGlvbl9tZXNzYWdlcxgRIAEoCzI7LnNlcnZlcl90b19jbGllbnQuUmVzcG' + '9uc2UuUGFzc3dvcmRsZXNzTm90aWZpY2F0aW9uTWVzc2FnZXNIAFIgcGFzc3dvcmRsZXNzTm90' + 'aWZpY2F0aW9uTWVzc2FnZXMSYQoUbWVtb3JpZXNfdXBsb2FkX3VybHMYEiABKAsyLS5zZXJ2ZX' + 'JfdG9fY2xpZW50LlJlc3BvbnNlLk1lbW9yaWVzVXBsb2FkVXJsc0gAUhJtZW1vcmllc1VwbG9h' + 'ZFVybHMSTgoNbWVtb3JpZXNfbGlzdBgTIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2' + 'UuTWVtb3JpZXNMaXN0SABSDG1lbW9yaWVzTGlzdBJLCgxtZW1vcmllc191cmwYFCABKAsyJi5z' + 'ZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk1lbW9yaWVzVXJsSABSC21lbW9yaWVzVXJsElEKDm' + '1lbW9yaWVzX3VzYWdlGBUgASgLMiguc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5NZW1vcmll' + 'c1VzYWdlSABSDW1lbW9yaWVzVXNhZ2USagoXcHJpdmFjeV9wYXNzX3BhcmFtZXRlcnMYFiABKA' + 'syMC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlByaXZhY3lQYXNzUGFyYW1ldGVyc0gAUhVw' + 'cml2YWN5UGFzc1BhcmFtZXRlcnMSdwoccHJpdmFjeV9wYXNzX3Rva2VuX3Jlc3BvbnNlcxgXIA' + 'EoCzI0LnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHJpdmFjeVBhc3NUb2tlblJlc3BvbnNl' + 'c0gAUhlwcml2YWN5UGFzc1Rva2VuUmVzcG9uc2VzQgQKAk9rSgQIBxAISgQICxAMQgoKCFJlc3' + 'BvbnNl'); diff --git a/lib/src/services/android_photo_picker.service.dart b/lib/src/services/android_photo_picker.service.dart index d4380fdb..7e17e4e2 100644 --- a/lib/src/services/android_photo_picker.service.dart +++ b/lib/src/services/android_photo_picker.service.dart @@ -16,7 +16,9 @@ class AndroidPhotoPickerService { /// Reads the raw bytes from a content URI using the Android ContentResolver. static Future getUriBytes(String uri) async { try { - final bytes = await _channel.invokeMethod('getUriBytes', {'uri': uri}); + final bytes = await _channel.invokeMethod('getUriBytes', { + 'uri': uri, + }); return bytes; } catch (e) { return null; diff --git a/lib/src/services/api/api.service.dart b/lib/src/services/api/api.service.dart index 3ab2eca6..a5ea9588 100644 --- a/lib/src/services/api/api.service.dart +++ b/lib/src/services/api/api.service.dart @@ -15,6 +15,8 @@ import 'package:flutter/foundation.dart'; import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart'; import 'package:mutex/mutex.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:twonly/core/bridge/api.dart' as rust_api; +import 'package:twonly/core/bridge/api.dart'; import 'package:twonly/core/bridge/wrapper/key_manager.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; @@ -59,6 +61,41 @@ final lockAuthentication = Mutex(); /// errors or network changes. class ApiService { ApiService(); + Future _rustCall(Future promise) async { + try { + final res = await promise; + if (res is ServerResultEmpty) { + final r = res as ServerResultEmpty; + return await r.map( + ok: (_) => Result.success(null), + errorCode: (e) => Result.error(ErrorCode.valueOf(e.field0)), + ); + } else if (res is ServerResultVecU8) { + final r = res as ServerResultVecU8; + return await r.map( + ok: (ok) => Result.success(ok.field0), + errorCode: (e) => Result.error(ErrorCode.valueOf(e.field0)), + ); + } else if (res is ServerResultI64) { + final r = res as ServerResultI64; + return await r.map( + ok: (ok) => Result.success(ok.field0), + errorCode: (e) => Result.error(ErrorCode.valueOf(e.field0)), + ); + } + return Result.success(res); + } catch (e) { + final msg = e.toString(); + final match = RegExp(r'API error code (\\d+)').firstMatch(msg); + if (match != null) { + final code = int.parse(match.group(1)!); + return Result.error(ErrorCode.valueOf(code)); + } + Log.error('Rust API call failed', error: e); + return Result.error(ErrorCode.InternalError); + } + } + final String apiHost = kReleaseMode ? 'api.twonly.eu' : 'dev-api.twonly.eu'; // final String apiHost = kReleaseMode ? 'api.twonly.eu' : 'dev.twonly.eu'; final String apiSecure = kReleaseMode ? 's' : 's'; @@ -680,46 +717,35 @@ class ApiService { } Future getUserById(int userId) async { - final get = ApplicationData_GetUserById()..userId = Int64(userId); - final appData = ApplicationData()..getUserById = get; - final req = createClientToServerFromApplicationData(appData); - final res = await sendRequestSync(req); - if (res.isSuccess) { - final ok = res.value as server.Response_Ok; - if (ok.hasUserdata()) { - return ok.userdata; - } + final result = await _rustCall( + rust_api.RustApi.getUserById(userId: userId), + ); + if (result.isSuccess && result.value != null) { + return server.Response_UserData.fromBuffer(result.value as List); } return null; } Future<(Response_ProofOfWork?, bool)> getProofOfWork() async { - final handshake = Handshake()..requestPOW = Handshake_RequestPOW(); - final req = createClientToServerFromHandshake(handshake); - final result = await sendRequestSync(req, authenticated: false); - if (result.isError) { - Log.error('could not request proof of work params', error: result); - if (result.error == ErrorCode.RegistrationDisabled) { - return (null, true); - } - Log.error('could not request proof of work params', error: result); - return (null, false); + final result = await _rustCall(rust_api.RustApi.getProofOfWork()); + if (result.isSuccess && result.value != null) { + return ( + Response_ProofOfWork.fromBuffer(result.value as List), + false, + ); } - return (result.value.proofOfWork as Response_ProofOfWork, false); + if (result.isError && result.error == ErrorCode.RegistrationDisabled) { + return (null, true); + } + return (null, false); } Future downloadDone(List token) async { - final get = ApplicationData_DownloadDone()..downloadToken = token; - final appData = ApplicationData()..downloadDone = get; - final req = createClientToServerFromApplicationData(appData); - return sendRequestSync(req, ensureRetransmission: true); + return _rustCall(rust_api.RustApi.downloadDone(token: token)); } Future _setLoginToken(List token) async { - final get = ApplicationData_SetLoginToken()..loginToken = token; - final appData = ApplicationData()..setLoginToken = get; - final req = createClientToServerFromApplicationData(appData); - return sendRequestSync(req); + return _rustCall(rust_api.RustApi.setLoginToken(token: token)); } Future requestMemoriesUpload( @@ -934,19 +960,13 @@ class ApiService { } Future reportUser(int userId, String reason) async { - final get = ApplicationData_ReportUser() - ..reportedUserId = Int64(userId) - ..reason = reason; - final appData = ApplicationData()..reportUser = get; - final req = createClientToServerFromApplicationData(appData); - return sendRequestSync(req); + return _rustCall( + rust_api.RustApi.reportUser(userId: userId, reason: reason), + ); } Future deleteAccount() async { - final get = ApplicationData_DeleteAccount(); - final appData = ApplicationData()..deleteAccount = get; - final req = createClientToServerFromApplicationData(appData); - return sendRequestSync(req); + return _rustCall(rust_api.RustApi.deleteAccount()); } Future updateFCMToken(String googleFcm) async { @@ -973,19 +993,11 @@ class ApiService { } Future changeUsername(String username) async { - final get = ApplicationData_ChangeUsername()..username = username; - final appData = ApplicationData()..changeUsername = get; - final req = createClientToServerFromApplicationData(appData); - return sendRequestSync(req); + return _rustCall(rust_api.RustApi.changeUsername(username: username)); } Future forceIpaCheck() async { - final req = createClientToServerFromApplicationData( - ApplicationData( - ipaForceCheck: ApplicationData_IPAForceCheck(), - ), - ); - return sendRequestSync(req); + return _rustCall(rust_api.RustApi.forceIpaCheck()); } Future updateSignedPreKey( diff --git a/lib/src/services/api/client2client/errors.c2c.dart b/lib/src/services/api/client2client/errors.c2c.dart index 4547385b..f7732231 100644 --- a/lib/src/services/api/client2client/errors.c2c.dart +++ b/lib/src/services/api/client2client/errors.c2c.dart @@ -1,6 +1,5 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart' show Value; -import 'package:fixnum/fixnum.dart' show Int64; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart' show IdentityKeyPair; import 'package:twonly/locator.dart'; @@ -51,10 +50,14 @@ Future handleErrorMessage( return; } // Update group state from the server to ensure the user is still part of the group... - final updatedState = await fetchGroupState(group); - if (updatedState != null) { - final (_, state) = updatedState; - final isStillMember = state.memberIds.contains(Int64(fromUserId)); + final updated = await fetchGroupState(group); + if (updated) { + final members = await twonlyDB.groupsDao.getGroupNonLeftMembers( + groupId, + ); + final isStillMember = members.any( + (member) => member.contactId == fromUserId, + ); if (isStillMember) { final keyPair = IdentityKeyPair.fromSerialized( group.myGroupPrivateKey!, diff --git a/lib/src/services/api/client2client/groups.c2c.dart b/lib/src/services/api/client2client/groups.c2c.dart index b4021a7a..b1bc45d8 100644 --- a/lib/src/services/api/client2client/groups.c2c.dart +++ b/lib/src/services/api/client2client/groups.c2c.dart @@ -51,20 +51,13 @@ Future handleGroupCreate( ); } else { // In this case make a group state update and check if the fromUserId is still a admin. otherwise return with an log error message - final updatedState = await fetchGroupState(group); - if (updatedState == null) { + final updated = await fetchGroupState(group); + if (!updated) { Log.error( '[$receiptId] Received group invite/create for $groupId, but failed to fetch group state from server.', ); return; } - final (_, state) = updatedState; - if (!state.adminIds.any((id) => id.toInt() == fromUserId)) { - Log.error( - '[$receiptId] Received group invite/create for $groupId from $fromUserId, but they are not an admin of this group.', - ); - return; - } // User was already in the group, so update leftGroup back to false await twonlyDB.groupsDao.updateGroup( groupId, diff --git a/lib/src/services/api/client2client/messages.c2c.dart b/lib/src/services/api/client2client/messages.c2c.dart index a8f911fd..d6396467 100644 --- a/lib/src/services/api/client2client/messages.c2c.dart +++ b/lib/src/services/api/client2client/messages.c2c.dart @@ -24,7 +24,11 @@ Future handleMessageUpdate( Log.warn('[$receiptId] Error handling messages opened: $e'); } case EncryptedContent_MessageUpdate_Type.DELETE: - if (!await isSender(contactId, messageUpdate.senderMessageId, receiptId)) { + if (!await isSender( + contactId, + messageUpdate.senderMessageId, + receiptId, + )) { return; } Log.info('[$receiptId] Delete message ${messageUpdate.senderMessageId}'); @@ -38,7 +42,11 @@ Future handleMessageUpdate( Log.warn('[$receiptId] Error handling message deletion: $e'); } case EncryptedContent_MessageUpdate_Type.EDIT_TEXT: - if (!await isSender(contactId, messageUpdate.senderMessageId, receiptId)) { + if (!await isSender( + contactId, + messageUpdate.senderMessageId, + receiptId, + )) { return; } Log.info('[$receiptId] Edit message ${messageUpdate.senderMessageId}'); @@ -55,7 +63,11 @@ Future handleMessageUpdate( } } -Future isSender(int fromUserId, String messageId, String receiptId) async { +Future isSender( + int fromUserId, + String messageId, + String receiptId, +) async { final message = await twonlyDB.messagesDao .getMessageById(messageId) .getSingleOrNull(); @@ -63,6 +75,8 @@ Future isSender(int fromUserId, String messageId, String receiptId) async if (message.senderId == fromUserId) { return true; } - Log.error('[$receiptId] Contact $fromUserId tried to modify the message $messageId'); + Log.error( + '[$receiptId] Contact $fromUserId tried to modify the message $messageId', + ); return false; } diff --git a/lib/src/services/api/client2client/text_message.c2c.dart b/lib/src/services/api/client2client/text_message.c2c.dart index 26cbf46a..e6d0e2d0 100644 --- a/lib/src/services/api/client2client/text_message.c2c.dart +++ b/lib/src/services/api/client2client/text_message.c2c.dart @@ -48,7 +48,9 @@ Future handleTextMessage( fromTimestamp(textMessage.timestamp), ); if (message != null) { - Log.info('[$receiptId] Inserted a new text message with ID: ${message.messageId}'); + Log.info( + '[$receiptId] Inserted a new text message with ID: ${message.messageId}', + ); } return message != null; } diff --git a/lib/src/services/api/client2client/user_discovery.c2c.dart b/lib/src/services/api/client2client/user_discovery.c2c.dart index df0f03ec..d784aa56 100644 --- a/lib/src/services/api/client2client/user_discovery.c2c.dart +++ b/lib/src/services/api/client2client/user_discovery.c2c.dart @@ -28,7 +28,9 @@ Future checkForUserDiscoveryChanges( // Only request a new version once per app session return; } - Log.info('[$receiptId] Having old version from contact. Requesting new version.'); + Log.info( + '[$receiptId] Having old version from contact. Requesting new version.', + ); _requestedUpdates.add(fromUserId); await sendCipherText( fromUserId, @@ -67,7 +69,9 @@ Future handleUserDiscoveryRequest( request.currentVersion, ); if (newMessages != null && newMessages.isNotEmpty) { - Log.info('[$receiptId] Sending ${newMessages.length} user discovery messages'); + Log.info( + '[$receiptId] Sending ${newMessages.length} user discovery messages', + ); await sendCipherText( fromUserId, EncryptedContent( @@ -78,7 +82,9 @@ Future handleUserDiscoveryRequest( blocking: false, ); } else { - Log.info('[$receiptId] Got update request, but there are no new updates for the user'); + Log.info( + '[$receiptId] Got update request, but there are no new updates for the user', + ); } } @@ -91,7 +97,9 @@ Future handleUserDiscoveryUpdate( Log.warn('[$receiptId] Got a user discovery update while it is disabled'); return; } - Log.info('[$receiptId] Got ${update.messages.length} user discovery messages'); + Log.info( + '[$receiptId] Got ${update.messages.length} user discovery messages', + ); await UserDiscoveryService.handleNewMessages( fromUserId, update.messages.map(Uint8List.fromList).toList(), diff --git a/lib/src/services/api/messages.api.dart b/lib/src/services/api/messages.api.dart index 9c2aa47d..08c75586 100644 --- a/lib/src/services/api/messages.api.dart +++ b/lib/src/services/api/messages.api.dart @@ -1,264 +1,30 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; +import 'dart:typed_data'; -import 'package:clock/clock.dart'; -import 'package:drift/drift.dart'; -import 'package:fixnum/fixnum.dart'; -import 'package:mutex/mutex.dart'; -import 'package:twonly/locator.dart'; -import 'package:twonly/src/database/daos/contacts.dao.dart'; -import 'package:twonly/src/database/tables/contacts.table.dart'; -import 'package:twonly/src/database/tables/messages.table.dart'; -import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; -import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart' + as frb; +import 'package:twonly/core/bridge/api.dart' as rust_api; +import 'package:twonly/src/database/twonly.db.dart' show Receipt; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' as pb; -import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart'; -import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; -import 'package:twonly/src/services/signal/encryption.signal.dart'; -import 'package:twonly/src/services/signal/session.signal.dart'; -import 'package:twonly/src/services/user.service.dart' show UserService; -import 'package:twonly/src/services/user_discovery.service.dart'; -import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/misc.dart'; -final lockRetransmission = Mutex(); +// Compatibility adapters. All messaging state and behavior lives in Rust. +Future retransmitAllMessages() => + rust_api.RustApi.retransmitAllMessages(); -Future retransmitAllMessages() async { - return lockRetransmission.protect(() async { - final receipts = await twonlyDB.receiptsDao.getReceiptsForRetransmission(); - - if (receipts.isEmpty) return; - - Log.info('Reuploading ${receipts.length} messages to the server.'); - - final contacts = {}; - - for (final receipt in receipts) { - if (receipt.markForRetryAfterAccepted != null) { - if (!contacts.containsKey(receipt.contactId)) { - final contact = await twonlyDB.contactsDao - .getContactByUserId(receipt.contactId) - .getSingleOrNull(); - if (contact == null) { - Log.error( - 'Contact does not exists, but has a record in receipts, this should not be possible, because of the DELETE CASCADE relation.', - ); - continue; - } - contacts[receipt.contactId] = contact; - } - if (!(contacts[receipt.contactId]?.accepted ?? true)) { - Log.warn( - 'Could not send message as contact has still not yet accepted.', - ); - continue; - } - } - await tryToSendCompleteMessage(receipt: receipt); - } - }); -} - -final Map _tryToSendLocks = {}; - -// When the ackByServerAt is set this value is written in the receipted Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({ String? receiptId, Receipt? receipt, bool onlyReturnEncryptedData = false, bool blocking = true, }) async { - final rId = receiptId ?? receipt?.receiptId; - if (rId == null) { - Log.error( - 'Cannot try to send complete message as both receiptId and receipt are null.', - ); + final id = receiptId ?? receipt?.receiptId; + if (id == null) return null; + if (!onlyReturnEncryptedData) { + await rust_api.RustApi.sendQueuedMessage(receiptId: id); return null; } - - final mutex = _tryToSendLocks.putIfAbsent(rId, Mutex.new); - return mutex.protect(() async { - return _tryToSendCompleteMessageInternal( - receiptId: receiptId, - receipt: receipt, - onlyReturnEncryptedData: onlyReturnEncryptedData, - blocking: blocking, - ); - }); -} - -Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({ - String? receiptId, - Receipt? receipt, - bool onlyReturnEncryptedData = false, - bool blocking = true, -}) async { - // this should have a lock for every receiptID, split the function into a _internal withou the lock and a normal with the lock - if (apiService.appIsOutdated) return null; - if (receiptId == null && receipt == null) return null; - - try { - final targetReceiptId = receipt?.receiptId ?? receiptId!; - final loadedReceipt = await twonlyDB.receiptsDao.getReceiptById( - targetReceiptId, - ); - if (loadedReceipt == null) { - Log.info( - '[$targetReceiptId] Receipt not found (might have been processed or deleted).', - ); - return null; - } - // ignore: parameter_assignments - receipt = loadedReceipt; - - final contact = await twonlyDB.contactsDao.getContactById( - receipt.contactId, - ); - if (contact == null || contact.accountDeleted) { - Log.warn('Will not send message again as user does not exist anymore.'); - await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId); - return null; - } - - if (!onlyReturnEncryptedData && - receipt.ackByServerAt != null && - receipt.markForRetry == null) { - Log.info('Message already uploaded and mark for retry is not set.'); - return null; - } - - final message = pb.Message.fromBuffer(receipt.message) - ..receiptId = receipt.receiptId; - - final encryptedContent = pb.EncryptedContent.fromBuffer( - message.encryptedContent, - ); - - Uint8List? pushData; - if (receipt.retryCount == 0) { - final pushNotification = await getPushNotificationFromEncryptedContent( - receipt.contactId, - receipt.messageId, - encryptedContent, - ); - - if (pushNotification != null) { - // Only show the push notification the first two time. - pushData = await encryptPushNotification( - receipt.contactId, - pushNotification, - ); - } - } - - if (message.type == pb.Message_Type.TEST_NOTIFICATION) { - pushData = (PushNotification()..kind = PushKind.TEST_NOTIFICATION) - .writeToBuffer(); - } - - if (message.type == pb.Message_Type.CIPHERTEXT) { - final encryptResult = await signalEncryptMessage( - receipt.contactId, - Uint8List.fromList(message.encryptedContent), - ); - if (encryptResult == null) { - Log.error( - '[${receipt.receiptId}] Could not encrypt the message for user ${receipt.contactId}. Aborting and trying again.', - ); - if (receipt.messageId != null) { - await twonlyDB.messagesDao.handleMessageAckByServer( - receipt.contactId, - receipt.messageId!, - clock.now(), - ); - } - await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId); - return null; - } - message - ..encryptedContent = encryptResult.ciphertext - ..type = encryptResult.type; - } else if (message.type == pb.Message_Type.CIPHERTEXT_V2) { - final encryptResult = await signalEncryptMessageV2( - receipt.contactId, - Uint8List.fromList(message.encryptedContent), - ); - if (encryptResult == null) { - Log.error( - '[${receipt.receiptId}] Could not encrypt the message (V2) for user ${receipt.contactId}. Aborting and trying again.', - ); - if (receipt.messageId != null) { - await twonlyDB.messagesDao.handleMessageAckByServer( - receipt.contactId, - receipt.messageId!, - clock.now(), - ); - } - await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId); - return null; - } - message - ..encryptedContent = encryptResult.ciphertext - ..type = encryptResult.type; - } - - if (onlyReturnEncryptedData) { - Log.info('Returning message with receiptID ${receipt.receiptId}.'); - return (message.writeToBuffer(), pushData); - } - - Log.info('Uploading message with receiptID ${receipt.receiptId}.'); - - final resp = await apiService.sendTextMessage( - receipt.contactId, - message.writeToBuffer(), - pushData, - ); - - if (resp.isError) { - Log.warn('Could not transmit ${receipt.receiptId} got ${resp.error}.'); - if (resp.error == ErrorCode.UserIdNotFound) { - await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId); - await twonlyDB.contactsDao.updateContact( - receipt.contactId, - const ContactsCompanion(accountDeleted: Value(true)), - ); - return null; - } - } - - if (resp.isSuccess) { - if (receipt.messageId != null) { - await twonlyDB.messagesDao.handleMessageAckByServer( - receipt.contactId, - receipt.messageId!, - clock.now(), - ); - } - if (!receipt.contactWillSendsReceipt) { - await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId); - } else { - await twonlyDB.receiptsDao.updateReceipt( - receipt.receiptId, - ReceiptsCompanion( - ackByServerAt: Value(clock.now()), - retryCount: Value(receipt.retryCount + 1), - lastRetry: Value(clock.now()), - markForRetry: const Value(null), - ), - ); - } - } - } catch (e) { - Log.error('[$receiptId] unknown error when sending message: $e'); - if (receipt != null) { - await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId); - } - } - return null; + final prepared = await rust_api.RustApi.prepareQueuedMessage(receiptId: id); + return prepared == null ? null : (prepared.message, prepared.pushData); } Future insertAndSendTextMessage( @@ -266,41 +32,10 @@ Future insertAndSendTextMessage( String textMessage, String? quotesMessageId, ) async { - await twonlyDB.groupsDao.updateGroup( - groupId, - const GroupsCompanion( - draftMessage: Value(null), - ), - ); - final message = await twonlyDB.messagesDao.insertMessage( - MessagesCompanion( - groupId: Value(groupId), - content: Value(textMessage), - type: Value(MessageType.text.name), - quotesMessageId: Value(quotesMessageId), - ), - ); - if (message == null) { - Log.error('Could not insert message into database'); - return; - } - - final encryptedContent = pb.EncryptedContent( - textMessage: pb.EncryptedContent_TextMessage( - senderMessageId: message.messageId, - text: textMessage, - timestamp: Int64(message.createdAt.millisecondsSinceEpoch), - ), - ); - - if (quotesMessageId != null) { - encryptedContent.textMessage.quoteMessageId = quotesMessageId; - } - - await sendCipherTextToGroup( - groupId, - encryptedContent, - messageId: message.messageId, + await rust_api.RustApi.insertAndSendText( + groupId: groupId, + text: textMessage, + quoteMessageId: quotesMessageId, ); } @@ -308,54 +43,9 @@ Future insertAndSendContactShareMessage( String groupId, List contactsToShare, ) async { - final contacts = []; - - for (final contactId in contactsToShare) { - final contact = await twonlyDB.contactsDao.getContactById(contactId); - if (contact != null) { - final publicIdentityKey = await getPublicKeyFromContact(contactId); - - contacts.add( - SharedContact( - userId: Int64(contact.userId), - publicIdentityKey: publicIdentityKey, - displayName: getContactDisplayName(contact), - ), - ); - } - } - - final additionalMessageData = AdditionalMessageData( - type: AdditionalMessageData_Type.CONTACTS, - contacts: contacts, - ); - - final message = await twonlyDB.messagesDao.insertMessage( - MessagesCompanion( - groupId: Value(groupId), - type: Value(MessageType.contacts.name), - additionalMessageData: Value(additionalMessageData.writeToBuffer()), - ), - ); - - if (message == null) { - Log.error('Could not insert message into database'); - return; - } - - final encryptedContent = pb.EncryptedContent( - additionalDataMessage: pb.EncryptedContent_AdditionalDataMessage( - senderMessageId: message.messageId, - additionalMessageData: additionalMessageData.writeToBuffer(), - timestamp: Int64(message.createdAt.millisecondsSinceEpoch), - type: MessageType.contacts.name, - ), - ); - - await sendCipherTextToGroup( - groupId, - encryptedContent, - messageId: message.messageId, + await rust_api.RustApi.insertAndSendContactShare( + groupId: groupId, + contactIds: frb.Int64List.fromList(contactsToShare), ); } @@ -363,47 +53,9 @@ Future insertAndSendAskAboutUserMessage( int contactId, int askAboutUserId, ) async { - final directChat = await twonlyDB.groupsDao.createOrGetDirectChat(contactId); - if (directChat == null) { - Log.error( - 'Failed to get or create direct chat group for contact $contactId', - ); - return; - } - - final groupId = directChat.groupId; - - final additionalMessageData = AdditionalMessageData( - type: AdditionalMessageData_Type.ASK_ABOUT_USER, - askAboutUserId: Int64(askAboutUserId), - ); - - final message = await twonlyDB.messagesDao.insertMessage( - MessagesCompanion( - groupId: Value(groupId), - type: Value(MessageType.askAboutUser.name), - additionalMessageData: Value(additionalMessageData.writeToBuffer()), - ), - ); - - if (message == null) { - Log.error('Could not insert message into database'); - return; - } - - final encryptedContent = pb.EncryptedContent( - additionalDataMessage: pb.EncryptedContent_AdditionalDataMessage( - senderMessageId: message.messageId, - additionalMessageData: additionalMessageData.writeToBuffer(), - timestamp: Int64(message.createdAt.millisecondsSinceEpoch), - type: MessageType.askAboutUser.name, - ), - ); - - await sendCipherTextToGroup( - groupId, - encryptedContent, - messageId: message.messageId, + await rust_api.RustApi.insertAndSendAskAboutUser( + contactId: contactId, + askAboutUserId: askAboutUserId, ); } @@ -413,27 +65,12 @@ Future sendCipherTextToGroup( String? messageId, bool onlySendIfNoReceiptsAreOpen = false, }) async { - final groupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(groupId); - - if (messageId != null || - encryptedContent.hasReaction() || - encryptedContent.hasMedia() || - encryptedContent.hasTextMessage()) { - // only update the counter in case this is a actual message - await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now()); - } - - encryptedContent.groupId = groupId; - - for (final groupMember in groupMembers) { - await sendCipherText( - groupMember.contactId, - encryptedContent, - messageId: messageId, - blocking: false, - onlySendIfNoReceiptsAreOpen: onlySendIfNoReceiptsAreOpen, - ); - } + await rust_api.RustApi.sendEncryptedContentToGroup( + groupId: groupId, + content: encryptedContent.writeToBuffer(), + messageId: messageId, + onlySendIfNoReceiptsAreOpen: onlySendIfNoReceiptsAreOpen, + ); } Future<(Uint8List, Uint8List?)?> sendCipherText( @@ -444,190 +81,28 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( String? messageId, bool onlySendIfNoReceiptsAreOpen = false, }) async { - if (onlySendIfNoReceiptsAreOpen) { - final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact( - contactId, - ); - if (openReceipts > 10) { - // this prevents that these types of messages are send in case the receiver is offline - return null; - } - } - encryptedContent.senderProfileCounter = Int64( - userService.currentUser.avatarCounter, + final prepared = await rust_api.RustApi.sendEncryptedContent( + contactId: contactId, + content: encryptedContent.writeToBuffer(), + messageId: messageId, + onlySendIfNoReceiptsAreOpen: onlySendIfNoReceiptsAreOpen, + onlyReturnEncryptedData: onlyReturnEncryptedData, + blocking: blocking, ); - - { - if (userService.currentUser.askForFriendPromotions) { - final contacts = await twonlyDB.contactsDao.getAllContacts(); - final contactCount = contacts.where((c) => c.accepted).length; - if (contactCount > 5) { - await UserService.update((u) { - u.askForFriendPromotions = false; - }); - } else { - encryptedContent.askForFriendPromotions = true; - } - } - - if (userService.currentUser.isUserDiscoveryEnabled && messageId != null) { - final contact = await twonlyDB.contactsDao.getContactById(contactId); - if (UserDiscoveryService.isContactAllowed(contact)) { - final version = await UserDiscoveryService.getCurrentVersion(); - if (version != null) { - encryptedContent.senderUserDiscoveryVersion = version; - } - } - } - } - - final contact = await twonlyDB.contactsDao.getContactById(contactId); - final isV2 = contact?.signalVersion == SignalVersion.v2; - - final response = pb.Message() - ..type = isV2 ? pb.Message_Type.CIPHERTEXT_V2 : pb.Message_Type.CIPHERTEXT - ..encryptedContent = encryptedContent.writeToBuffer(); - - var retryCounter = 0; - DateTime? lastRetry; - - if (messageId != null) { - final receipts = await twonlyDB.receiptsDao - .getReceiptsByContactAndMessageId(contactId, messageId); - - for (final receipt in receipts) { - if (receipt.lastRetry != null) { - lastRetry = receipt.lastRetry; - } - retryCounter += 1; - Log.info('Removing duplicated receipt for message $messageId'); - await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId); - } - } - - final receipt = await twonlyDB.receiptsDao.insertReceipt( - ReceiptsCompanion( - contactId: Value(contactId), - message: Value(response.writeToBuffer()), - messageId: Value(messageId), - willBeRetriedByMediaUpload: Value(onlyReturnEncryptedData), - retryCount: Value(retryCounter), - lastRetry: Value(lastRetry), - ), - ); - - if (receipt != null) { - try { - final typeKeys = _getEncryptedContentTypes(encryptedContent); - Log.info( - 'sendCipherText: type=[$typeKeys] messageId=$messageId receiptId=${receipt.receiptId}', - ); - } catch (_) { - Log.info( - 'sendCipherText: messageId=$messageId receiptId=${receipt.receiptId}', - ); - } - - final tmp = tryToSendCompleteMessage( - receipt: receipt, - onlyReturnEncryptedData: onlyReturnEncryptedData, - blocking: blocking, - ); - if (!blocking) { - return null; - } - return tmp; - } - return null; + if (!onlyReturnEncryptedData || prepared == null) return null; + return (prepared.message, prepared.pushData); } -Future sendTypingIndication(String groupId, bool isTyping) async { - if (!userService.currentUser.typingIndicators) return; - await sendCipherTextToGroup( - groupId, - pb.EncryptedContent( - typingIndicator: pb.EncryptedContent_TypingIndicator( - isTyping: isTyping, - createdAt: Int64(clock.now().millisecondsSinceEpoch), - ), - ), - onlySendIfNoReceiptsAreOpen: true, - ); -} +Future sendTypingIndication(String groupId, bool isTyping) => + rust_api.RustApi.sendTyping(groupId: groupId, isTyping: isTyping); Future notifyContactAboutOpeningMessage( int contactId, List messageOtherIds, -) async { - var biggestMessageId = messageOtherIds.first; +) => rust_api.RustApi.notifyMessagesOpened( + contactId: contactId, + messageIds: messageOtherIds, +); - for (final messageOtherId in messageOtherIds) { - if (isUUIDNewer(messageOtherId, biggestMessageId)) { - biggestMessageId = messageOtherId; - } - } - Log.info('Opened messages: $messageOtherIds'); - - final actionAt = clock.now(); - - await sendCipherText( - contactId, - pb.EncryptedContent( - messageUpdate: pb.EncryptedContent_MessageUpdate( - type: pb.EncryptedContent_MessageUpdate_Type.OPENED, - multipleTargetMessageIds: messageOtherIds, - timestamp: Int64(actionAt.millisecondsSinceEpoch), - ), - ), - blocking: false, - ); - await twonlyDB.batch((batch) { - for (final messageId in messageOtherIds) { - batch.update( - twonlyDB.messages, - MessagesCompanion( - openedAt: Value(actionAt), - openedByAll: Value(actionAt), - ), - where: (tbl) => tbl.messageId.equals(messageId), - ); - } - }); - await updateLastMessageId(contactId, biggestMessageId); -} - -Future sendContactMyProfileData(int contactId) async { - List? avatarSvgCompressed; - if (userService.currentUser.avatarSvg != null) { - avatarSvgCompressed = gzip.encode( - utf8.encode(userService.currentUser.avatarSvg!), - ); - } - final encryptedContent = pb.EncryptedContent( - contactUpdate: pb.EncryptedContent_ContactUpdate( - type: pb.EncryptedContent_ContactUpdate_Type.UPDATE, - avatarSvgCompressed: avatarSvgCompressed, - displayName: userService.currentUser.displayName, - username: userService.currentUser.username, - ), - ); - await sendCipherText(contactId, encryptedContent, blocking: false); -} - -String _getEncryptedContentTypes(pb.EncryptedContent content) { - final ignoredFields = { - 'groupId', - 'isDirectChat', - 'senderProfileCounter', - 'senderUserDiscoveryVersion', - }; - - final types = []; - for (final field in content.info_.byName.values) { - if (content.hasField(field.tagNumber) && - !ignoredFields.contains(field.name)) { - types.add(field.name); - } - } - return types.join(', '); -} +Future sendContactMyProfileData(int contactId) => + rust_api.RustApi.sendContactProfile(contactId: contactId); diff --git a/lib/src/services/flame.service.dart b/lib/src/services/flame.service.dart index a0396237..d7613e38 100644 --- a/lib/src/services/flame.service.dart +++ b/lib/src/services/flame.service.dart @@ -65,18 +65,23 @@ Future syncFlameCounters({String? forceForGroup}) async { ({int counter, bool isExpiring}) getFlameCounterFromGroup(Group? group) { const zero = (counter: 0, isExpiring: false); if (group == null) return zero; - if (group.lastMessageSend == null || group.lastMessageReceived == null || group.lastFlameCounterChange == null) { + if (group.lastMessageSend == null || + group.lastMessageReceived == null || + group.lastFlameCounterChange == null) { return zero; } final now = clock.now(); final startOfToday = DateTime(now.year, now.month, now.day); final twoDaysAgo = startOfToday.subtract(const Duration(days: 2)); final oneDayAgo = startOfToday.subtract(const Duration(days: 1)); - if (group.lastMessageSend!.isAfter(twoDaysAgo) && group.lastMessageReceived!.isAfter(twoDaysAgo) || + if (group.lastMessageSend!.isAfter(twoDaysAgo) && + group.lastMessageReceived!.isAfter(twoDaysAgo) || group.lastFlameCounterChange!.isAfter(oneDayAgo)) { // Flame is expiring when today no exchange has happened yet: // both lastMessageSend and lastMessageReceived are before startOfToday. - final isExpiring = group.lastMessageSend!.isBefore(oneDayAgo) || group.lastMessageReceived!.isBefore(oneDayAgo); + final isExpiring = + group.lastMessageSend!.isBefore(oneDayAgo) || + group.lastMessageReceived!.isBefore(oneDayAgo); return (counter: group.flameCounter, isExpiring: isExpiring); } else { return zero; @@ -117,7 +122,8 @@ Future incFlameCounter( final now = clock.now(); final startOfToday = DateTime(now.year, now.month, now.day); final twoDaysAgo = startOfToday.subtract(const Duration(days: 2)); - if (group.lastMessageSend!.isBefore(twoDaysAgo) || group.lastMessageReceived!.isBefore(twoDaysAgo)) { + if (group.lastMessageSend!.isBefore(twoDaysAgo) || + group.lastMessageReceived!.isBefore(twoDaysAgo)) { flameCounter = 0; } } @@ -129,21 +135,25 @@ Future incFlameCounter( final now = clock.now(); final startOfToday = DateTime(now.year, now.month, now.day); - if (group.lastFlameCounterChange == null || group.lastFlameCounterChange!.isBefore(startOfToday)) { + if (group.lastFlameCounterChange == null || + group.lastFlameCounterChange!.isBefore(startOfToday)) { // last flame update was yesterday. check if it can be updated. var updateFlame = false; if (received) { - if (group.lastMessageSend != null && group.lastMessageSend!.isAfter(startOfToday)) { + if (group.lastMessageSend != null && + group.lastMessageSend!.isAfter(startOfToday)) { // today a message was already send -> update flame updateFlame = true; } - } else if (group.lastMessageReceived != null && group.lastMessageReceived!.isAfter(startOfToday)) { + } else if (group.lastMessageReceived != null && + group.lastMessageReceived!.isAfter(startOfToday)) { // today a message was already received -> update flame updateFlame = true; } if (updateFlame) { flameCounter += 1; - if (group.lastFlameCounterChange == null || group.lastFlameCounterChange!.isBefore(timestamp)) { + if (group.lastFlameCounterChange == null || + group.lastFlameCounterChange!.isBefore(timestamp)) { // only update if the timestamp is newer lastFlameCounterChange = Value(timestamp); } @@ -160,11 +170,13 @@ Future incFlameCounter( } if (received) { - if (group.lastMessageReceived == null || group.lastMessageReceived!.isBefore(timestamp)) { + if (group.lastMessageReceived == null || + group.lastMessageReceived!.isBefore(timestamp)) { lastMessageReceived = Value(timestamp); } } else { - if (group.lastMessageSend == null || group.lastMessageSend!.isBefore(timestamp)) { + if (group.lastMessageSend == null || + group.lastMessageSend!.isBefore(timestamp)) { lastMessageSend = Value(timestamp); } } diff --git a/lib/src/services/group.service.dart b/lib/src/services/group.service.dart index 1613b525..0dd2c648 100644 --- a/lib/src/services/group.service.dart +++ b/lib/src/services/group.service.dart @@ -1,970 +1,55 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:math'; +import 'dart:typed_data' show Uint8List; -import 'package:clock/clock.dart'; -import 'package:collection/collection.dart'; -import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; -import 'package:cryptography_plus/cryptography_plus.dart'; -import 'package:drift/drift.dart' show Value; -import 'package:fixnum/fixnum.dart'; -import 'package:flutter/foundation.dart'; -import 'package:hashlib/random.dart'; -import 'package:http/http.dart' as http; -import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -// ignore: implementation_imports -import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart'; -import 'package:twonly/locator.dart'; -import 'package:twonly/src/database/tables/groups.table.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart' + show Int64List; +import 'package:twonly/core/bridge/groups.dart' as rust_groups; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart'; -import 'package:twonly/src/model/protobuf/client/generated/groups.pb.dart'; -import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'; -import 'package:twonly/src/services/api/messages.api.dart'; -import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; -import 'package:twonly/src/services/signal/session.signal.dart'; -import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/misc.dart'; -String getGroupStateUrl() { - return 'http${apiService.apiSecure}://${apiService.apiHost}/api/group/state'; -} - -String getGroupChallengeUrl() { - return 'http${apiService.apiSecure}://${apiService.apiHost}/api/group/challenge'; -} - -Future createNewGroup(String groupName, List members) async { - // First: Upload new State to the server..... - // if (groupName) return; - - final groupId = uuid.v4(); - - final memberIds = members.map((x) => Int64(x.userId)).toList(); - - final groupState = EncryptedGroupState( - memberIds: [Int64(userService.currentUser.userId)] + memberIds, - adminIds: [Int64(userService.currentUser.userId)], - groupName: groupName, - deleteMessagesAfterMilliseconds: Int64( - defaultDeleteMessagesAfterMilliseconds, - ), - padding: List.generate(Random().nextInt(80), (_) => 0), - ); - - final stateEncryptionKey = getRandomUint8List(32); - final chacha20 = FlutterChacha20.poly1305Aead(); - final encryptionNonce = chacha20.newNonce(); - - final secretBox = await chacha20.encrypt( - groupState.writeToBuffer(), - secretKey: SecretKey(stateEncryptionKey), - nonce: encryptionNonce, - ); - - final encryptedGroupState = EncryptedGroupStateEnvelop( - nonce: encryptionNonce, - encryptedGroupState: secretBox.cipherText, - mac: secretBox.mac.bytes, - ); - - final myGroupKey = generateIdentityKeyPair(); - - { - // Upload the group state, if this fails, the group can not be created. - - final newGroupState = NewGroupState( - groupId: groupId, - versionId: Int64(1), - encryptedGroupState: encryptedGroupState.writeToBuffer(), - publicKey: myGroupKey.getPublicKey().serialize(), - ); - - final response = await http - .post( - Uri.parse(getGroupStateUrl()), - body: newGroupState.writeToBuffer(), - ) - .timeout(const Duration(seconds: 10)); - - if (response.statusCode != 200) { - Log.error( - 'Could not upload group state. Got status code ${response.statusCode} from server.', - ); - return false; - } - } - - final group = await twonlyDB.groupsDao.createNewGroup( - GroupsCompanion( - groupId: Value(groupId), - groupName: Value(groupName), - isGroupAdmin: const Value(true), - stateEncryptionKey: Value(stateEncryptionKey), - stateVersionId: const Value(1), - myGroupPrivateKey: Value(myGroupKey.serialize()), - joinedGroup: const Value(true), - ), - ); - - if (group == null) { - Log.error('Could not insert group into database.'); - return false; - } - - Log.info('Created new group: ${group.groupId}'); - - for (final member in members) { - await twonlyDB.groupsDao.insertOrUpdateGroupMember( - GroupMembersCompanion( - groupId: Value(group.groupId), - contactId: Value(member.userId), - memberState: const Value(MemberState.normal), - ), - ); - } - - await twonlyDB.groupsDao.insertGroupAction( - GroupHistoriesCompanion( - groupId: Value(groupId), - type: const Value(GroupActionType.createdGroup), - ), - ); - - // Notify members about the new group :) - - await sendCipherTextToGroup( - group.groupId, - EncryptedContent( - groupCreate: EncryptedContent_GroupCreate( - stateKey: stateEncryptionKey, - groupPublicKey: myGroupKey.getPublicKey().serialize(), - groupName: group.groupName, - ), - ), - ); - - return true; -} - -Future fetchGroupStatesForUnjoinedGroups() async { - final groups = await twonlyDB.groupsDao.getAllNotJoinedGroups(); - - for (final group in groups) { - await fetchGroupState(group); - } -} - -Future fetchMissingGroupPublicKey() async { - final members = await twonlyDB.groupsDao.getAllGroupMemberWithoutPublicKey(); - - for (final member in members) { - if (member.lastMessage == null) continue; - // only request if the users has send a message in the last two days. - if (member.lastMessage!.isAfter( - clock.now().subtract(const Duration(days: 2)), - )) { - await sendCipherText( - member.contactId, - EncryptedContent( - groupId: member.groupId, - resendGroupPublicKey: EncryptedContent_ResendGroupPublicKey(), - ), - ); - } - } -} - -Future?> _decryptEnvelop( - Group group, - List encryptedGroupState, -) async { - try { - final envelope = EncryptedGroupStateEnvelop.fromBuffer( - encryptedGroupState, - ); - final chacha20 = FlutterChacha20.poly1305Aead(); - - final secretBox = SecretBox( - envelope.encryptedGroupState, - nonce: envelope.nonce, - mac: Mac(envelope.mac), - ); - - final encryptedGroupStateRaw = await chacha20.decrypt( - secretBox, - secretKey: SecretKey(group.stateEncryptionKey!), - ); - - return encryptedGroupStateRaw; - } catch (e) { - Log.error(e); - return null; - } -} - -Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { - try { - var isSuccess = true; - - final response = await http - .get( - Uri.parse('${getGroupStateUrl()}/${group.groupId}'), - ) - .timeout(const Duration(seconds: 10)); - - if (response.statusCode != 200) { - if (response.statusCode == 404) { - // group does not exists any more. - await twonlyDB.groupsDao.updateGroup( - group.groupId, - const GroupsCompanion( - leftGroup: Value(true), - ), - ); - } - Log.error( - 'Could not load group state. Got status code ${response.statusCode} from server.', - ); - return null; - } - - final groupStateServer = GroupState.fromBuffer(response.bodyBytes); - - final encryptedStateRaw = await _decryptEnvelop( - group, - groupStateServer.encryptedGroupState, - ); - if (encryptedStateRaw == null) return null; - - final encryptedGroupState = EncryptedGroupState.fromBuffer( - encryptedStateRaw, - ); - - if (group.stateVersionId >= groupStateServer.versionId.toInt()) { - Log.info( - 'Group ${group.groupId} has already newest group state from the server!', - ); - } - - final memberIds = List.from(encryptedGroupState.memberIds); - final adminIds = List.from(encryptedGroupState.adminIds); - - for (final appendedState in groupStateServer.appendedGroupStates) { - final identityKey = IdentityKey.fromBytes( - Uint8List.fromList(appendedState.appendTBS.publicKey), - 0, - ); - - final valid = Curve.verifySignature( - identityKey.publicKey, - appendedState.appendTBS.writeToBuffer(), - Uint8List.fromList(appendedState.signature), - ); - - if (!valid) { - Log.error('Invalid signature for the appendedState'); - continue; - } - - final encryptedStateRaw = await _decryptEnvelop( - group, - appendedState.appendTBS.encryptedGroupStateAppend, - ); - if (encryptedStateRaw == null) continue; - - final appended = EncryptedAppendedGroupState.fromBuffer( - encryptedStateRaw, - ); - if (appended.type == EncryptedAppendedGroupState_Type.LEFT_GROUP) { - final keyPair = IdentityKeyPair.fromSerialized( - group.myGroupPrivateKey!, - ); - - final appendedPubKey = appendedState.appendTBS.publicKey; - final myPubKey = keyPair.getPublicKey().serialize().toList(); - - if (listEquals(appendedPubKey, myPubKey)) { - adminIds.remove(Int64(userService.currentUser.userId)); - memberIds.remove( - Int64(userService.currentUser.userId), - ); // -> Will remove the user later... - } else { - Log.info('A non admin left the group!!!'); - - final member = await twonlyDB.groupsDao.getGroupMemberByPublicKey( - Uint8List.fromList(appendedPubKey), - ); - if (member == null) { - Log.error('Member is already not in this group...'); - continue; - } - adminIds.remove(Int64(member.contactId)); - memberIds.remove(Int64(member.contactId)); - } - } - } - - if (!memberIds.contains(Int64(userService.currentUser.userId))) { - // OH no, I am no longer a member of this group... - // Return from the group... - await twonlyDB.groupsDao.updateGroup( - group.groupId, - const GroupsCompanion( - leftGroup: Value(true), - ), - ); - return (groupStateServer.versionId.toInt(), encryptedGroupState); - } - - final isGroupAdmin = - adminIds.firstWhereOrNull( - (t) => t.toInt() == userService.currentUser.userId, - ) != - null; - - if (!listEquals(memberIds, encryptedGroupState.memberIds)) { - if (isGroupAdmin) { - try { - // this removes the appended_group_state from the server and merges the changes into the main group state - final newState = EncryptedGroupState( - groupName: encryptedGroupState.groupName, - deleteMessagesAfterMilliseconds: - encryptedGroupState.deleteMessagesAfterMilliseconds, - memberIds: memberIds, - adminIds: adminIds, - padding: List.generate(Random().nextInt(80), (_) => 0), - ); - // send new state to the server - if (!await _updateGroupState( - group, - newState, - versionId: groupStateServer.versionId.toInt() + 1, - )) { - // could not update the group state... - Log.error('Update the state to remove the appended state...'); - return null; - } - // the state is now updated and the appended_group_state should be removed on the server, so just call this - // function again, to sync the local database - return await fetchGroupState(group); - } catch (e) { - Log.error(e); - return null; - } - } - // in case this is not an admin, just work with the new memberIds and adminIds... - } - - await twonlyDB.groupsDao.updateGroup( - group.groupId, - GroupsCompanion( - groupName: Value(encryptedGroupState.groupName), - deleteMessagesAfterMilliseconds: Value( - encryptedGroupState.deleteMessagesAfterMilliseconds.toInt(), - ), - isGroupAdmin: Value(isGroupAdmin), +Future createNewGroup(String groupName, List members) => + rust_groups.createNewGroup( + groupName: groupName, + memberIds: Int64List.fromList( + members.map((contact) => contact.userId).toList(), ), ); - var currentGroupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers( - group.groupId, - ); - - // First find and insert NEW members - for (final memberId in memberIds) { - if (memberId == Int64(userService.currentUser.userId)) { - continue; - } - if (currentGroupMembers.any((t) => t.contactId == memberId.toInt())) { - // User is already in the database - continue; - } - Log.info('New member in the GROUP state: $memberId'); - - var inContacts = true; - - if (await twonlyDB.contactsDao.getContactById(memberId.toInt()) == null) { - // User is not yet in the contacts, add him in the hidden. So he is not in the contact list / needs to be - // requested separately. - if (!await addNewHiddenContact(memberId.toInt())) { - Log.error('Could not request member ID will retry later.'); - isSuccess = false; - inContacts = false; - } - } - if (inContacts) { - // User is already a contact, so just add him to the group members list - await twonlyDB.groupsDao.insertOrUpdateGroupMember( - GroupMembersCompanion( - groupId: Value(group.groupId), - contactId: Value(memberId.toInt()), - memberState: const Value(MemberState.normal), - ), - ); - } - - // Send the new user my public group key - if (group.myGroupPrivateKey != null) { - final keyPair = IdentityKeyPair.fromSerialized( - group.myGroupPrivateKey!, - ); - await sendCipherText( - memberId.toInt(), - EncryptedContent( - groupJoin: EncryptedContent_GroupJoin( - groupPublicKey: keyPair.getPublicKey().serialize(), - ), - ), - ); - } - } - - // check if there is a member which is not in the server list... - - // update the current members list - currentGroupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers( - group.groupId, - ); - - for (final member in currentGroupMembers) { - // Member is not any more in the members list - if (!encryptedGroupState.memberIds.contains(Int64(member.contactId))) { - await twonlyDB.groupsDao.removeMember(group.groupId, member.contactId); - continue; - } - - MemberState? newMemberState; - - if (adminIds.contains(Int64(member.contactId))) { - if (member.memberState == MemberState.normal) { - // user was promoted - newMemberState = MemberState.admin; - } - } else if (member.memberState == MemberState.admin) { - // user was demoted - newMemberState = MemberState.normal; - } - - if (newMemberState != null) { - await twonlyDB.groupsDao.updateMember( - group.groupId, - member.contactId, - GroupMembersCompanion( - memberState: Value(newMemberState), - ), - ); - } - } - - if (isSuccess) { - // in case not all members could be loaded from the server, - // this will ensure it will be tried again later - await twonlyDB.groupsDao.updateGroup( - group.groupId, - GroupsCompanion( - stateVersionId: Value(groupStateServer.versionId.toInt()), - joinedGroup: const Value(true), - ), - ); - } - return (groupStateServer.versionId.toInt(), encryptedGroupState); - } catch (e) { - Log.error(e); - return null; - } -} - -Future addNewHiddenContact(int contactId) async { - final userData = await apiService.getUserById(contactId); - if (userData == null) { - Log.error('Could not load contact informations'); - return false; - } - await twonlyDB.contactsDao.insertOnConflictUpdate( - ContactsCompanion( - username: Value(utf8.decode(userData.username)), - userId: Value(contactId), - deletedByUser: const Value( - true, - ), // this will hide the contact in the contact list - ), - ); - await processSignalUserData(userData); - unawaited(setupNotificationWithUsers(forceContact: contactId)); - return true; -} - -Future _updateGroupState( - Group group, - EncryptedGroupState state, { - Uint8List? addAdmin, - Uint8List? removeAdmin, - int? versionId, -}) async { - final chacha20 = FlutterChacha20.poly1305Aead(); - final encryptionNonce = chacha20.newNonce(); - - final secretBox = await chacha20.encrypt( - state.writeToBuffer(), - secretKey: SecretKey(group.stateEncryptionKey!), - nonce: encryptionNonce, - ); - - final encryptedGroupState = EncryptedGroupStateEnvelop( - nonce: encryptionNonce, - encryptedGroupState: secretBox.cipherText, - mac: secretBox.mac.bytes, - ); - - { - // Upload the group state, if this fails, the group can not be created. - - final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!); - - final nonce = await getNonce(keyPair.getPublicKey().serialize()); - if (nonce == null) return false; - - final updateTBS = UpdateGroupState_UpdateTBS( - versionId: Int64(versionId ?? group.stateVersionId + 1), - encryptedGroupState: encryptedGroupState.writeToBuffer(), - publicKey: keyPair.getPublicKey().serialize(), - nonce: nonce, - addAdmin: addAdmin, - removeAdmin: removeAdmin, - ); - - final random = getRandomUint8List(32); - final signature = sign( - keyPair.getPrivateKey().serialize(), - updateTBS.writeToBuffer(), - random, - ); - - final newGroupState = UpdateGroupState( - update: updateTBS, - signature: signature, - ); - - final response = await http - .patch( - Uri.parse(getGroupStateUrl()), - body: newGroupState.writeToBuffer(), - ) - .timeout(const Duration(seconds: 10)); - - if (response.statusCode != 200) { - Log.error( - 'Could not patch group state. Got status code ${response.statusCode} from server.', - ); - return false; - } - } - return true; -} +Future fetchGroupStatesForUnjoinedGroups() => + rust_groups.fetchGroupStatesForUnjoinedGroups(); +Future fetchMissingGroupPublicKey() => + rust_groups.fetchMissingGroupPublicKeys(); +Future fetchGroupState(Group group) => + rust_groups.fetchGroupState(groupId: group.groupId); +Future addNewHiddenContact(int contactId) => + rust_groups.addHiddenContact(contactId: contactId); Future manageAdminState( Group group, - Uint8List groupPublicKey, + Uint8List key, int contactId, bool remove, -) async { - // ensure the latest state is used - final currentState = await fetchGroupState(group); - if (currentState == null) return false; - final (versionId, state) = currentState; - - final userId = Int64(contactId); - - Uint8List? addAdmin; - Uint8List? removeAdmin; - - if (remove) { - if (state.adminIds.contains(userId)) { - state.adminIds.remove(userId); - removeAdmin = groupPublicKey; - } else { - Log.info('User was already removed as admin.'); - return true; - } - } else { - if (!state.adminIds.contains(userId)) { - state.adminIds.add(userId); - addAdmin = groupPublicKey; - } else { - Log.info('User is already admin.'); - return true; - } - } - - if (addAdmin == null && removeAdmin == null) { - Log.info('User does not have a group public key.'); - return false; - } - - // send new state to the server - if (!await _updateGroupState( - group, - state, - addAdmin: addAdmin, - removeAdmin: removeAdmin, - )) { - return false; - } - - final groupActionType = remove - ? GroupActionType.demoteToMember - : GroupActionType.promoteToAdmin; - - await sendCipherTextToGroup( - group.groupId, - EncryptedContent( - groupUpdate: EncryptedContent_GroupUpdate( - groupActionType: groupActionType.name, - affectedContactId: Int64(contactId), - ), - ), - ); - - await twonlyDB.groupsDao.insertGroupAction( - GroupHistoriesCompanion( - groupId: Value(group.groupId), - type: Value(groupActionType), - affectedContactId: Value(contactId), - ), - ); - - // Updates the memberState :) - return (await fetchGroupState(group)) != null; -} - -Future updateGroupName(Group group, String groupName) async { - // ensure the latest state is used - final currentState = await fetchGroupState(group); - if (currentState == null) return false; - final (versionId, state) = currentState; - - state.groupName = groupName; - - // send new state to the server - if (!await _updateGroupState(group, state)) { - return false; - } - - await sendCipherTextToGroup( - group.groupId, - EncryptedContent( - groupUpdate: EncryptedContent_GroupUpdate( - groupActionType: GroupActionType.updatedGroupName.name, - newGroupName: groupName, - ), - ), - ); - - await twonlyDB.groupsDao.insertGroupAction( - GroupHistoriesCompanion( - groupId: Value(group.groupId), - type: const Value(GroupActionType.updatedGroupName), - oldGroupName: Value(group.groupName), - newGroupName: Value(groupName), - ), - ); - - // Updates the groupName :) - return (await fetchGroupState(group)) != null; -} - -Future updateChatDeletionTime( - Group group, - int deleteMessagesAfterMilliseconds, -) async { - // ensure the latest state is used - final currentState = await fetchGroupState(group); - if (currentState == null) return false; - final (versionId, state) = currentState; - - state.deleteMessagesAfterMilliseconds = Int64( - deleteMessagesAfterMilliseconds, - ); - - // send new state to the server - if (!await _updateGroupState(group, state)) { - return false; - } - - await sendCipherTextToGroup( - group.groupId, - EncryptedContent( - groupUpdate: EncryptedContent_GroupUpdate( - groupActionType: GroupActionType.changeDisplayMaxTime.name, - newDeleteMessagesAfterMilliseconds: Int64( - deleteMessagesAfterMilliseconds, - ), - ), - ), - ); - - await twonlyDB.groupsDao.insertGroupAction( - GroupHistoriesCompanion( - groupId: Value(group.groupId), - type: const Value(GroupActionType.changeDisplayMaxTime), - newDeleteMessagesAfterMilliseconds: Value( - deleteMessagesAfterMilliseconds, - ), - ), - ); - - // Updates the groupName :) - return (await fetchGroupState(group)) != null; -} - -Future addNewGroupMembers( - Group group, - List newGroupMemberIds, -) async { - // ensure the latest state is used - final currentState = await fetchGroupState(group); - if (currentState == null) return false; - final (versionId, state) = currentState; - - var memberIds = state.memberIds + newGroupMemberIds.map(Int64.new).toList(); - memberIds = memberIds.toSet().toList(); - - final newState = EncryptedGroupState( - groupName: state.groupName, - deleteMessagesAfterMilliseconds: state.deleteMessagesAfterMilliseconds, - memberIds: memberIds, - adminIds: state.adminIds, - padding: List.generate(Random().nextInt(80), (_) => 0), - ); - - // send new state to the server - if (!await _updateGroupState(group, newState)) { - return false; - } - - final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!); - - for (final newMember in newGroupMemberIds) { - await sendCipherTextToGroup( - group.groupId, - EncryptedContent( - groupUpdate: EncryptedContent_GroupUpdate( - groupActionType: GroupActionType.addMember.name, - affectedContactId: Int64(newMember), - ), - ), - ); - - await twonlyDB.groupsDao.insertGroupAction( - GroupHistoriesCompanion( - groupId: Value(group.groupId), - type: const Value(GroupActionType.addMember), - affectedContactId: Value(newMember), - ), - ); - - await sendCipherText( - newMember, - EncryptedContent( - groupId: group.groupId, - groupCreate: EncryptedContent_GroupCreate( - stateKey: group.stateEncryptionKey, - groupPublicKey: keyPair.getPublicKey().serialize(), - groupName: group.groupName, - ), - ), - ); - } - - // Updates the groupMembers table :) - return (await fetchGroupState(group)) != null; -} - -Future removeMemberFromGroup( - Group group, - Uint8List groupPublicKey, - int removeContactId, -) async { - // ensure the latest state is used - final currentState = await fetchGroupState(group); - if (currentState == null) return false; - final (versionId, state) = currentState; - - final contactId = Int64(removeContactId); - - final membersIdSet = state.memberIds.toSet(); - final adminIdSet = state.adminIds.toSet(); - Uint8List? removeAdmin; - if (!membersIdSet.contains(contactId)) { - Log.info('User was already removed from the group!'); - return true; - } - if (adminIdSet.contains(contactId)) { - // if (member.groupPublicKey == null) { - // // If the admin public key is not removed, that the user could potentially still update the group state. So only - // // allow the user removal, if this key is known. It is better the users can not remove the other user, then - // // the he can but the other user, could still update the group state. - // Log.error( - // 'Could not remove user. User is admin, but groupPublicKey is unknown.', - // ); - // return false; - // } - removeAdmin = groupPublicKey; - } - - membersIdSet.remove(contactId); - adminIdSet.remove(contactId); - - final newState = EncryptedGroupState( - groupName: state.groupName, - deleteMessagesAfterMilliseconds: state.deleteMessagesAfterMilliseconds, - memberIds: membersIdSet.toList(), - adminIds: adminIdSet.toList(), - padding: List.generate(Random().nextInt(80), (_) => 0), - ); - - // send new state to the server - if (!await _updateGroupState(group, newState, removeAdmin: removeAdmin)) { - return false; - } - - await sendCipherTextToGroup( - group.groupId, - EncryptedContent( - groupUpdate: EncryptedContent_GroupUpdate( - groupActionType: GroupActionType.removedMember.name, - affectedContactId: Int64(removeContactId), - ), - ), - ); - - await twonlyDB.groupsDao.insertGroupAction( - GroupHistoriesCompanion( - groupId: Value(group.groupId), - type: const Value(GroupActionType.removedMember), - affectedContactId: Value( - removeContactId == userService.currentUser.userId - ? null - : removeContactId, - ), - ), - ); - - // Updates the groupMembers table :) - return (await fetchGroupState(group)) != null; -} - -Future getNonce(Uint8List publicKey) async { - final publicKeyHex = uint8ListToHex(publicKey); - - final responseNonce = await http - .get( - Uri.parse('${getGroupChallengeUrl()}/$publicKeyHex'), - ) - .timeout(const Duration(seconds: 10)); - - if (responseNonce.statusCode != 200) { - Log.error( - 'Could not load nonce. Got status code ${responseNonce.statusCode} from server.', - ); - return null; - } - return responseNonce.bodyBytes; -} - -Future leaveAsNonAdminFromGroup(Group group) async { - final currentState = await fetchGroupState(group); - if (currentState == null) { - Log.error('Could not load current state'); - return false; - } - - final (version, _) = currentState; - if (group.stateVersionId != version) { - Log.error('Version is not valid. Just retry.'); - return false; - } - - final chacha20 = FlutterChacha20.poly1305Aead(); - final encryptionNonce = chacha20.newNonce(); - - final state = EncryptedAppendedGroupState( - type: EncryptedAppendedGroupState_Type.LEFT_GROUP, - ); - - final secretBox = await chacha20.encrypt( - state.writeToBuffer(), - secretKey: SecretKey(group.stateEncryptionKey!), - nonce: encryptionNonce, - ); - - final encryptedGroupStateAppend = EncryptedGroupStateEnvelop( - nonce: encryptionNonce, - encryptedGroupState: secretBox.cipherText, - mac: secretBox.mac.bytes, - ); - - { - // Upload the group state, if this fails, the group can not be created. - - final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!); - - final nonce = await getNonce(keyPair.getPublicKey().serialize()); - if (nonce == null) return false; - - final appendTBS = AppendGroupState_AppendTBS( - publicKey: keyPair.getPublicKey().serialize(), - encryptedGroupStateAppend: encryptedGroupStateAppend.writeToBuffer(), +) => rust_groups.manageAdminState( + groupId: group.groupId, + groupPublicKey: key, + contactId: contactId, + remove: remove, +); +Future updateGroupName(Group group, String name) => + rust_groups.updateGroupName(groupId: group.groupId, groupName: name); +Future updateChatDeletionTime(Group group, int milliseconds) => + rust_groups.updateChatDeletionTime( groupId: group.groupId, - nonce: nonce, + deleteMessagesAfterMilliseconds: milliseconds, ); - - final random = getRandomUint8List(32); - final signature = sign( - keyPair.getPrivateKey().serialize(), - appendTBS.writeToBuffer(), - random, +Future addNewGroupMembers(Group group, List ids) => + rust_groups.addNewGroupMembers( + groupId: group.groupId, + memberIds: Int64List.fromList(ids), ); - - final newGroupState = AppendGroupState( - versionId: Int64(group.stateVersionId + 1), - appendTBS: appendTBS, - signature: signature, +Future removeMemberFromGroup(Group group, Uint8List key, int contactId) => + rust_groups.removeMemberFromGroup( + groupId: group.groupId, + groupPublicKey: key, + contactId: contactId, ); - - final response = await http - .post( - Uri.parse('${getGroupStateUrl()}/append'), - body: newGroupState.writeToBuffer(), - ) - .timeout(const Duration(seconds: 10)); - - if (response.statusCode != 200) { - Log.error( - 'Could not patch group state. Got status code ${response.statusCode} from server.', - ); - return false; - } - } - const groupActionType = GroupActionType.leftGroup; - await sendCipherTextToGroup( - group.groupId, - EncryptedContent( - groupUpdate: EncryptedContent_GroupUpdate( - groupActionType: groupActionType.name, - affectedContactId: Int64(userService.currentUser.userId), - ), - ), - ); - - await twonlyDB.groupsDao.insertGroupAction( - GroupHistoriesCompanion( - groupId: Value(group.groupId), - type: const Value(groupActionType), - ), - ); - - // Updates the table :) - return (await fetchGroupState(group)) != null; -} +Future leaveAsNonAdminFromGroup(Group group) => + rust_groups.leaveGroup(groupId: group.groupId); diff --git a/lib/src/services/signal/protocol_state.signal.dart b/lib/src/services/signal/protocol_state.signal.dart index 58f830e8..55f5f1e7 100644 --- a/lib/src/services/signal/protocol_state.signal.dart +++ b/lib/src/services/signal/protocol_state.signal.dart @@ -14,7 +14,9 @@ bool shouldAttemptResync(int userId) { if (attempt == null) return true; if (attempt.failureCount >= maxResyncAttempts) return false; - final cooldown = Duration(minutes: 5 * pow(5, attempt.failureCount - 1).toInt()); + final cooldown = Duration( + minutes: 5 * pow(5, attempt.failureCount - 1).toInt(), + ); return DateTime.now().difference(attempt.lastAttempt) > cooldown; } diff --git a/lib/src/services/user_discovery.service.dart b/lib/src/services/user_discovery.service.dart index 7dfff07f..21c99c14 100644 --- a/lib/src/services/user_discovery.service.dart +++ b/lib/src/services/user_discovery.service.dart @@ -105,8 +105,9 @@ class UserDiscoveryService { static Future getCurrentVersion() async { try { - return await FlutterUserDiscovery.getCurrentVersion(callbackId: isolateCallbackId) - .timeout(const Duration(seconds: 5)); + return await FlutterUserDiscovery.getCurrentVersion( + callbackId: isolateCallbackId, + ).timeout(const Duration(seconds: 5)); } catch (e) { Log.error(e); return null; diff --git a/lib/src/utils/keyvalue.dart b/lib/src/utils/keyvalue.dart index b1ce6202..1d34fc03 100644 --- a/lib/src/utils/keyvalue.dart +++ b/lib/src/utils/keyvalue.dart @@ -107,7 +107,9 @@ class KeyValueStore { try { if (file.existsSync()) { final contents = await file.readAsString(); - val = factory.fromJson(jsonDecode(contents) as Map) as T; + val = + factory.fromJson(jsonDecode(contents) as Map) + as T; } else { val = factory.defaultValue() as T; } @@ -138,7 +140,8 @@ class KeyValueStore { try { if (file.existsSync()) { final contents = await file.readAsString(); - return factory.fromJson(jsonDecode(contents) as Map) as T; + return factory.fromJson(jsonDecode(contents) as Map) + as T; } } catch (e) { Log.warn('Error reading file. Returning default.: $e'); @@ -157,7 +160,8 @@ class KeyValueStore { try { if (file.existsSync()) { final contents = await file.readAsString(); - return factory.fromJson(jsonDecode(contents) as Map) as T; + return factory.fromJson(jsonDecode(contents) as Map) + as T; } } catch (e) { Log.warn('Error reading file.: $e'); diff --git a/lib/src/visual/components/contact_request_badge.comp.dart b/lib/src/visual/components/contact_request_badge.comp.dart index b2d6a13f..a048c547 100644 --- a/lib/src/visual/components/contact_request_badge.comp.dart +++ b/lib/src/visual/components/contact_request_badge.comp.dart @@ -34,7 +34,9 @@ class ContactRequestBadgeComp extends StatelessWidget { ), Center( child: NotificationBadgeComp( - backgroundColor: isDarkMode(context) ? Colors.white : Colors.black, + backgroundColor: isDarkMode(context) + ? Colors.white + : Colors.black, textColor: isDarkMode(context) ? Colors.black : Colors.white, count: count.toString(), child: IconButton( diff --git a/lib/src/visual/components/custom_color_picker_dialog.comp.dart b/lib/src/visual/components/custom_color_picker_dialog.comp.dart index 8ee9fd9d..abf5805b 100644 --- a/lib/src/visual/components/custom_color_picker_dialog.comp.dart +++ b/lib/src/visual/components/custom_color_picker_dialog.comp.dart @@ -11,7 +11,8 @@ class CustomColorPickerDialog extends StatefulWidget { final Color initialColor; @override - State createState() => _CustomColorPickerDialogState(); + State createState() => + _CustomColorPickerDialogState(); } class _CustomColorPickerDialogState extends State { @@ -54,7 +55,10 @@ class _CustomColorPickerDialogState extends State { ), const SizedBox(height: 20), // Hue Slider - Text(context.lang.hue, style: const TextStyle(fontWeight: FontWeight.w600)), + Text( + context.lang.hue, + style: const TextStyle(fontWeight: FontWeight.w600), + ), Slider( value: _hsvColor.hue, max: 360, @@ -66,7 +70,10 @@ class _CustomColorPickerDialogState extends State { }, ), // Saturation Slider - Text(context.lang.saturation, style: const TextStyle(fontWeight: FontWeight.w600)), + Text( + context.lang.saturation, + style: const TextStyle(fontWeight: FontWeight.w600), + ), Slider( value: _hsvColor.saturation, onChanged: (val) { @@ -76,7 +83,10 @@ class _CustomColorPickerDialogState extends State { }, ), // Brightness / Value Slider - Text(context.lang.brightness, style: const TextStyle(fontWeight: FontWeight.w600)), + Text( + context.lang.brightness, + style: const TextStyle(fontWeight: FontWeight.w600), + ), Slider( value: _hsvColor.value, onChanged: (val) { @@ -102,7 +112,8 @@ class _CustomColorPickerDialogState extends State { Expanded( child: MyButton( variant: MyButtonVariant.primaryMiddle, - onPressed: () => Navigator.of(context).pop(currentColor.toARGB32()), + onPressed: () => + Navigator.of(context).pop(currentColor.toARGB32()), child: Text(context.lang.ok), ), ), diff --git a/lib/src/visual/components/label_editor_bottom_sheet.comp.dart b/lib/src/visual/components/label_editor_bottom_sheet.comp.dart index 27cce02b..3753c980 100644 --- a/lib/src/visual/components/label_editor_bottom_sheet.comp.dart +++ b/lib/src/visual/components/label_editor_bottom_sheet.comp.dart @@ -60,7 +60,9 @@ class _LabelEditorBottomSheetState extends State { } Future _pickCustomColor({required bool isBgColor}) async { - final initial = isBgColor ? Color(_selectedBgColor) : Color(_selectedTextColor); + final initial = isBgColor + ? Color(_selectedBgColor) + : Color(_selectedTextColor); final pickedColorInt = await showDialog( context: context, builder: (context) => CustomColorPickerDialog(initialColor: initial), @@ -109,20 +111,29 @@ class _LabelEditorBottomSheetState extends State { Text( isEditing ? context.lang.editLabel : context.lang.createLabel, textAlign: TextAlign.center, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 20), // Interactive Inline Label Badge Center( child: IntrinsicWidth( child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 100, maxWidth: 200), + constraints: const BoxConstraints( + minWidth: 100, + maxWidth: 200, + ), child: Container( decoration: BoxDecoration( color: Color(_selectedBgColor), borderRadius: BorderRadius.circular(16), ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 2, + ), child: TextField( controller: _nameController, autofocus: true, @@ -137,13 +148,18 @@ class _LabelEditorBottomSheetState extends State { decoration: InputDecoration( hintText: context.lang.labelNameHint, hintStyle: TextStyle( - color: Color(_selectedTextColor).withValues(alpha: 0.6), + color: Color( + _selectedTextColor, + ).withValues(alpha: 0.6), fontSize: 13, fontWeight: FontWeight.w600, ), border: InputBorder.none, isDense: true, - contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), + contentPadding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 6, + ), counterText: '', ), onChanged: (_) => setState(() {}), @@ -157,7 +173,10 @@ class _LabelEditorBottomSheetState extends State { Text( context.lang.labelBackgroundColor, textAlign: TextAlign.center, - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold), + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 10), Wrap( @@ -184,7 +203,8 @@ class _LabelEditorBottomSheetState extends State { ? Icon( Icons.check, size: 18, - color: Color(colorValue).computeLuminance() > 0.5 + color: + Color(colorValue).computeLuminance() > 0.5 ? Colors.black : Colors.white, ) @@ -226,7 +246,10 @@ class _LabelEditorBottomSheetState extends State { Text( context.lang.labelTextColor, textAlign: TextAlign.center, - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold), + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 10), Wrap( @@ -253,7 +276,8 @@ class _LabelEditorBottomSheetState extends State { ? Icon( Icons.check, size: 18, - color: Color(colorValue).computeLuminance() > 0.5 + color: + Color(colorValue).computeLuminance() > 0.5 ? Colors.black : Colors.white, ) diff --git a/lib/src/visual/components/profile_qr_code.comp.dart b/lib/src/visual/components/profile_qr_code.comp.dart index 3d113a07..970c3475 100644 --- a/lib/src/visual/components/profile_qr_code.comp.dart +++ b/lib/src/visual/components/profile_qr_code.comp.dart @@ -28,7 +28,9 @@ class ProfileQrCodeComp extends StatefulWidget { StreamSubscription? subscription; if (openToVerify) { - subscription = KeyVerificationService.onVerificationSuccessClose.listen((_) { + subscription = KeyVerificationService.onVerificationSuccessClose.listen(( + _, + ) { if (context.mounted) { KeyVerificationService.closeVerificationFlows(context); } @@ -65,15 +67,15 @@ class ProfileQrCodeComp extends StatefulWidget { Text( openToVerify ? (contact != null - ? context.lang.letUserScanQrCode( - getContactDisplayName(contact), - ) - : context.lang.letFriendScanQrToVerify) + ? context.lang.letUserScanQrCode( + getContactDisplayName(contact), + ) + : context.lang.letFriendScanQrToVerify) : (contact != null - ? context.lang.letUserScanQrCode( - getContactDisplayName(contact), - ) - : context.lang.addContactQrSheetSubtext), + ? context.lang.letUserScanQrCode( + getContactDisplayName(contact), + ) + : context.lang.addContactQrSheetSubtext), style: TextStyle( fontSize: 14, color: context.color.onSurface.withValues(alpha: 0.6), diff --git a/lib/src/visual/context_menu/group.context_menu.dart b/lib/src/visual/context_menu/group.context_menu.dart index 63c8d881..8c6094b6 100644 --- a/lib/src/visual/context_menu/group.context_menu.dart +++ b/lib/src/visual/context_menu/group.context_menu.dart @@ -43,7 +43,8 @@ class GroupContextMenu extends StatelessWidget { ), ContextMenuItem( title: context.lang.contextMenuOpenChat, - onTap: () => navigator.context.push(Routes.chatsMessages(group.groupId)), + onTap: () => + navigator.context.push(Routes.chatsMessages(group.groupId)), icon: FontAwesomeIcons.comments, ), if (!group.archived) diff --git a/lib/src/visual/context_menu/user.context_menu.dart b/lib/src/visual/context_menu/user.context_menu.dart index 59278a86..453c0306 100644 --- a/lib/src/visual/context_menu/user.context_menu.dart +++ b/lib/src/visual/context_menu/user.context_menu.dart @@ -23,7 +23,8 @@ class UserContextMenu extends StatelessWidget { items: [ ContextMenuItem( title: context.lang.contextMenuUserProfile, - onTap: () => navigator.context.push(Routes.profileContact(contact.userId)), + onTap: () => + navigator.context.push(Routes.profileContact(contact.userId)), icon: FontAwesomeIcons.user, ), ], diff --git a/lib/src/visual/elements/better_list_title.element.dart b/lib/src/visual/elements/better_list_title.element.dart index c3ac6f6f..6b0277dd 100644 --- a/lib/src/visual/elements/better_list_title.element.dart +++ b/lib/src/visual/elements/better_list_title.element.dart @@ -35,16 +35,16 @@ class BetterListTile extends StatelessWidget { child: (leading != null) ? leading : (icon is IconData) - ? Icon( - icon as IconData, - size: iconSize, - color: color, - ) - : FaIcon( - icon as FaIconData?, - size: iconSize, - color: color, - ), + ? Icon( + icon as IconData, + size: iconSize, + color: color, + ) + : FaIcon( + icon as FaIconData?, + size: iconSize, + color: color, + ), ), ), trailing: trailing, diff --git a/lib/src/visual/elements/my_icon_button.element.dart b/lib/src/visual/elements/my_icon_button.element.dart index 9717722d..43e761b6 100644 --- a/lib/src/visual/elements/my_icon_button.element.dart +++ b/lib/src/visual/elements/my_icon_button.element.dart @@ -26,7 +26,6 @@ class MyIconButton extends StatefulWidget { } class _MyIconButtonState extends State { - @override Widget build(BuildContext context) { final isEnabled = widget.onPressed != null || widget.onLongPress != null; diff --git a/lib/src/visual/elements/reactive_tap_feedback.element.dart b/lib/src/visual/elements/reactive_tap_feedback.element.dart index fe599d29..710f36c8 100644 --- a/lib/src/visual/elements/reactive_tap_feedback.element.dart +++ b/lib/src/visual/elements/reactive_tap_feedback.element.dart @@ -28,14 +28,15 @@ class _ReactiveTapFeedbackState extends State @override void initState() { super.initState(); - _controller = AnimationController( - vsync: this, - lowerBound: double.negativeInfinity, - upperBound: double.infinity, - value: 0, - )..addListener(() { - setState(() {}); - }); + _controller = + AnimationController( + vsync: this, + lowerBound: double.negativeInfinity, + upperBound: double.infinity, + value: 0, + )..addListener(() { + setState(() {}); + }); } @override diff --git a/lib/src/visual/views/camera/camera_preview_components/permissions_view.dart b/lib/src/visual/views/camera/camera_preview_components/permissions_view.dart index 9249020f..9fe87d5a 100644 --- a/lib/src/visual/views/camera/camera_preview_components/permissions_view.dart +++ b/lib/src/visual/views/camera/camera_preview_components/permissions_view.dart @@ -48,7 +48,8 @@ class PermissionHandlerViewState extends State @override void didUpdateWidget(PermissionHandlerView oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.triggerPermissionRequest && !oldWidget.triggerPermissionRequest) { + if (widget.triggerPermissionRequest && + !oldWidget.triggerPermissionRequest) { WidgetsBinding.instance.addPostFrameCallback((_) async { await _requestPermissions(); }); diff --git a/lib/src/visual/views/camera/camera_preview_components/zoom_selector.dart b/lib/src/visual/views/camera/camera_preview_components/zoom_selector.dart index dc40aec9..51f38b59 100644 --- a/lib/src/visual/views/camera/camera_preview_components/zoom_selector.dart +++ b/lib/src/visual/views/camera/camera_preview_components/zoom_selector.dart @@ -49,9 +49,14 @@ class CameraZoomButtons extends StatelessWidget { wideCameraIndex = index; } - final isFront = controller.description.lensDirection == CameraLensDirection.front; + final isFront = + controller.description.lensDirection == CameraLensDirection.front; - final showWideAngleZoomIOS = !showWideAngleZoom && Platform.isIOS && wideCameraIndex != null && !isFront; + final showWideAngleZoomIOS = + !showWideAngleZoom && + Platform.isIOS && + wideCameraIndex != null && + !isFront; final zoomButtonStyle = TextButton.styleFrom( padding: EdgeInsets.zero, @@ -63,11 +68,14 @@ class CameraZoomButtons extends StatelessWidget { const zoomTextStyle = TextStyle(fontSize: 13); final isSmallerFocused = - scaleFactor < 1 || (showWideAngleZoomIOS && selectedCameraDetails.cameraId == wideCameraIndex); + scaleFactor < 1 || + (showWideAngleZoomIOS && + selectedCameraDetails.cameraId == wideCameraIndex); final isMiddleFocused = scaleFactor >= 1 && scaleFactor < 2 && - !(showWideAngleZoomIOS && selectedCameraDetails.cameraId == wideCameraIndex); + !(showWideAngleZoomIOS && + selectedCameraDetails.cameraId == wideCameraIndex); final maxLevel = max( min(selectedCameraDetails.maxAvailableZoom, 2), @@ -117,14 +125,17 @@ class CameraZoomButtons extends StatelessWidget { ), ), onPressed: () async { - if (showWideAngleZoomIOS && selectedCameraDetails.cameraId == wideCameraIndex) { + if (showWideAngleZoomIOS && + selectedCameraDetails.cameraId == wideCameraIndex) { await selectCamera(0, true); } else { updateScaleFactor(1.0); } }, child: Text( - isMiddleFocused ? '${beautifulZoomScale(scaleFactor)}x' : '1.0x', + isMiddleFocused + ? '${beautifulZoomScale(scaleFactor)}x' + : '1.0x', style: zoomTextStyle, ), ), @@ -140,7 +151,8 @@ class CameraZoomButtons extends StatelessWidget { 2, ).toDouble(); - if (showWideAngleZoomIOS && selectedCameraDetails.cameraId == wideCameraIndex) { + if (showWideAngleZoomIOS && + selectedCameraDetails.cameraId == wideCameraIndex) { await selectCamera(0, true); } updateScaleFactor(level); diff --git a/lib/src/visual/views/chats/media_viewer_components/media_viewer_bottom_navigation.comp.dart b/lib/src/visual/views/chats/media_viewer_components/media_viewer_bottom_navigation.comp.dart index 45c0de41..19081739 100644 --- a/lib/src/visual/views/chats/media_viewer_components/media_viewer_bottom_navigation.comp.dart +++ b/lib/src/visual/views/chats/media_viewer_components/media_viewer_bottom_navigation.comp.dart @@ -77,8 +77,8 @@ class MediaViewerBottomNavigationBar extends StatelessWidget { child: CircularProgressIndicator.adaptive(strokeWidth: 2), ) : imageSaved - ? const Icon(Icons.check) - : const FaIcon(FontAwesomeIcons.floppyDisk, size: 20), + ? const Icon(Icons.check) + : const FaIcon(FontAwesomeIcons.floppyDisk, size: 20), ), const SizedBox(width: 10), IconButton( diff --git a/lib/src/visual/views/contact/contact_components/restore_flame.comp.dart b/lib/src/visual/views/contact/contact_components/restore_flame.comp.dart index 980045a4..1768a046 100644 --- a/lib/src/visual/views/contact/contact_components/restore_flame.comp.dart +++ b/lib/src/visual/views/contact/contact_components/restore_flame.comp.dart @@ -10,7 +10,8 @@ import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; -import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' as pb; +import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' + as pb; import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/services/subscription.service.dart'; @@ -60,7 +61,8 @@ class _RestoreFlameCompState extends State { final currentPlan = planFromString( userService.currentUser.subscriptionPlan, ); - if (!isUserAllowed(currentPlan, PremiumFeatures.RestoreFlames) && kReleaseMode) { + if (!isUserAllowed(currentPlan, PremiumFeatures.RestoreFlames) && + kReleaseMode) { await context.push(Routes.settingsSubscription); return; } diff --git a/lib/src/visual/views/contact/select_contact_labels.view.dart b/lib/src/visual/views/contact/select_contact_labels.view.dart index 273f9027..18d49a2b 100644 --- a/lib/src/visual/views/contact/select_contact_labels.view.dart +++ b/lib/src/visual/views/contact/select_contact_labels.view.dart @@ -20,7 +20,8 @@ class SelectContactLabelsView extends StatefulWidget { final int contactId; @override - State createState() => _SelectContactLabelsViewState(); + State createState() => + _SelectContactLabelsViewState(); } class _SelectContactLabelsViewState extends State { @@ -35,7 +36,9 @@ class _SelectContactLabelsViewState extends State { @override void initState() { super.initState(); - _contactSub = twonlyDB.contactsDao.watchContact(widget.contactId).listen((contact) { + _contactSub = twonlyDB.contactsDao.watchContact(widget.contactId).listen(( + contact, + ) { if (mounted) { setState(() { _contact = contact; @@ -51,13 +54,15 @@ class _SelectContactLabelsViewState extends State { } }); - _contactLabelsSub = twonlyDB.labelsDao.watchContactLabels(widget.contactId).listen((labels) { - if (mounted) { - setState(() { - _selectedLabelIds = labels.map((l) => l.id).toSet(); + _contactLabelsSub = twonlyDB.labelsDao + .watchContactLabels(widget.contactId) + .listen((labels) { + if (mounted) { + setState(() { + _selectedLabelIds = labels.map((l) => l.id).toSet(); + }); + } }); - } - }); } @override @@ -88,7 +93,10 @@ class _SelectContactLabelsViewState extends State { _selectedLabelIds = newSet; }); - await twonlyDB.labelsDao.setContactLabels(widget.contactId, _selectedLabelIds.toList()); + await twonlyDB.labelsDao.setContactLabels( + widget.contactId, + _selectedLabelIds.toList(), + ); } Future _createLabel() async { @@ -181,7 +189,10 @@ class _SelectContactLabelsViewState extends State { children: [ Text( getContactDisplayName(contact, maxLength: 25), - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), if (getContactDisplayName(contact) != contact.username) Text( @@ -258,7 +269,10 @@ class _SelectContactLabelsViewState extends State { title: Row( children: [ Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), decoration: BoxDecoration( color: Color(label.backgroundColor), borderRadius: BorderRadius.circular(12), @@ -284,7 +298,10 @@ class _SelectContactLabelsViewState extends State { onTap: () => _editLabel(label), child: const Padding( padding: EdgeInsets.all(6), - child: FaIcon(FontAwesomeIcons.penToSquare, size: 16), + child: FaIcon( + FontAwesomeIcons.penToSquare, + size: 16, + ), ), ), ), @@ -296,7 +313,11 @@ class _SelectContactLabelsViewState extends State { onTap: () => _deleteLabel(label), child: const Padding( padding: EdgeInsets.all(6), - child: FaIcon(FontAwesomeIcons.trashCan, size: 16, color: Colors.red), + child: FaIcon( + FontAwesomeIcons.trashCan, + size: 16, + color: Colors.red, + ), ), ), ), diff --git a/lib/src/visual/views/groups/group_create_select_members.view.dart b/lib/src/visual/views/groups/group_create_select_members.view.dart index 2205d689..e0f5cf18 100644 --- a/lib/src/visual/views/groups/group_create_select_members.view.dart +++ b/lib/src/visual/views/groups/group_create_select_members.view.dart @@ -221,7 +221,8 @@ class _StartNewChatView extends State { contact: user, child: ContactLabelsSubtitleBuilder( contactId: user.userId, - additionalSubtitle: alreadyInGroup.contains(user.userId) + additionalSubtitle: + alreadyInGroup.contains(user.userId) ? Text(context.lang.alreadyInGroup) : null, builder: (context, subtitleWidget) { @@ -259,7 +260,9 @@ class _StartNewChatView extends State { return const BorderSide(width: 0); } return BorderSide( - color: Theme.of(context).colorScheme.outline, + color: Theme.of( + context, + ).colorScheme.outline, ); }, ), diff --git a/lib/src/visual/views/groups/group_member.context.dart b/lib/src/visual/views/groups/group_member.context.dart index 251a9010..33d675df 100644 --- a/lib/src/visual/views/groups/group_member.context.dart +++ b/lib/src/visual/views/groups/group_member.context.dart @@ -133,7 +133,9 @@ class GroupMemberContextMenu extends StatelessWidget { return; } if (!navigator.mounted) return; - await navigator.context.push(Routes.chatsMessages(directChat.groupId)); + await navigator.context.push( + Routes.chatsMessages(directChat.groupId), + ); }, icon: FontAwesomeIcons.message, ), diff --git a/lib/src/visual/views/onboarding/register.view.dart b/lib/src/visual/views/onboarding/register.view.dart index 7903b9f0..b024fe0e 100644 --- a/lib/src/visual/views/onboarding/register.view.dart +++ b/lib/src/visual/views/onboarding/register.view.dart @@ -39,7 +39,6 @@ class RegisterView extends StatefulWidget { class _RegisterViewState extends State { final TextEditingController usernameController = TextEditingController(); - final TextEditingController inviteCodeController = TextEditingController(); bool _registrationDisabled = false; bool _isTryingToRegister = false; @@ -65,7 +64,6 @@ class _RegisterViewState extends State { return; } final username = usernameController.text; - final inviteCode = inviteCodeController.text; setState(() { _isTryingToRegister = true; @@ -99,7 +97,7 @@ class _RegisterViewState extends State { var userId = 0; - final res = await apiService.register(username, inviteCode, proof); + final res = await apiService.register(username, null, proof); if (res.isSuccess) { Log.info('Got user_id ${res.value} from server'); userId = res.value.userid.toInt() as int; diff --git a/lib/src/visual/views/onboarding/setup/components/mock_contact_request_actions.comp.dart b/lib/src/visual/views/onboarding/setup/components/mock_contact_request_actions.comp.dart index b13ffce5..80183cfd 100644 --- a/lib/src/visual/views/onboarding/setup/components/mock_contact_request_actions.comp.dart +++ b/lib/src/visual/views/onboarding/setup/components/mock_contact_request_actions.comp.dart @@ -111,7 +111,10 @@ class MockContactSuggestedActionsComp extends StatelessWidget { children: [ const Padding( padding: EdgeInsets.symmetric(horizontal: 6), - child: FaIcon(FontAwesomeIcons.circleQuestion, size: 10), + child: FaIcon( + FontAwesomeIcons.circleQuestion, + size: 10, + ), ), Text( context.lang.friendSuggestionsAskFriend, diff --git a/lib/src/visual/views/onboarding/setup/components/next_button.comp.dart b/lib/src/visual/views/onboarding/setup/components/next_button.comp.dart index 7973ca36..915b9a12 100644 --- a/lib/src/visual/views/onboarding/setup/components/next_button.comp.dart +++ b/lib/src/visual/views/onboarding/setup/components/next_button.comp.dart @@ -47,7 +47,9 @@ class NextButtonComp extends StatelessWidget { ), ) : Text( - currentPage.isLast ? context.lang.finishSetup : context.lang.next, + currentPage.isLast + ? context.lang.finishSetup + : context.lang.next, ), ); }, diff --git a/lib/src/visual/views/onboarding/setup/components/profile_card.comp.dart b/lib/src/visual/views/onboarding/setup/components/profile_card.comp.dart index fe8bd5a3..04f5f8c8 100644 --- a/lib/src/visual/views/onboarding/setup/components/profile_card.comp.dart +++ b/lib/src/visual/views/onboarding/setup/components/profile_card.comp.dart @@ -49,7 +49,6 @@ class SafetyProfileCard extends StatelessWidget { ); icon = Icons.tune_rounded; badgeText = null; - } return MouseRegion( @@ -70,8 +69,8 @@ class SafetyProfileCard extends StatelessWidget { color: isSelected ? context.color.primary : (isHovered - ? context.color.onSurfaceVariant.withValues(alpha: 0.3) - : context.color.outlineVariant.withValues(alpha: 0.4)), + ? context.color.onSurfaceVariant.withValues(alpha: 0.3) + : context.color.outlineVariant.withValues(alpha: 0.4)), width: isSelected ? 2 : 1, ), boxShadow: isSelected diff --git a/lib/src/visual/views/recovery_from_secure_storage.view.dart b/lib/src/visual/views/recovery_from_secure_storage.view.dart index 2a1397a0..85ad4a94 100644 --- a/lib/src/visual/views/recovery_from_secure_storage.view.dart +++ b/lib/src/visual/views/recovery_from_secure_storage.view.dart @@ -114,7 +114,9 @@ class _RecoveryViewState extends State { ? const SizedBox( height: 16, width: 16, - child: CircularProgressIndicator.adaptive(strokeWidth: 2), + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), ) : const Icon(Icons.restore_rounded), style: FilledButton.styleFrom( diff --git a/lib/src/visual/views/settings/appearance.view.dart b/lib/src/visual/views/settings/appearance.view.dart index 7bb271c7..59bcfdd3 100644 --- a/lib/src/visual/views/settings/appearance.view.dart +++ b/lib/src/visual/views/settings/appearance.view.dart @@ -86,8 +86,9 @@ class _AppearanceViewState extends State { context: context, showDragHandle: true, builder: (context) { - final activeColor = - context.watch().primaryColor; + final activeColor = context + .watch() + .primaryColor; return Padding( padding: const EdgeInsets.fromLTRB(24, 8, 24, 32), child: Column( @@ -97,15 +98,14 @@ class _AppearanceViewState extends State { Text( context.lang.settingsAppearancePrimaryColor, style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: presetColors.map((color) { - final isSelected = - activeColor.toARGB32() == color.toARGB32(); + final isSelected = activeColor.toARGB32() == color.toARGB32(); return GestureDetector( onTap: () async { await context @@ -126,8 +126,7 @@ class _AppearanceViewState extends State { ), ], border: Border.all( - color: - isSelected ? Colors.white : Colors.transparent, + color: isSelected ? Colors.white : Colors.transparent, width: 3, ), ), diff --git a/lib/src/visual/views/settings/backup/backup_utils.dart b/lib/src/visual/views/settings/backup/backup_utils.dart index 67c09e08..dd401cc8 100644 --- a/lib/src/visual/views/settings/backup/backup_utils.dart +++ b/lib/src/visual/views/settings/backup/backup_utils.dart @@ -10,8 +10,8 @@ import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/elements/my_button.element.dart'; Future promptAndDisableMemoriesBackup(BuildContext context) async { - final cloudOnlyCount = - await twonlyDB.mediaFilesDao.getCloudOnlyMemoriesCount(); + final cloudOnlyCount = await twonlyDB.mediaFilesDao + .getCloudOnlyMemoriesCount(); if (!context.mounted) return false; diff --git a/lib/src/visual/views/settings/backup/components/backup_setup.comp.dart b/lib/src/visual/views/settings/backup/components/backup_setup.comp.dart index 925d401c..9dd395fa 100644 --- a/lib/src/visual/views/settings/backup/components/backup_setup.comp.dart +++ b/lib/src/visual/views/settings/backup/components/backup_setup.comp.dart @@ -34,7 +34,8 @@ class BackupPasswordTextField extends StatefulWidget { final bool obscureByDefault; @override - State createState() => _BackupPasswordTextFieldState(); + State createState() => + _BackupPasswordTextFieldState(); } class _BackupPasswordTextFieldState extends State { diff --git a/lib/src/visual/views/settings/backup/passwordless_recovery/components/second_factor_picker.comp.dart b/lib/src/visual/views/settings/backup/passwordless_recovery/components/second_factor_picker.comp.dart index 5836c59b..6e870db3 100644 --- a/lib/src/visual/views/settings/backup/passwordless_recovery/components/second_factor_picker.comp.dart +++ b/lib/src/visual/views/settings/backup/passwordless_recovery/components/second_factor_picker.comp.dart @@ -112,7 +112,9 @@ class SecondFactorPicker extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( - color: isSelected ? context.color.primary : Colors.transparent, + color: isSelected + ? context.color.primary + : Colors.transparent, borderRadius: borderRadius, ), child: Column( diff --git a/lib/src/visual/views/settings/backup/passwordless_recovery/components/status.passwordless_recovery.comp.dart b/lib/src/visual/views/settings/backup/passwordless_recovery/components/status.passwordless_recovery.comp.dart index 1f952618..3ca3ef10 100644 --- a/lib/src/visual/views/settings/backup/passwordless_recovery/components/status.passwordless_recovery.comp.dart +++ b/lib/src/visual/views/settings/backup/passwordless_recovery/components/status.passwordless_recovery.comp.dart @@ -58,7 +58,9 @@ class PasswordLessRecoveryStatus extends StatelessWidget { ), const SizedBox(height: 4), Text( - context.lang.passwordlessRecoveryStatusEnabled(trustedFriendsCount), + context.lang.passwordlessRecoveryStatusEnabled( + trustedFriendsCount, + ), style: Theme.of(context).textTheme.bodyMedium ?.copyWith( color: context.color.onSurfaceVariant, diff --git a/lib/src/visual/views/settings/backup/passwordless_recovery/components/threshold_picker.comp.dart b/lib/src/visual/views/settings/backup/passwordless_recovery/components/threshold_picker.comp.dart index cf449f57..cdb02684 100644 --- a/lib/src/visual/views/settings/backup/passwordless_recovery/components/threshold_picker.comp.dart +++ b/lib/src/visual/views/settings/backup/passwordless_recovery/components/threshold_picker.comp.dart @@ -102,7 +102,9 @@ class ThresholdPicker extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( - color: isSelected ? context.color.primary : Colors.transparent, + color: isSelected + ? context.color.primary + : Colors.transparent, borderRadius: borderRadius, ), child: Center( diff --git a/lib/src/visual/views/settings/backup/passwordless_recovery/help_a_friend.passwordless_recovery.view.dart b/lib/src/visual/views/settings/backup/passwordless_recovery/help_a_friend.passwordless_recovery.view.dart index 12c0adeb..ed83ce64 100644 --- a/lib/src/visual/views/settings/backup/passwordless_recovery/help_a_friend.passwordless_recovery.view.dart +++ b/lib/src/visual/views/settings/backup/passwordless_recovery/help_a_friend.passwordless_recovery.view.dart @@ -152,7 +152,9 @@ class _HelpAFriendPasswordlessRecoveryViewState const SizedBox(width: 12), Expanded( child: Text( - context.lang.passwordlessRecoverySelectContactDesc, + context + .lang + .passwordlessRecoverySelectContactDesc, style: const TextStyle( fontSize: 14, height: 1.4, @@ -174,7 +176,9 @@ class _HelpAFriendPasswordlessRecoveryViewState child: filteredContacts.isEmpty ? Center( child: Text( - context.lang.passwordlessRecoveryNoContactsFound, + context + .lang + .passwordlessRecoveryNoContactsFound, ), ) : ListView.builder( @@ -208,7 +212,9 @@ class _HelpAFriendPasswordlessRecoveryViewState subtitle: hasShare ? null : Text( - context.lang.passwordlessRecoveryCantHelpHim, + context + .lang + .passwordlessRecoveryCantHelpHim, style: const TextStyle( color: Colors.grey, ), diff --git a/lib/src/visual/views/settings/data_and_storage/manage_storage.view.dart b/lib/src/visual/views/settings/data_and_storage/manage_storage.view.dart index da28a317..2e3d1258 100644 --- a/lib/src/visual/views/settings/data_and_storage/manage_storage.view.dart +++ b/lib/src/visual/views/settings/data_and_storage/manage_storage.view.dart @@ -90,7 +90,8 @@ class _ManageStorageViewState extends State { Expanded( child: Text( context.lang.backupFreeSpaceWithCloud, - style: Theme.of(context).textTheme.titleSmall?.copyWith( + style: Theme.of(context).textTheme.titleSmall + ?.copyWith( fontWeight: FontWeight.bold, ), ), diff --git a/pubspec.lock b/pubspec.lock index 42528d22..18a9dc74 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" + sha256: "1b0e6a07425a3e460666e88bf1c949ccc7bb0116ad562ce94a1eca60fe820725" url: "https://pub.dev" source: hosted - version: "93.0.0" + version: "103.0.0" _flutterfire_internals: dependency: transitive description: @@ -28,10 +28,18 @@ packages: dependency: transitive description: name: analyzer - sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b + sha256: "61c04d0c1bfed555c681ea079519933f071a5a026578ff73c4ff0df2d3462e5e" url: "https://pub.dev" source: hosted - version: "10.0.1" + version: "13.3.0" + analyzer_buffer: + dependency: transitive + description: + name: analyzer_buffer + sha256: "445b77e2054fa3e8c8a8ef1b5e9e6b23bb8028fffd34b5e60eaef315b7750674" + url: "https://pub.dev" + source: hosted + version: "0.3.3" app_links: dependency: "direct main" description: @@ -129,10 +137,10 @@ packages: dependency: transitive description: name: build - sha256: aadd943f4f8cc946882c954c187e6115a84c98c81ad1d9c6cbf0895a8c85da9c + sha256: b94f5da9ed3d081fd4ecc426c260998b5f4f4eb2f6d89b7e5472edef0b2b2a1b url: "https://pub.dev" source: hosted - version: "4.0.5" + version: "4.0.10" build_cli_annotations: dependency: transitive description: @@ -161,10 +169,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e" + sha256: be46e59dcfefd6c6c7127df9f1be6c1f89219fee22ae02a923463e0f286ce652 url: "https://pub.dev" source: hosted - version: "2.13.1" + version: "2.15.2" built_collection: dependency: transitive description: @@ -268,14 +276,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" - url: "https://pub.dev" - source: hosted - version: "4.11.1" collection: dependency: "direct main" description: @@ -352,10 +352,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" + sha256: "82ade9fc4273f29ed673e33166944465225b4f7fc5d4aaef48605cc751c18fc1" url: "https://pub.dev" source: hosted - version: "3.1.7" + version: "3.1.13" dbus: dependency: transitive description: @@ -391,18 +391,18 @@ packages: dependency: "direct main" description: name: drift - sha256: "055c249d1f91be5a47fe447f88afc24c4ca6f4cd6c5ed66767b4797d48acc2e5" + sha256: c21b9af62e78f86837638dda6c33506bd9444ca8a32cad2b99c07369cf9e2462 url: "https://pub.dev" source: hosted - version: "2.32.1" + version: "2.34.1" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "88a9de3af8571518148a6d8a513b57779fd1e60a026d3ab8a481a878fba01d91" + sha256: "735aad3c34215805c66bd518c8812fa4f83b5534b2c13c4092c970c04a7e9983" url: "https://pub.dev" source: hosted - version: "2.32.1" + version: "2.34.5" drift_flutter: dependency: "direct main" description: @@ -857,6 +857,22 @@ packages: url: "https://pub.dev" source: hosted version: "11.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "27585a6c2b9ac28ffb7b45d87fd1502332af1eb90e1c9f5eb1acb24c71d26e17" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -1146,10 +1162,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: fbcf404b03520e6e795f6b9b39badb2b788407dfc0a50cf39158a6ae1ca78925 + sha256: "2c15e78e1cc6e62aadecf59f81566fd56829713d96a8c4177699e2b2e17f20db" url: "https://pub.dev" source: hosted - version: "6.13.1" + version: "6.13.2" leak_tracker: dependency: transitive description: @@ -1747,18 +1763,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "732792cfd197d2161a65bb029606a46e0a18ff30ef9e141a7a82172b05ea8ecd" + sha256: a603f1fb984a7391ae5978d1b92bfaaa08b350dca5c825256f925818f7943bf5 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.4" source_helper: dependency: transitive description: name: source_helper - sha256: "1d3b229b2934034fb2e691fbb3d53e0f75a4af7b1407f88425ed8f209bcb1b8f" + sha256: "5e6f216fdf6376c9f3852381ae037499797a3385377d388b011dac98d303c67c" url: "https://pub.dev" source: hosted - version: "1.3.11" + version: "1.3.13" source_span: dependency: transitive description: @@ -1802,10 +1818,10 @@ packages: dependency: transitive description: name: sqlparser - sha256: ab2b467425f1d4f3acfa5fd11a08226f7d6c26ff102c06be1807e1dff34e050b + sha256: "772bb2f6f5bce0631a60f26b57d6e8882e1105c22345b05c163ee6ee5d7ba32d" url: "https://pub.dev" source: hosted - version: "0.44.3" + version: "0.45.0" stack_trace: dependency: transitive description: @@ -2155,5 +2171,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.12.0 <4.0.0" + dart: ">=3.13.0 <4.0.0" flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5e838714..8ff64fcd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -221,6 +221,7 @@ dev_dependencies: integration_test: sdk: flutter workmanager_platform_interface: any + freezed: ^4.0.0 flutter_launcher_icons: android: true diff --git a/rust/.sqlx/query-04bc6d004a70fe1ef001c3fe31f1cd7445eb0eb0a08be9a478cd38f4a1a26bb5.json b/rust/.sqlx/query-04bc6d004a70fe1ef001c3fe31f1cd7445eb0eb0a08be9a478cd38f4a1a26bb5.json new file mode 100644 index 00000000..ee8c74c4 --- /dev/null +++ b/rust/.sqlx/query-04bc6d004a70fe1ef001c3fe31f1cd7445eb0eb0a08be9a478cd38f4a1a26bb5.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "PRAGMA user_version = 25", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "04bc6d004a70fe1ef001c3fe31f1cd7445eb0eb0a08be9a478cd38f4a1a26bb5" +} diff --git a/rust/.sqlx/query-065a4f476b0322c400c1756c0cd109bfa0267e3668695ab9169cee567253d92b.json b/rust/.sqlx/query-065a4f476b0322c400c1756c0cd109bfa0267e3668695ab9169cee567253d92b.json new file mode 100644 index 00000000..80f433de --- /dev/null +++ b/rust/.sqlx/query-065a4f476b0322c400c1756c0cd109bfa0267e3668695ab9169cee567253d92b.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "PRAGMA defer_foreign_keys = ON", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "065a4f476b0322c400c1756c0cd109bfa0267e3668695ab9169cee567253d92b" +} diff --git a/rust/.sqlx/query-07d19c693f05b07d6d3c29ff7c77571d8dd6b4618434e36f2d9d97a9178ddb3b.json b/rust/.sqlx/query-07d19c693f05b07d6d3c29ff7c77571d8dd6b4618434e36f2d9d97a9178ddb3b.json new file mode 100644 index 00000000..97a6f7ef --- /dev/null +++ b/rust/.sqlx/query-07d19c693f05b07d6d3c29ff7c77571d8dd6b4618434e36f2d9d97a9178ddb3b.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT my_group_private_key IS NOT NULL\n FROM groups\n WHERE group_id = ?\n ", + "describe": { + "columns": [ + { + "name": "my_group_private_key IS NOT NULL", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "07d19c693f05b07d6d3c29ff7c77571d8dd6b4618434e36f2d9d97a9178ddb3b" +} diff --git a/rust/.sqlx/query-0a000bb74996bb70072c0b0dbb3bd54b690ec20c817a2cb1a29b0ac0fcc884ad.json b/rust/.sqlx/query-0a000bb74996bb70072c0b0dbb3bd54b690ec20c817a2cb1a29b0ac0fcc884ad.json new file mode 100644 index 00000000..b44300fb --- /dev/null +++ b/rust/.sqlx/query-0a000bb74996bb70072c0b0dbb3bd54b690ec20c817a2cb1a29b0ac0fcc884ad.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO messages(\n group_id,\n message_id,\n sender_id,\n type,\n content,\n quotes_message_id,\n created_at,\n ack_by_server\n ) VALUES (?, ?, ?, 'text', ?, ?, ?, CAST(strftime('%s', 'now') AS INTEGER))\n ON CONFLICT(message_id) DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 6 + }, + "nullable": [] + }, + "hash": "0a000bb74996bb70072c0b0dbb3bd54b690ec20c817a2cb1a29b0ac0fcc884ad" +} diff --git a/rust/.sqlx/query-0c7011264675a005d09dbe0f690e9475f8b8964a8cbce2b0dd4919bd5953bc08.json b/rust/.sqlx/query-0c7011264675a005d09dbe0f690e9475f8b8964a8cbce2b0dd4919bd5953bc08.json new file mode 100644 index 00000000..b20434ce --- /dev/null +++ b/rust/.sqlx/query-0c7011264675a005d09dbe0f690e9475f8b8964a8cbce2b0dd4919bd5953bc08.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT content\n FROM messages\n WHERE message_id = ?\n ", + "describe": { + "columns": [ + { + "name": "content", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "messages", + "name": "content" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true + ] + }, + "hash": "0c7011264675a005d09dbe0f690e9475f8b8964a8cbce2b0dd4919bd5953bc08" +} diff --git a/rust/.sqlx/query-117582dbddd40553a8a622ed65b24284d8fb91c141ba989e2e3f4846c93d2a4c.json b/rust/.sqlx/query-117582dbddd40553a8a622ed65b24284d8fb91c141ba989e2e3f4846c93d2a4c.json new file mode 100644 index 00000000..dce8db06 --- /dev/null +++ b/rust/.sqlx/query-117582dbddd40553a8a622ed65b24284d8fb91c141ba989e2e3f4846c93d2a4c.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET username = COALESCE(?, username),\n display_name = ?,\n avatar_svg_compressed = ?,\n sender_profile_counter = COALESCE(?, sender_profile_counter)\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 5 + }, + "nullable": [] + }, + "hash": "117582dbddd40553a8a622ed65b24284d8fb91c141ba989e2e3f4846c93d2a4c" +} diff --git a/rust/.sqlx/query-12cfa4cbc893602b97027c6f48c659e1b3d7b0ff8fc6037fbf6a89fbc8051584.json b/rust/.sqlx/query-12cfa4cbc893602b97027c6f48c659e1b3d7b0ff8fc6037fbf6a89fbc8051584.json new file mode 100644 index 00000000..d8646e4c --- /dev/null +++ b/rust/.sqlx/query-12cfa4cbc893602b97027c6f48c659e1b3d7b0ff8fc6037fbf6a89fbc8051584.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO contacts(user_id, username)\n VALUES(99, 'from old backup')\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "12cfa4cbc893602b97027c6f48c659e1b3d7b0ff8fc6037fbf6a89fbc8051584" +} diff --git a/rust/.sqlx/query-174e1f8d22842961cc26633c7f715a0c278dcda82838188586821354c306b1e9.json b/rust/.sqlx/query-174e1f8d22842961cc26633c7f715a0c278dcda82838188586821354c306b1e9.json new file mode 100644 index 00000000..c51fffc1 --- /dev/null +++ b/rust/.sqlx/query-174e1f8d22842961cc26633c7f715a0c278dcda82838188586821354c306b1e9.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT COUNT(*)\n FROM reactions\n WHERE message_id = 'path-text-message' AND sender_id = ? AND emoji = '👍'\n ", + "describe": { + "columns": [ + { + "name": "COUNT(*)", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "174e1f8d22842961cc26633c7f715a0c278dcda82838188586821354c306b1e9" +} diff --git a/rust/.sqlx/query-1acafcad398cb31491dcee91a005eddd33a03d40afa505b7e0b5f59e2f731a20.json b/rust/.sqlx/query-1acafcad398cb31491dcee91a005eddd33a03d40afa505b7e0b5f59e2f731a20.json new file mode 100644 index 00000000..a9746e54 --- /dev/null +++ b/rust/.sqlx/query-1acafcad398cb31491dcee91a005eddd33a03d40afa505b7e0b5f59e2f731a20.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE receipts\n SET mark_for_retry = CAST(strftime('%s', 'now') AS INTEGER),\n retry_count = retry_count + 1,\n ack_by_server_at = NULL\n WHERE receipt_id = ? AND contact_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "1acafcad398cb31491dcee91a005eddd33a03d40afa505b7e0b5f59e2f731a20" +} diff --git a/rust/.sqlx/query-1acb6b89980e132de5c016723d87d4aee9b08c0f0df9ad97431f408cae37fb7e.json b/rust/.sqlx/query-1acb6b89980e132de5c016723d87d4aee9b08c0f0df9ad97431f408cae37fb7e.json new file mode 100644 index 00000000..974d0d18 --- /dev/null +++ b/rust/.sqlx/query-1acb6b89980e132de5c016723d87d4aee9b08c0f0df9ad97431f408cae37fb7e.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO groups(\n group_id,\n state_encryption_key,\n my_group_private_key,\n group_name,\n joined_group\n ) VALUES (?, ?, ?, ?, 0)\n ON CONFLICT(group_id) DO UPDATE SET\n state_encryption_key = excluded.state_encryption_key,\n my_group_private_key = excluded.my_group_private_key,\n left_group = 0,\n deleted_content = 0\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "1acb6b89980e132de5c016723d87d4aee9b08c0f0df9ad97431f408cae37fb7e" +} diff --git a/rust/.sqlx/query-2070ef6adc65d84c9e5b4fd451a0023d518b365156981783a621484eefa89291.json b/rust/.sqlx/query-2070ef6adc65d84c9e5b4fd451a0023d518b365156981783a621484eefa89291.json new file mode 100644 index 00000000..0d4d1bf6 --- /dev/null +++ b/rust/.sqlx/query-2070ef6adc65d84c9e5b4fd451a0023d518b365156981783a621484eefa89291.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT EXISTS(SELECT 1 FROM contacts WHERE user_id = ?)", + "describe": { + "columns": [ + { + "name": "EXISTS(SELECT 1 FROM contacts WHERE user_id = ?)", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "2070ef6adc65d84c9e5b4fd451a0023d518b365156981783a621484eefa89291" +} diff --git a/rust/.sqlx/query-2136bd64733c0268d4da52cf36db8df3533d34a45008bddf61e1090ae1ace885.json b/rust/.sqlx/query-2136bd64733c0268d4da52cf36db8df3533d34a45008bddf61e1090ae1ace885.json new file mode 100644 index 00000000..53238ca7 --- /dev/null +++ b/rust/.sqlx/query-2136bd64733c0268d4da52cf36db8df3533d34a45008bddf61e1090ae1ace885.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO received_receipts(receipt_id)\n VALUES (?)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "2136bd64733c0268d4da52cf36db8df3533d34a45008bddf61e1090ae1ace885" +} diff --git a/rust/.sqlx/query-23bf972a725c666365f5bd324f10c6ffd1460d932a31f3099a697ac5c35b882f.json b/rust/.sqlx/query-23bf972a725c666365f5bd324f10c6ffd1460d932a31f3099a697ac5c35b882f.json new file mode 100644 index 00000000..2c28840d --- /dev/null +++ b/rust/.sqlx/query-23bf972a725c666365f5bd324f10c6ffd1460d932a31f3099a697ac5c35b882f.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET recovery_contacts_secret_share = ?,\n recovery_contacts_threshold = ?,\n recovery_contacts_last_heartbeat = NULL\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "23bf972a725c666365f5bd324f10c6ffd1460d932a31f3099a697ac5c35b882f" +} diff --git a/rust/.sqlx/query-2484b898369866b8e84d2cdcfb5730d18581563906c91333025699708269181c.json b/rust/.sqlx/query-2484b898369866b8e84d2cdcfb5730d18581563906c91333025699708269181c.json new file mode 100644 index 00000000..d8ed1394 --- /dev/null +++ b/rust/.sqlx/query-2484b898369866b8e84d2cdcfb5730d18581563906c91333025699708269181c.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO contacts(user_id, username)\n VALUES(1, 'current data')\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "2484b898369866b8e84d2cdcfb5730d18581563906c91333025699708269181c" +} diff --git a/rust/.sqlx/query-26099393d47834ce775cee6533f42d4cab0069c385c3efe8be764c6eacd8e112.json b/rust/.sqlx/query-26099393d47834ce775cee6533f42d4cab0069c385c3efe8be764c6eacd8e112.json new file mode 100644 index 00000000..1eb0eb1e --- /dev/null +++ b/rust/.sqlx/query-26099393d47834ce775cee6533f42d4cab0069c385c3efe8be764c6eacd8e112.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO messages(\n group_id,\n message_id,\n sender_id,\n type,\n media_id,\n additional_message_data,\n quotes_message_id,\n created_at\n ) VALUES (?, ?, ?, 'media', ?, ?, ?, ?)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 7 + }, + "nullable": [] + }, + "hash": "26099393d47834ce775cee6533f42d4cab0069c385c3efe8be764c6eacd8e112" +} diff --git a/rust/.sqlx/query-272b5a6defc13fb9fe0cedeab4e3e375eccc0513cf2ae0a7e6f95f817b65b1e6.json b/rust/.sqlx/query-272b5a6defc13fb9fe0cedeab4e3e375eccc0513cf2ae0a7e6f95f817b65b1e6.json new file mode 100644 index 00000000..e042f0f7 --- /dev/null +++ b/rust/.sqlx/query-272b5a6defc13fb9fe0cedeab4e3e375eccc0513cf2ae0a7e6f95f817b65b1e6.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT username\n FROM contacts\n WHERE user_id = 1\n ", + "describe": { + "columns": [ + { + "name": "username", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "contacts", + "name": "username" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "272b5a6defc13fb9fe0cedeab4e3e375eccc0513cf2ae0a7e6f95f817b65b1e6" +} diff --git a/rust/.sqlx/query-27b030470f79c8fdcd05b10291a9606f04c75c01243c14149a8f7e0e32bb408d.json b/rust/.sqlx/query-27b030470f79c8fdcd05b10291a9606f04c75c01243c14149a8f7e0e32bb408d.json new file mode 100644 index 00000000..82f9e511 --- /dev/null +++ b/rust/.sqlx/query-27b030470f79c8fdcd05b10291a9606f04c75c01243c14149a8f7e0e32bb408d.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_kyber_pre_keys(kyber_pre_key_id, record_bytes)\n VALUES (?, ?)\n ON CONFLICT(kyber_pre_key_id)\n DO UPDATE SET record_bytes = excluded.record_bytes\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "27b030470f79c8fdcd05b10291a9606f04c75c01243c14149a8f7e0e32bb408d" +} diff --git a/rust/.sqlx/query-2e26c6221d3d2d2d64087fe5c563e1632305e9af03a872635ffe99a822ddd643.json b/rust/.sqlx/query-2e26c6221d3d2d2d64087fe5c563e1632305e9af03a872635ffe99a822ddd643.json new file mode 100644 index 00000000..1c6b3aa9 --- /dev/null +++ b/rust/.sqlx/query-2e26c6221d3d2d2d64087fe5c563e1632305e9af03a872635ffe99a822ddd643.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT recovery_secret_share\n FROM contacts\n WHERE user_id = ?\n ", + "describe": { + "columns": [ + { + "name": "recovery_secret_share", + "ordinal": 0, + "type_info": "Blob", + "origin": { + "Table": { + "table": "contacts", + "name": "recovery_secret_share" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true + ] + }, + "hash": "2e26c6221d3d2d2d64087fe5c563e1632305e9af03a872635ffe99a822ddd643" +} diff --git a/rust/.sqlx/query-2ef49cbb297f02183510fd85882d4b1a117ad3500d9ea6a43d22c461623850c9.json b/rust/.sqlx/query-2ef49cbb297f02183510fd85882d4b1a117ad3500d9ea6a43d22c461623850c9.json new file mode 100644 index 00000000..93d75932 --- /dev/null +++ b/rust/.sqlx/query-2ef49cbb297f02183510fd85882d4b1a117ad3500d9ea6a43d22c461623850c9.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT media_id\n FROM messages\n WHERE message_id = ? AND sender_id = ?\n ", + "describe": { + "columns": [ + { + "name": "media_id", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "messages", + "name": "media_id" + } + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + true + ] + }, + "hash": "2ef49cbb297f02183510fd85882d4b1a117ad3500d9ea6a43d22c461623850c9" +} diff --git a/rust/.sqlx/query-3484d2f40693259f1e6030383a9d537ffee52100fe1cd01b25165c6557ac16f9.json b/rust/.sqlx/query-3484d2f40693259f1e6030383a9d537ffee52100fe1cd01b25165c6557ac16f9.json new file mode 100644 index 00000000..322a5b82 --- /dev/null +++ b/rust/.sqlx/query-3484d2f40693259f1e6030383a9d537ffee52100fe1cd01b25165c6557ac16f9.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE group_members\n SET last_chat_opened = ?, last_type_indicator = ?\n WHERE group_id = ? AND contact_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "3484d2f40693259f1e6030383a9d537ffee52100fe1cd01b25165c6557ac16f9" +} diff --git a/rust/.sqlx/query-370baafebd6f09754b0e29cf2b384c07426a989e8b1299414d6f5e7a9cdccadb.json b/rust/.sqlx/query-370baafebd6f09754b0e29cf2b384c07426a989e8b1299414d6f5e7a9cdccadb.json new file mode 100644 index 00000000..5da40a79 --- /dev/null +++ b/rust/.sqlx/query-370baafebd6f09754b0e29cf2b384c07426a989e8b1299414d6f5e7a9cdccadb.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET recovery_secret_share = ?\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "370baafebd6f09754b0e29cf2b384c07426a989e8b1299414d6f5e7a9cdccadb" +} diff --git a/rust/.sqlx/query-37ec1618898a149c969bc80fb54f93d5f354e7a9f380ce3420c8feb6378c9ac3.json b/rust/.sqlx/query-37ec1618898a149c969bc80fb54f93d5f354e7a9f380ce3420c8feb6378c9ac3.json new file mode 100644 index 00000000..cffe7280 --- /dev/null +++ b/rust/.sqlx/query-37ec1618898a149c969bc80fb54f93d5f354e7a9f380ce3420c8feb6378c9ac3.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT EXISTS(SELECT 1 FROM groups WHERE group_id = ?)", + "describe": { + "columns": [ + { + "name": "EXISTS(SELECT 1 FROM groups WHERE group_id = ?)", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "37ec1618898a149c969bc80fb54f93d5f354e7a9f380ce3420c8feb6378c9ac3" +} diff --git a/rust/.sqlx/query-3a31981dfd8f53dd63744ae7c15ee125effcfc116476eeda3fbdfc7eb4c26ec9.json b/rust/.sqlx/query-3a31981dfd8f53dd63744ae7c15ee125effcfc116476eeda3fbdfc7eb4c26ec9.json new file mode 100644 index 00000000..940c24e0 --- /dev/null +++ b/rust/.sqlx/query-3a31981dfd8f53dd63744ae7c15ee125effcfc116476eeda3fbdfc7eb4c26ec9.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO app_metadata(key, value)\n VALUES('schema_version', ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "3a31981dfd8f53dd63744ae7c15ee125effcfc116476eeda3fbdfc7eb4c26ec9" +} diff --git a/rust/.sqlx/query-400da22eb89f22254b6de1cfcea0b0abaf5e05e73a4c534ee45bc0b950c1e356.json b/rust/.sqlx/query-400da22eb89f22254b6de1cfcea0b0abaf5e05e73a4c534ee45bc0b950c1e356.json new file mode 100644 index 00000000..bf8f3687 --- /dev/null +++ b/rust/.sqlx/query-400da22eb89f22254b6de1cfcea0b0abaf5e05e73a4c534ee45bc0b950c1e356.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT value\n FROM app_metadata\n WHERE key = 'legacy_import_complete'\n ", + "describe": { + "columns": [ + { + "name": "value", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "app_metadata", + "name": "value" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "400da22eb89f22254b6de1cfcea0b0abaf5e05e73a4c534ee45bc0b950c1e356" +} diff --git a/rust/.sqlx/query-42faf2b3351e83b2313cdac31bb03b45f8106967cbdc6e25cb46d92086811ac3.json b/rust/.sqlx/query-42faf2b3351e83b2313cdac31bb03b45f8106967cbdc6e25cb46d92086811ac3.json new file mode 100644 index 00000000..a7a711c9 --- /dev/null +++ b/rust/.sqlx/query-42faf2b3351e83b2313cdac31bb03b45f8106967cbdc6e25cb46d92086811ac3.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET recovery_last_heartbeat = CASE\n WHEN ? THEN CAST(strftime('%s', 'now') AS INTEGER)\n ELSE NULL\n END\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "42faf2b3351e83b2313cdac31bb03b45f8106967cbdc6e25cb46d92086811ac3" +} diff --git a/rust/.sqlx/query-4681180c0b45a7f3941ee9d58966dc6eb8a039744e80facc991c7fa3aa8be029.json b/rust/.sqlx/query-4681180c0b45a7f3941ee9d58966dc6eb8a039744e80facc991c7fa3aa8be029.json new file mode 100644 index 00000000..f2652147 --- /dev/null +++ b/rust/.sqlx/query-4681180c0b45a7f3941ee9d58966dc6eb8a039744e80facc991c7fa3aa8be029.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT identity_key FROM signal_identities WHERE name = ?", + "describe": { + "columns": [ + { + "name": "identity_key", + "ordinal": 0, + "type_info": "Blob", + "origin": { + "Table": { + "table": "signal_identities", + "name": "identity_key" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "4681180c0b45a7f3941ee9d58966dc6eb8a039744e80facc991c7fa3aa8be029" +} diff --git a/rust/.sqlx/query-49a771af89168b9e3d09e2b0cbe49eb2a51189212e1cdb99fb8211bd614c9558.json b/rust/.sqlx/query-49a771af89168b9e3d09e2b0cbe49eb2a51189212e1cdb99fb8211bd614c9558.json new file mode 100644 index 00000000..53651e0f --- /dev/null +++ b/rust/.sqlx/query-49a771af89168b9e3d09e2b0cbe49eb2a51189212e1cdb99fb8211bd614c9558.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET requested = 0, deleted_by_user = 0\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "49a771af89168b9e3d09e2b0cbe49eb2a51189212e1cdb99fb8211bd614c9558" +} diff --git a/rust/.sqlx/query-4d18233931fbadbfc01254449b9a9dd1913ecf014c117f3b7d77e6d8267dd7d6.json b/rust/.sqlx/query-4d18233931fbadbfc01254449b9a9dd1913ecf014c117f3b7d77e6d8267dd7d6.json new file mode 100644 index 00000000..64461470 --- /dev/null +++ b/rust/.sqlx/query-4d18233931fbadbfc01254449b9a9dd1913ecf014c117f3b7d77e6d8267dd7d6.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_identities (name, identity_key, timestamp)\n VALUES (?, ?, 0)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "4d18233931fbadbfc01254449b9a9dd1913ecf014c117f3b7d77e6d8267dd7d6" +} diff --git a/rust/.sqlx/query-500c182a7888c7b36c257936ba188aff109a5dac250d5402f33b55ec9d127f76.json b/rust/.sqlx/query-500c182a7888c7b36c257936ba188aff109a5dac250d5402f33b55ec9d127f76.json new file mode 100644 index 00000000..4def1f40 --- /dev/null +++ b/rust/.sqlx/query-500c182a7888c7b36c257936ba188aff109a5dac250d5402f33b55ec9d127f76.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT EXISTS(\n SELECT 1\n FROM received_receipts\n WHERE receipt_id = ?\n )\n ", + "describe": { + "columns": [ + { + "name": "EXISTS(\n SELECT 1\n FROM received_receipts\n WHERE receipt_id = ?\n )", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "500c182a7888c7b36c257936ba188aff109a5dac250d5402f33b55ec9d127f76" +} diff --git a/rust/.sqlx/query-52b32153dd0cc9f5efca773487839fab7ed13b2124ec6153a0b55a3afe3c4ded.json b/rust/.sqlx/query-52b32153dd0cc9f5efca773487839fab7ed13b2124ec6153a0b55a3afe3c4ded.json new file mode 100644 index 00000000..b1ab790d --- /dev/null +++ b/rust/.sqlx/query-52b32153dd0cc9f5efca773487839fab7ed13b2124ec6153a0b55a3afe3c4ded.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT record_bytes FROM signal_pre_keys WHERE pre_key_id = ?", + "describe": { + "columns": [ + { + "name": "record_bytes", + "ordinal": 0, + "type_info": "Blob", + "origin": { + "Table": { + "table": "signal_pre_keys", + "name": "record_bytes" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "52b32153dd0cc9f5efca773487839fab7ed13b2124ec6153a0b55a3afe3c4ded" +} diff --git a/rust/.sqlx/query-5357d7c57bd2d7a7d2c52e4dd41e90bdea08f915d736745592ddd375171787f8.json b/rust/.sqlx/query-5357d7c57bd2d7a7d2c52e4dd41e90bdea08f915d736745592ddd375171787f8.json new file mode 100644 index 00000000..f413e567 --- /dev/null +++ b/rust/.sqlx/query-5357d7c57bd2d7a7d2c52e4dd41e90bdea08f915d736745592ddd375171787f8.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE messages\n SET is_deleted_from_sender = 1, modified_at = ?\n WHERE message_id = ? AND sender_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "5357d7c57bd2d7a7d2c52e4dd41e90bdea08f915d736745592ddd375171787f8" +} diff --git a/rust/.sqlx/query-5703922d81e137ae18f060aebc15210f118dc0ab28d445b2375cf789987525ab.json b/rust/.sqlx/query-5703922d81e137ae18f060aebc15210f118dc0ab28d445b2375cf789987525ab.json new file mode 100644 index 00000000..00a6f991 --- /dev/null +++ b/rust/.sqlx/query-5703922d81e137ae18f060aebc15210f118dc0ab28d445b2375cf789987525ab.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "PRAGMA integrity_check", + "describe": { + "columns": [ + { + "name": "integrity_check", + "ordinal": 0, + "type_info": "Text", + "origin": "Expression" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + null + ] + }, + "hash": "5703922d81e137ae18f060aebc15210f118dc0ab28d445b2375cf789987525ab" +} diff --git a/rust/.sqlx/query-5988df8ec10b625bbd16ddcd2dccad54f2b209e74849509c1905e0f106756c7d.json b/rust/.sqlx/query-5988df8ec10b625bbd16ddcd2dccad54f2b209e74849509c1905e0f106756c7d.json new file mode 100644 index 00000000..2d65b321 --- /dev/null +++ b/rust/.sqlx/query-5988df8ec10b625bbd16ddcd2dccad54f2b209e74849509c1905e0f106756c7d.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT sender_id\n FROM messages\n WHERE message_id = ?\n ", + "describe": { + "columns": [ + { + "name": "sender_id", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "messages", + "name": "sender_id" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true + ] + }, + "hash": "5988df8ec10b625bbd16ddcd2dccad54f2b209e74849509c1905e0f106756c7d" +} diff --git a/rust/.sqlx/query-5b848eba9965a386b9189ff911015c9652305c82281efd37491139b814a4562a.json b/rust/.sqlx/query-5b848eba9965a386b9189ff911015c9652305c82281efd37491139b814a4562a.json new file mode 100644 index 00000000..aeaec2f6 --- /dev/null +++ b/rust/.sqlx/query-5b848eba9965a386b9189ff911015c9652305c82281efd37491139b814a4562a.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE groups\n SET also_best_friend = ?,\n flame_counter = MAX(flame_counter, ?),\n max_flame_counter = MAX(max_flame_counter, ?)\n WHERE group_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "5b848eba9965a386b9189ff911015c9652305c82281efd37491139b814a4562a" +} diff --git a/rust/.sqlx/query-5c21ee90d244d4859b3b9544f182dd071b91356acbf297ced95ebc496ff03091.json b/rust/.sqlx/query-5c21ee90d244d4859b3b9544f182dd071b91356acbf297ced95ebc496ff03091.json new file mode 100644 index 00000000..b77bdb16 --- /dev/null +++ b/rust/.sqlx/query-5c21ee90d244d4859b3b9544f182dd071b91356acbf297ced95ebc496ff03091.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET accepted = 0, requested = 1\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "5c21ee90d244d4859b3b9544f182dd071b91356acbf297ced95ebc496ff03091" +} diff --git a/rust/.sqlx/query-5c8abe1148ff7ea5b69309413292d9ad0a5cd529fae889a28fff935174528ed1.json b/rust/.sqlx/query-5c8abe1148ff7ea5b69309413292d9ad0a5cd529fae889a28fff935174528ed1.json new file mode 100644 index 00000000..4e03092c --- /dev/null +++ b/rust/.sqlx/query-5c8abe1148ff7ea5b69309413292d9ad0a5cd529fae889a28fff935174528ed1.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT record_bytes FROM signal_sessions WHERE name = ? AND device_id = ?", + "describe": { + "columns": [ + { + "name": "record_bytes", + "ordinal": 0, + "type_info": "Blob", + "origin": { + "Table": { + "table": "signal_sessions", + "name": "record_bytes" + } + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false + ] + }, + "hash": "5c8abe1148ff7ea5b69309413292d9ad0a5cd529fae889a28fff935174528ed1" +} diff --git a/rust/.sqlx/query-5d60b9223c1454cb582c834911fe078e33f3cc279660cdf2ade2319b940e7a4e.json b/rust/.sqlx/query-5d60b9223c1454cb582c834911fe078e33f3cc279660cdf2ade2319b940e7a4e.json new file mode 100644 index 00000000..2dde2314 --- /dev/null +++ b/rust/.sqlx/query-5d60b9223c1454cb582c834911fe078e33f3cc279660cdf2ade2319b940e7a4e.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_pre_keys(pre_key_id, record_bytes) VALUES (?, ?)\n ON CONFLICT(pre_key_id) DO UPDATE SET record_bytes = excluded.record_bytes\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "5d60b9223c1454cb582c834911fe078e33f3cc279660cdf2ade2319b940e7a4e" +} diff --git a/rust/.sqlx/query-5f88312cd175e5fdf5539cf9bc49c2e2103def24c54db6c58a9beffd80036472.json b/rust/.sqlx/query-5f88312cd175e5fdf5539cf9bc49c2e2103def24c54db6c58a9beffd80036472.json new file mode 100644 index 00000000..469257f7 --- /dev/null +++ b/rust/.sqlx/query-5f88312cd175e5fdf5539cf9bc49c2e2103def24c54db6c58a9beffd80036472.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET recovery_contacts_secret_share = NULL,\n recovery_contacts_last_heartbeat = NULL,\n recovery_contacts_threshold = NULL\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "5f88312cd175e5fdf5539cf9bc49c2e2103def24c54db6c58a9beffd80036472" +} diff --git a/rust/.sqlx/query-61c2483b50f9f5c8a586eaa2c2381b06898954f5c898abf280b9ab02d96ff457.json b/rust/.sqlx/query-61c2483b50f9f5c8a586eaa2c2381b06898954f5c898abf280b9ab02d96ff457.json new file mode 100644 index 00000000..c10f1ad5 --- /dev/null +++ b/rust/.sqlx/query-61c2483b50f9f5c8a586eaa2c2381b06898954f5c898abf280b9ab02d96ff457.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO messages(group_id, message_id, sender_id, type)\n VALUES('group', 'message', 7, 'text')\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "61c2483b50f9f5c8a586eaa2c2381b06898954f5c898abf280b9ab02d96ff457" +} diff --git a/rust/.sqlx/query-64de4aee234dbfab635c65bb995f513be49cc860f298cf6e2cd9dc1f71265047.json b/rust/.sqlx/query-64de4aee234dbfab635c65bb995f513be49cc860f298cf6e2cd9dc1f71265047.json new file mode 100644 index 00000000..4270dbaa --- /dev/null +++ b/rust/.sqlx/query-64de4aee234dbfab635c65bb995f513be49cc860f298cf6e2cd9dc1f71265047.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_identities(name, identity_key, timestamp)\n VALUES (?, ?, ?)\n ON CONFLICT(name) DO UPDATE SET\n identity_key = excluded.identity_key,\n timestamp = excluded.timestamp\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "64de4aee234dbfab635c65bb995f513be49cc860f298cf6e2cd9dc1f71265047" +} diff --git a/rust/.sqlx/query-66b487bac770deb373505952150d939ca95add84b329bd100ef14911babe33d1.json b/rust/.sqlx/query-66b487bac770deb373505952150d939ca95add84b329bd100ef14911babe33d1.json new file mode 100644 index 00000000..2341ed61 --- /dev/null +++ b/rust/.sqlx/query-66b487bac770deb373505952150d939ca95add84b329bd100ef14911babe33d1.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT record_bytes FROM signal_kyber_pre_keys WHERE kyber_pre_key_id = ?", + "describe": { + "columns": [ + { + "name": "record_bytes", + "ordinal": 0, + "type_info": "Blob", + "origin": { + "Table": { + "table": "signal_kyber_pre_keys", + "name": "record_bytes" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "66b487bac770deb373505952150d939ca95add84b329bd100ef14911babe33d1" +} diff --git a/rust/.sqlx/query-6c01273c757a772f2c2726b75a95df7f39ee9353043ec309fe128183fe1cdcd6.json b/rust/.sqlx/query-6c01273c757a772f2c2726b75a95df7f39ee9353043ec309fe128183fe1cdcd6.json new file mode 100644 index 00000000..94a3b1a1 --- /dev/null +++ b/rust/.sqlx/query-6c01273c757a772f2c2726b75a95df7f39ee9353043ec309fe128183fe1cdcd6.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT COUNT(*) FROM reactions", + "describe": { + "columns": [ + { + "name": "COUNT(*)", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "6c01273c757a772f2c2726b75a95df7f39ee9353043ec309fe128183fe1cdcd6" +} diff --git a/rust/.sqlx/query-6c4c06153ae73f7e638284c8f1ea932fa530f6aac3f13d41e8db5f9805691cfb.json b/rust/.sqlx/query-6c4c06153ae73f7e638284c8f1ea932fa530f6aac3f13d41e8db5f9805691cfb.json new file mode 100644 index 00000000..cae9df15 --- /dev/null +++ b/rust/.sqlx/query-6c4c06153ae73f7e638284c8f1ea932fa530f6aac3f13d41e8db5f9805691cfb.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE messages\n SET media_stored = 1\n WHERE message_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "6c4c06153ae73f7e638284c8f1ea932fa530f6aac3f13d41e8db5f9805691cfb" +} diff --git a/rust/.sqlx/query-6ccbb2b713538eff9f1f875d34db485dea137371d504e1d3edf724174dff0264.json b/rust/.sqlx/query-6ccbb2b713538eff9f1f875d34db485dea137371d504e1d3edf724174dff0264.json new file mode 100644 index 00000000..9c88dd8d --- /dev/null +++ b/rust/.sqlx/query-6ccbb2b713538eff9f1f875d34db485dea137371d504e1d3edf724174dff0264.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE media_files\n SET upload_state = 'reuploadRequested',\n reupload_requested_by = ?\n WHERE media_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "6ccbb2b713538eff9f1f875d34db485dea137371d504e1d3edf724174dff0264" +} diff --git a/rust/.sqlx/query-6f27065bbb1277aa35530ac6bce6e780683555d0c125183b949846950cfac139.json b/rust/.sqlx/query-6f27065bbb1277aa35530ac6bce6e780683555d0c125183b949846950cfac139.json new file mode 100644 index 00000000..268dca09 --- /dev/null +++ b/rust/.sqlx/query-6f27065bbb1277aa35530ac6bce6e780683555d0c125183b949846950cfac139.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE messages\n SET media_reopened = 1\n WHERE message_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "6f27065bbb1277aa35530ac6bce6e780683555d0c125183b949846950cfac139" +} diff --git a/rust/.sqlx/query-713b45ba555a737f059569886ba38eedcd0664aa6c8a6d2190802b7a85e77371.json b/rust/.sqlx/query-713b45ba555a737f059569886ba38eedcd0664aa6c8a6d2190802b7a85e77371.json new file mode 100644 index 00000000..7a8dfc58 --- /dev/null +++ b/rust/.sqlx/query-713b45ba555a737f059569886ba38eedcd0664aa6c8a6d2190802b7a85e77371.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT EXISTS(\n SELECT 1 FROM contacts WHERE user_id = ?\n )\n ", + "describe": { + "columns": [ + { + "name": "EXISTS(\n SELECT 1 FROM contacts WHERE user_id = ?\n )", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "713b45ba555a737f059569886ba38eedcd0664aa6c8a6d2190802b7a85e77371" +} diff --git a/rust/.sqlx/query-71d28416e43499e2d898f978567f96a52256b401625ae513f9b9c7cef623c1d8.json b/rust/.sqlx/query-71d28416e43499e2d898f978567f96a52256b401625ae513f9b9c7cef623c1d8.json new file mode 100644 index 00000000..d4cc7677 --- /dev/null +++ b/rust/.sqlx/query-71d28416e43499e2d898f978567f96a52256b401625ae513f9b9c7cef623c1d8.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO app_metadata(key, value)\n VALUES('legacy_schema_version', '25')\n ON CONFLICT(key) DO UPDATE SET value = excluded.value\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "71d28416e43499e2d898f978567f96a52256b401625ae513f9b9c7cef623c1d8" +} diff --git a/rust/.sqlx/query-7498999dcb013e18ec840820caeb996779d7aa4d74b69c8da97f0c429a41a3aa.json b/rust/.sqlx/query-7498999dcb013e18ec840820caeb996779d7aa4d74b69c8da97f0c429a41a3aa.json new file mode 100644 index 00000000..14ad027f --- /dev/null +++ b/rust/.sqlx/query-7498999dcb013e18ec840820caeb996779d7aa4d74b69c8da97f0c429a41a3aa.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET requested = 0, accepted = 1, deleted_by_user = 0\n WHERE user_id = ? AND requested = 0\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "7498999dcb013e18ec840820caeb996779d7aa4d74b69c8da97f0c429a41a3aa" +} diff --git a/rust/.sqlx/query-790ea7b81c3e1c7b65c00f160702a99c05846d0d37f7d7024e8345562072b0fb.json b/rust/.sqlx/query-790ea7b81c3e1c7b65c00f160702a99c05846d0d37f7d7024e8345562072b0fb.json new file mode 100644 index 00000000..15f119af --- /dev/null +++ b/rust/.sqlx/query-790ea7b81c3e1c7b65c00f160702a99c05846d0d37f7d7024e8345562072b0fb.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT message_id\n FROM receipts\n WHERE receipt_id = ? AND contact_id = ?\n ", + "describe": { + "columns": [ + { + "name": "message_id", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "receipts", + "name": "message_id" + } + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + true + ] + }, + "hash": "790ea7b81c3e1c7b65c00f160702a99c05846d0d37f7d7024e8345562072b0fb" +} diff --git a/rust/.sqlx/query-81135d8e9734594afc97deee965b5b2db4edab812e94912b4a70c3a83ad91933.json b/rust/.sqlx/query-81135d8e9734594afc97deee965b5b2db4edab812e94912b4a70c3a83ad91933.json new file mode 100644 index 00000000..ff979a32 --- /dev/null +++ b/rust/.sqlx/query-81135d8e9734594afc97deee965b5b2db4edab812e94912b4a70c3a83ad91933.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE messages\n SET opened_at = ?, opened_by_all = ?\n WHERE message_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "81135d8e9734594afc97deee965b5b2db4edab812e94912b4a70c3a83ad91933" +} diff --git a/rust/.sqlx/query-81485ddfb46f464b8f8c6813a2e5528dbbf783b55a6731e43fae04b1f90fa13b.json b/rust/.sqlx/query-81485ddfb46f464b8f8c6813a2e5528dbbf783b55a6731e43fae04b1f90fa13b.json new file mode 100644 index 00000000..0b315645 --- /dev/null +++ b/rust/.sqlx/query-81485ddfb46f464b8f8c6813a2e5528dbbf783b55a6731e43fae04b1f90fa13b.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM signal_pre_keys WHERE pre_key_id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "81485ddfb46f464b8f8c6813a2e5528dbbf783b55a6731e43fae04b1f90fa13b" +} diff --git a/rust/.sqlx/query-82d093614f398c7aac81e4081858e278854f560b6b184e02ea9eddad4d8d4a51.json b/rust/.sqlx/query-82d093614f398c7aac81e4081858e278854f560b6b184e02ea9eddad4d8d4a51.json new file mode 100644 index 00000000..43989b56 --- /dev/null +++ b/rust/.sqlx/query-82d093614f398c7aac81e4081858e278854f560b6b184e02ea9eddad4d8d4a51.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO app_metadata(key, value)\n VALUES('legacy_schema_version', ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "82d093614f398c7aac81e4081858e278854f560b6b184e02ea9eddad4d8d4a51" +} diff --git a/rust/.sqlx/query-84c77f8397fdf0dcf0d3c9ccab7f3854d2d607de0662b9e5f7d279f5e9417ebf.json b/rust/.sqlx/query-84c77f8397fdf0dcf0d3c9ccab7f3854d2d607de0662b9e5f7d279f5e9417ebf.json new file mode 100644 index 00000000..6b5d5241 --- /dev/null +++ b/rust/.sqlx/query-84c77f8397fdf0dcf0d3c9ccab7f3854d2d607de0662b9e5f7d279f5e9417ebf.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET requested = 1, deleted_by_user = 0\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "84c77f8397fdf0dcf0d3c9ccab7f3854d2d607de0662b9e5f7d279f5e9417ebf" +} diff --git a/rust/.sqlx/query-862a9b6bef72830c7439b51f3311d6c338937916d57ab6640f03ad44f6845fb6.json b/rust/.sqlx/query-862a9b6bef72830c7439b51f3311d6c338937916d57ab6640f03ad44f6845fb6.json new file mode 100644 index 00000000..382aa297 --- /dev/null +++ b/rust/.sqlx/query-862a9b6bef72830c7439b51f3311d6c338937916d57ab6640f03ad44f6845fb6.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO group_members(group_id, contact_id, group_public_key)\n VALUES (?, ?, ?)\n ON CONFLICT(group_id, contact_id)\n DO UPDATE SET group_public_key = excluded.group_public_key\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "862a9b6bef72830c7439b51f3311d6c338937916d57ab6640f03ad44f6845fb6" +} diff --git a/rust/.sqlx/query-8babc49259c8ba5332ad81a7c11027a03ca6d6e2b163d16670fd8505f5d27142.json b/rust/.sqlx/query-8babc49259c8ba5332ad81a7c11027a03ca6d6e2b163d16670fd8505f5d27142.json new file mode 100644 index 00000000..ca3b923e --- /dev/null +++ b/rust/.sqlx/query-8babc49259c8ba5332ad81a7c11027a03ca6d6e2b163d16670fd8505f5d27142.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT COALESCE(MAX(signed_pre_key_id), 0) AS \"id!: u32\" FROM signal_signed_pre_keys", + "describe": { + "columns": [ + { + "name": "id!: u32", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "8babc49259c8ba5332ad81a7c11027a03ca6d6e2b163d16670fd8505f5d27142" +} diff --git a/rust/.sqlx/query-8edba8bc58b60b678cb1730572119bd0f1ba1f98232e9d61eb0b7c920a5526f0.json b/rust/.sqlx/query-8edba8bc58b60b678cb1730572119bd0f1ba1f98232e9d61eb0b7c920a5526f0.json new file mode 100644 index 00000000..9b586c76 --- /dev/null +++ b/rust/.sqlx/query-8edba8bc58b60b678cb1730572119bd0f1ba1f98232e9d61eb0b7c920a5526f0.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT media_id\n FROM messages\n WHERE message_id = ? AND sender_id = ?\n ", + "describe": { + "columns": [ + { + "name": "media_id", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "messages", + "name": "media_id" + } + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + true + ] + }, + "hash": "8edba8bc58b60b678cb1730572119bd0f1ba1f98232e9d61eb0b7c920a5526f0" +} diff --git a/rust/.sqlx/query-93e9fb25a53d3de02840e86d0c450c364e2e4c3b4af9d015fb2fbb87770e703f.json b/rust/.sqlx/query-93e9fb25a53d3de02840e86d0c450c364e2e4c3b4af9d015fb2fbb87770e703f.json new file mode 100644 index 00000000..dace5654 --- /dev/null +++ b/rust/.sqlx/query-93e9fb25a53d3de02840e86d0c450c364e2e4c3b4af9d015fb2fbb87770e703f.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO groups(group_id, group_name, is_direct_chat, joined_group)\n VALUES (?, ?, 1, 1)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "93e9fb25a53d3de02840e86d0c450c364e2e4c3b4af9d015fb2fbb87770e703f" +} diff --git a/rust/.sqlx/query-96a6b8415b84654e0bc41298aa4ca886e42740dddf2e4db1a4a289e4f75a1ff4.json b/rust/.sqlx/query-96a6b8415b84654e0bc41298aa4ca886e42740dddf2e4db1a4a289e4f75a1ff4.json new file mode 100644 index 00000000..926f2c7e --- /dev/null +++ b/rust/.sqlx/query-96a6b8415b84654e0bc41298aa4ca886e42740dddf2e4db1a4a289e4f75a1ff4.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT EXISTS(\n SELECT 1\n FROM received_receipts\n WHERE receipt_id = ?\n )\n ", + "describe": { + "columns": [ + { + "name": "EXISTS(\n SELECT 1\n FROM received_receipts\n WHERE receipt_id = ?\n )", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "96a6b8415b84654e0bc41298aa4ca886e42740dddf2e4db1a4a289e4f75a1ff4" +} diff --git a/rust/.sqlx/query-9b551cd015eecff75483d9083ee4ebfc5c265e0b2b0bb9a032ce90778a68e512.json b/rust/.sqlx/query-9b551cd015eecff75483d9083ee4ebfc5c265e0b2b0bb9a032ce90778a68e512.json new file mode 100644 index 00000000..cc55de34 --- /dev/null +++ b/rust/.sqlx/query-9b551cd015eecff75483d9083ee4ebfc5c265e0b2b0bb9a032ce90778a68e512.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT value\n FROM app_metadata\n WHERE key = 'legacy_schema_version'\n ", + "describe": { + "columns": [ + { + "name": "value", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "app_metadata", + "name": "value" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "9b551cd015eecff75483d9083ee4ebfc5c265e0b2b0bb9a032ce90778a68e512" +} diff --git a/rust/.sqlx/query-9b83943b4b9917ba6e099bbf0bd69d4666813e03732f448f83a884134c300bfd.json b/rust/.sqlx/query-9b83943b4b9917ba6e099bbf0bd69d4666813e03732f448f83a884134c300bfd.json new file mode 100644 index 00000000..bc6d8916 --- /dev/null +++ b/rust/.sqlx/query-9b83943b4b9917ba6e099bbf0bd69d4666813e03732f448f83a884134c300bfd.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET ask_for_friend_promotions = COALESCE(ask_for_friend_promotions, 1)\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "9b83943b4b9917ba6e099bbf0bd69d4666813e03732f448f83a884134c300bfd" +} diff --git a/rust/.sqlx/query-9f0ca2175ab73256273ddcb658a9f0d61d427a97869c42361c3b7611deb3cad7.json b/rust/.sqlx/query-9f0ca2175ab73256273ddcb658a9f0d61d427a97869c42361c3b7611deb3cad7.json new file mode 100644 index 00000000..cbab574c --- /dev/null +++ b/rust/.sqlx/query-9f0ca2175ab73256273ddcb658a9f0d61d427a97869c42361c3b7611deb3cad7.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE receipts\n SET mark_for_retry_after_accepted = CAST(strftime('%s', 'now') AS INTEGER)\n WHERE receipt_id = ? AND contact_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "9f0ca2175ab73256273ddcb658a9f0d61d427a97869c42361c3b7611deb3cad7" +} diff --git a/rust/.sqlx/query-a110a1d7903937bf0ef806afc52410af6830f49d07258693fba1dfa765e70d23.json b/rust/.sqlx/query-a110a1d7903937bf0ef806afc52410af6830f49d07258693fba1dfa765e70d23.json new file mode 100644 index 00000000..d8ad858c --- /dev/null +++ b/rust/.sqlx/query-a110a1d7903937bf0ef806afc52410af6830f49d07258693fba1dfa765e70d23.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT username\n FROM contacts\n WHERE user_id = 7\n ", + "describe": { + "columns": [ + { + "name": "username", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "contacts", + "name": "username" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "a110a1d7903937bf0ef806afc52410af6830f49d07258693fba1dfa765e70d23" +} diff --git a/rust/.sqlx/query-a28ac461ed3b995287f2cfd9480c51562ba580cc34f8835163ee7145833e1455.json b/rust/.sqlx/query-a28ac461ed3b995287f2cfd9480c51562ba580cc34f8835163ee7145833e1455.json new file mode 100644 index 00000000..a063c3bf --- /dev/null +++ b/rust/.sqlx/query-a28ac461ed3b995287f2cfd9480c51562ba580cc34f8835163ee7145833e1455.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO main.sqlite_sequence(name, seq) VALUES(?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "a28ac461ed3b995287f2cfd9480c51562ba580cc34f8835163ee7145833e1455" +} diff --git a/rust/.sqlx/query-a5f4452d79d2ac19f158fb5c7b92f0f96e10a5e99788123a294763100d4e25dc.json b/rust/.sqlx/query-a5f4452d79d2ac19f158fb5c7b92f0f96e10a5e99788123a294763100d4e25dc.json new file mode 100644 index 00000000..64a4b7b7 --- /dev/null +++ b/rust/.sqlx/query-a5f4452d79d2ac19f158fb5c7b92f0f96e10a5e99788123a294763100d4e25dc.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO app_metadata(key, value)\n VALUES('legacy_import_complete', '1')\n ON CONFLICT(key) DO UPDATE SET value = excluded.value\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "a5f4452d79d2ac19f158fb5c7b92f0f96e10a5e99788123a294763100d4e25dc" +} diff --git a/rust/.sqlx/query-a90a25b72f9fb6269a4943fda3da3c8cc58b2d692fb79e9445e070d949afc9ab.json b/rust/.sqlx/query-a90a25b72f9fb6269a4943fda3da3c8cc58b2d692fb79e9445e070d949afc9ab.json new file mode 100644 index 00000000..1d7abca1 --- /dev/null +++ b/rust/.sqlx/query-a90a25b72f9fb6269a4943fda3da3c8cc58b2d692fb79e9445e070d949afc9ab.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO messages(\n group_id,\n message_id,\n sender_id,\n type,\n additional_message_data,\n created_at,\n ack_by_server\n ) VALUES (?, ?, ?, ?, ?, ?, CAST(strftime('%s', 'now') AS INTEGER))\n ON CONFLICT(message_id) DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 6 + }, + "nullable": [] + }, + "hash": "a90a25b72f9fb6269a4943fda3da3c8cc58b2d692fb79e9445e070d949afc9ab" +} diff --git a/rust/.sqlx/query-ac89da5b76bdd44d6c3c8901b2d99ff9352ab497a2abb687a1b8ef3cb09701ab.json b/rust/.sqlx/query-ac89da5b76bdd44d6c3c8901b2d99ff9352ab497a2abb687a1b8ef3cb09701ab.json new file mode 100644 index 00000000..156adfdb --- /dev/null +++ b/rust/.sqlx/query-ac89da5b76bdd44d6c3c8901b2d99ff9352ab497a2abb687a1b8ef3cb09701ab.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT EXISTS(\n SELECT 1\n FROM group_members\n WHERE group_id = ? AND contact_id = ?\n )\n ", + "describe": { + "columns": [ + { + "name": "EXISTS(\n SELECT 1\n FROM group_members\n WHERE group_id = ? AND contact_id = ?\n )", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false + ] + }, + "hash": "ac89da5b76bdd44d6c3c8901b2d99ff9352ab497a2abb687a1b8ef3cb09701ab" +} diff --git a/rust/.sqlx/query-ad8ff2a03d99759d40d59f0a4371d57b779c3fd5c8daf1094fd66a147aea973d.json b/rust/.sqlx/query-ad8ff2a03d99759d40d59f0a4371d57b779c3fd5c8daf1094fd66a147aea973d.json new file mode 100644 index 00000000..0efd2de8 --- /dev/null +++ b/rust/.sqlx/query-ad8ff2a03d99759d40d59f0a4371d57b779c3fd5c8daf1094fd66a147aea973d.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n DELETE FROM reactions\n WHERE message_id = ? AND sender_id = ? AND emoji = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "ad8ff2a03d99759d40d59f0a4371d57b779c3fd5c8daf1094fd66a147aea973d" +} diff --git a/rust/.sqlx/query-ae3320f8a4626eb44579f1319e448e905059918bd150b947d6341703ea9ee701.json b/rust/.sqlx/query-ae3320f8a4626eb44579f1319e448e905059918bd150b947d6341703ea9ee701.json new file mode 100644 index 00000000..df6ad4e7 --- /dev/null +++ b/rust/.sqlx/query-ae3320f8a4626eb44579f1319e448e905059918bd150b947d6341703ea9ee701.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT COALESCE(MAX(pre_key_id), 0) AS \"id!: u32\" FROM signal_pre_keys", + "describe": { + "columns": [ + { + "name": "id!: u32", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "ae3320f8a4626eb44579f1319e448e905059918bd150b947d6341703ea9ee701" +} diff --git a/rust/.sqlx/query-b109c2c02d61f15cd9b1a06b7eafce94fa1490b234db5db72a3775894eb5b771.json b/rust/.sqlx/query-b109c2c02d61f15cd9b1a06b7eafce94fa1490b234db5db72a3775894eb5b771.json new file mode 100644 index 00000000..032adbed --- /dev/null +++ b/rust/.sqlx/query-b109c2c02d61f15cd9b1a06b7eafce94fa1490b234db5db72a3775894eb5b771.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET username = 'changed contact'\n WHERE user_id = 1\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "b109c2c02d61f15cd9b1a06b7eafce94fa1490b234db5db72a3775894eb5b771" +} diff --git a/rust/.sqlx/query-b199f58b951b90331edbb5cbb378e016558d42c4b2ac93ce45a8eed0a16b9871.json b/rust/.sqlx/query-b199f58b951b90331edbb5cbb378e016558d42c4b2ac93ce45a8eed0a16b9871.json new file mode 100644 index 00000000..1ea3a84e --- /dev/null +++ b/rust/.sqlx/query-b199f58b951b90331edbb5cbb378e016558d42c4b2ac93ce45a8eed0a16b9871.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_signed_pre_keys(signed_pre_key_id, record_bytes)\n VALUES (?, ?)\n ON CONFLICT(signed_pre_key_id)\n DO UPDATE SET record_bytes = excluded.record_bytes\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "b199f58b951b90331edbb5cbb378e016558d42c4b2ac93ce45a8eed0a16b9871" +} diff --git a/rust/.sqlx/query-b3242f4964184d187d818d2e112aa826377c5ce5e09749f780c3a6a9870157d9.json b/rust/.sqlx/query-b3242f4964184d187d818d2e112aa826377c5ce5e09749f780c3a6a9870157d9.json new file mode 100644 index 00000000..7b8012e9 --- /dev/null +++ b/rust/.sqlx/query-b3242f4964184d187d818d2e112aa826377c5ce5e09749f780c3a6a9870157d9.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO contacts (user_id, username)\n VALUES (1, 'one'), (2, 'two')\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "b3242f4964184d187d818d2e112aa826377c5ce5e09749f780c3a6a9870157d9" +} diff --git a/rust/.sqlx/query-b375c5808bf4526a65d96c6262cf03bdb10c4c2dd00a4a57ab49c27ff7fa8d05.json b/rust/.sqlx/query-b375c5808bf4526a65d96c6262cf03bdb10c4c2dd00a4a57ab49c27ff7fa8d05.json new file mode 100644 index 00000000..97f5add9 --- /dev/null +++ b/rust/.sqlx/query-b375c5808bf4526a65d96c6262cf03bdb10c4c2dd00a4a57ab49c27ff7fa8d05.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO contact_push_keys(contact_id, key_id, key, created_at)\n VALUES (?, ?, ?, ?)\n ON CONFLICT(contact_id, key_id)\n DO UPDATE SET key = excluded.key, created_at = excluded.created_at\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "b375c5808bf4526a65d96c6262cf03bdb10c4c2dd00a4a57ab49c27ff7fa8d05" +} diff --git a/rust/.sqlx/query-b3da8b1a7a6985e1df47fe590ae60255f76c7bacdd0adce57faba9fa46bce96b.json b/rust/.sqlx/query-b3da8b1a7a6985e1df47fe590ae60255f76c7bacdd0adce57faba9fa46bce96b.json new file mode 100644 index 00000000..746baf57 --- /dev/null +++ b/rust/.sqlx/query-b3da8b1a7a6985e1df47fe590ae60255f76c7bacdd0adce57faba9fa46bce96b.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_sessions(name, device_id, record_bytes)\n VALUES (?, ?, ?)\n ON CONFLICT(name, device_id)\n DO UPDATE SET record_bytes = excluded.record_bytes\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "b3da8b1a7a6985e1df47fe590ae60255f76c7bacdd0adce57faba9fa46bce96b" +} diff --git a/rust/.sqlx/query-b400f362374072f8974fae82ed2a41a7f3445ea2c041dd058312564ac21af3ae.json b/rust/.sqlx/query-b400f362374072f8974fae82ed2a41a7f3445ea2c041dd058312564ac21af3ae.json new file mode 100644 index 00000000..0c44dc74 --- /dev/null +++ b/rust/.sqlx/query-b400f362374072f8974fae82ed2a41a7f3445ea2c041dd058312564ac21af3ae.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE media_files\n SET download_state = 'pending',\n download_token = ?,\n encryption_key = ?,\n encryption_mac = ?,\n encryption_nonce = ?\n WHERE media_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 5 + }, + "nullable": [] + }, + "hash": "b400f362374072f8974fae82ed2a41a7f3445ea2c041dd058312564ac21af3ae" +} diff --git a/rust/.sqlx/query-c0b385573c3b8b67aeabad4027eb0fe4a4b9a8630f32dd70d6397d5d0fa8d199.json b/rust/.sqlx/query-c0b385573c3b8b67aeabad4027eb0fe4a4b9a8630f32dd70d6397d5d0fa8d199.json new file mode 100644 index 00000000..b3c5ee47 --- /dev/null +++ b/rust/.sqlx/query-c0b385573c3b8b67aeabad4027eb0fe4a4b9a8630f32dd70d6397d5d0fa8d199.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT record_bytes FROM signal_signed_pre_keys WHERE signed_pre_key_id = ?", + "describe": { + "columns": [ + { + "name": "record_bytes", + "ordinal": 0, + "type_info": "Blob", + "origin": { + "Table": { + "table": "signal_signed_pre_keys", + "name": "record_bytes" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "c0b385573c3b8b67aeabad4027eb0fe4a4b9a8630f32dd70d6397d5d0fa8d199" +} diff --git a/rust/.sqlx/query-c1993cbf0d0255b2e68d0bf4ffc80d37fdec7b2d995b2553e534e9dc6e6db195.json b/rust/.sqlx/query-c1993cbf0d0255b2e68d0bf4ffc80d37fdec7b2d995b2553e534e9dc6e6db195.json new file mode 100644 index 00000000..2b400771 --- /dev/null +++ b/rust/.sqlx/query-c1993cbf0d0255b2e68d0bf4ffc80d37fdec7b2d995b2553e534e9dc6e6db195.json @@ -0,0 +1,47 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT\n (SELECT COUNT(*) FROM messages WHERE message_id LIKE 'path-media-%') AS media_count,\n (SELECT COUNT(*) FROM group_histories WHERE group_id = ?) AS history_count,\n (SELECT COUNT(*) FROM contact_push_keys WHERE contact_id = ?) AS push_key_count,\n (SELECT ask_for_friend_promotions FROM contacts WHERE user_id = ?) AS ask_promotions\n ", + "describe": { + "columns": [ + { + "name": "media_count", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + }, + { + "name": "history_count", + "ordinal": 1, + "type_info": "Integer", + "origin": "Expression" + }, + { + "name": "push_key_count", + "ordinal": 2, + "type_info": "Integer", + "origin": "Expression" + }, + { + "name": "ask_promotions", + "ordinal": 3, + "type_info": "Integer", + "origin": { + "Table": { + "table": "contacts", + "name": "ask_for_friend_promotions" + } + } + } + ], + "parameters": { + "Right": 3 + }, + "nullable": [ + false, + false, + false, + true + ] + }, + "hash": "c1993cbf0d0255b2e68d0bf4ffc80d37fdec7b2d995b2553e534e9dc6e6db195" +} diff --git a/rust/.sqlx/query-c57017d2c9c2b4b2282f89010ddd4250b566cda50870d09ecedc7e8eb81c5c26.json b/rust/.sqlx/query-c57017d2c9c2b4b2282f89010ddd4250b566cda50870d09ecedc7e8eb81c5c26.json new file mode 100644 index 00000000..77edd100 --- /dev/null +++ b/rust/.sqlx/query-c57017d2c9c2b4b2282f89010ddd4250b566cda50870d09ecedc7e8eb81c5c26.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT SUM(retry_count)\n FROM receipts\n WHERE receipt_id LIKE 'path-plaintext-%'\n ", + "describe": { + "columns": [ + { + "name": "SUM(retry_count)", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + true + ] + }, + "hash": "c57017d2c9c2b4b2282f89010ddd4250b566cda50870d09ecedc7e8eb81c5c26" +} diff --git a/rust/.sqlx/query-c5cbd0af68a108e02fb8c269760c3f323a44b7d69bee0e5f85e82a731011893f.json b/rust/.sqlx/query-c5cbd0af68a108e02fb8c269760c3f323a44b7d69bee0e5f85e82a731011893f.json new file mode 100644 index 00000000..55cdf557 --- /dev/null +++ b/rust/.sqlx/query-c5cbd0af68a108e02fb8c269760c3f323a44b7d69bee0e5f85e82a731011893f.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO contacts(user_id, username)\n VALUES(1, 'original contact')\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "c5cbd0af68a108e02fb8c269760c3f323a44b7d69bee0e5f85e82a731011893f" +} diff --git a/rust/.sqlx/query-c915911be387a4e7b49e184e55fdad0a83e9f7164736bdd1669650e52f879ed5.json b/rust/.sqlx/query-c915911be387a4e7b49e184e55fdad0a83e9f7164736bdd1669650e52f879ed5.json new file mode 100644 index 00000000..7b707d39 --- /dev/null +++ b/rust/.sqlx/query-c915911be387a4e7b49e184e55fdad0a83e9f7164736bdd1669650e52f879ed5.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO reactions(message_id, emoji, sender_id)\n VALUES (?, ?, ?)\n ON CONFLICT(message_id, sender_id, emoji) DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "c915911be387a4e7b49e184e55fdad0a83e9f7164736bdd1669650e52f879ed5" +} diff --git a/rust/.sqlx/query-ca2e9b5cabae7722b0ad8fe3d5d807b1a28de31a62f9d8f78c2e447f79a14f98.json b/rust/.sqlx/query-ca2e9b5cabae7722b0ad8fe3d5d807b1a28de31a62f9d8f78c2e447f79a14f98.json new file mode 100644 index 00000000..96b3e216 --- /dev/null +++ b/rust/.sqlx/query-ca2e9b5cabae7722b0ad8fe3d5d807b1a28de31a62f9d8f78c2e447f79a14f98.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO group_histories(\n group_history_id,\n group_id,\n contact_id,\n affected_contact_id,\n new_group_name,\n new_delete_messages_after_milliseconds,\n type\n ) VALUES (?, ?, ?, ?, ?, ?, ?)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 7 + }, + "nullable": [] + }, + "hash": "ca2e9b5cabae7722b0ad8fe3d5d807b1a28de31a62f9d8f78c2e447f79a14f98" +} diff --git a/rust/.sqlx/query-cd9f690447bdbe9dc52534194400781076ed1d3ca59dd52030c892dc4dc15224.json b/rust/.sqlx/query-cd9f690447bdbe9dc52534194400781076ed1d3ca59dd52030c892dc4dc15224.json new file mode 100644 index 00000000..4b3f0fe0 --- /dev/null +++ b/rust/.sqlx/query-cd9f690447bdbe9dc52534194400781076ed1d3ca59dd52030c892dc4dc15224.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE contacts\n SET requested = 0, accepted = 0, deleted_by_user = 1\n WHERE user_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "cd9f690447bdbe9dc52534194400781076ed1d3ca59dd52030c892dc4dc15224" +} diff --git a/rust/.sqlx/query-d1b24b806f4071108ba198504845cfeeeddc63628ed9ace6db3107ab2a4262ec.json b/rust/.sqlx/query-d1b24b806f4071108ba198504845cfeeeddc63628ed9ace6db3107ab2a4262ec.json new file mode 100644 index 00000000..2d8555db --- /dev/null +++ b/rust/.sqlx/query-d1b24b806f4071108ba198504845cfeeeddc63628ed9ace6db3107ab2a4262ec.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE messages\n SET content = ?, modified_at = ?\n WHERE message_id = ? AND sender_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "d1b24b806f4071108ba198504845cfeeeddc63628ed9ace6db3107ab2a4262ec" +} diff --git a/rust/.sqlx/query-d50a6ee6ff69408981408585348040d91c2e430c936b91bd02a3f7f562edf658.json b/rust/.sqlx/query-d50a6ee6ff69408981408585348040d91c2e430c936b91bd02a3f7f562edf658.json new file mode 100644 index 00000000..f05ca172 --- /dev/null +++ b/rust/.sqlx/query-d50a6ee6ff69408981408585348040d91c2e430c936b91bd02a3f7f562edf658.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO media_files(\n media_id,\n type,\n download_state,\n requires_authentication,\n display_limit_in_milliseconds,\n download_token,\n encryption_key,\n encryption_mac,\n encryption_nonce,\n created_at\n ) VALUES (?, ?, 'pending', ?, ?, ?, ?, ?, ?, ?)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 9 + }, + "nullable": [] + }, + "hash": "d50a6ee6ff69408981408585348040d91c2e430c936b91bd02a3f7f562edf658" +} diff --git a/rust/.sqlx/query-d8a8544c1d3eafe0b9c6f0ebd1b7102e792e3d27722294bdc626acde1b46ba2e.json b/rust/.sqlx/query-d8a8544c1d3eafe0b9c6f0ebd1b7102e792e3d27722294bdc626acde1b46ba2e.json new file mode 100644 index 00000000..3c1e66fd --- /dev/null +++ b/rust/.sqlx/query-d8a8544c1d3eafe0b9c6f0ebd1b7102e792e3d27722294bdc626acde1b46ba2e.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO contacts(user_id, username, accepted, signal_version)\n VALUES (?, ?, 1, 'v2')\n ON CONFLICT(user_id) DO UPDATE SET\n username = excluded.username,\n accepted = 1,\n signal_version = 'v2'\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "d8a8544c1d3eafe0b9c6f0ebd1b7102e792e3d27722294bdc626acde1b46ba2e" +} diff --git a/rust/.sqlx/query-da86a64379fbe00301ccdd82c721b99f3c78465544975d85e9e753337b2fce3e.json b/rust/.sqlx/query-da86a64379fbe00301ccdd82c721b99f3c78465544975d85e9e753337b2fce3e.json new file mode 100644 index 00000000..692e7321 --- /dev/null +++ b/rust/.sqlx/query-da86a64379fbe00301ccdd82c721b99f3c78465544975d85e9e753337b2fce3e.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_identities (name, identity_key, timestamp)\n VALUES ('1', ?, 0)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "da86a64379fbe00301ccdd82c721b99f3c78465544975d85e9e753337b2fce3e" +} diff --git a/rust/.sqlx/query-da9609bbcd1676a863f4612f19a24cfdf518d18dca57ba1693b0d51345a24ce1.json b/rust/.sqlx/query-da9609bbcd1676a863f4612f19a24cfdf518d18dca57ba1693b0d51345a24ce1.json new file mode 100644 index 00000000..09d4437e --- /dev/null +++ b/rust/.sqlx/query-da9609bbcd1676a863f4612f19a24cfdf518d18dca57ba1693b0d51345a24ce1.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO receipts(receipt_id, contact_id, message_id, message)\n VALUES (?, ?, 'path-text-message', x'01')\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "da9609bbcd1676a863f4612f19a24cfdf518d18dca57ba1693b0d51345a24ce1" +} diff --git a/rust/.sqlx/query-db998a6faae4968d03efb6496202c07be4b7c353d12372842b6cd5eb80fb7fd3.json b/rust/.sqlx/query-db998a6faae4968d03efb6496202c07be4b7c353d12372842b6cd5eb80fb7fd3.json new file mode 100644 index 00000000..b0a55748 --- /dev/null +++ b/rust/.sqlx/query-db998a6faae4968d03efb6496202c07be4b7c353d12372842b6cd5eb80fb7fd3.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO contacts(user_id, username, accepted)\n VALUES(7, 'alice', 1)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "db998a6faae4968d03efb6496202c07be4b7c353d12372842b6cd5eb80fb7fd3" +} diff --git a/rust/.sqlx/query-e2a5bdab035e10052f1091991d249407492b87fc3fe4fd56dac858a77a8f6845.json b/rust/.sqlx/query-e2a5bdab035e10052f1091991d249407492b87fc3fe4fd56dac858a77a8f6845.json new file mode 100644 index 00000000..18a03b56 --- /dev/null +++ b/rust/.sqlx/query-e2a5bdab035e10052f1091991d249407492b87fc3fe4fd56dac858a77a8f6845.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO groups(group_id, group_name)\n VALUES('group', 'Group')\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "e2a5bdab035e10052f1091991d249407492b87fc3fe4fd56dac858a77a8f6845" +} diff --git a/rust/.sqlx/query-e52ee5a2c980f153a9b3b901ab74e5646485b1a600c159dc7ac13370d8bba9d9.json b/rust/.sqlx/query-e52ee5a2c980f153a9b3b901ab74e5646485b1a600c159dc7ac13370d8bba9d9.json new file mode 100644 index 00000000..83b338bf --- /dev/null +++ b/rust/.sqlx/query-e52ee5a2c980f153a9b3b901ab74e5646485b1a600c159dc7ac13370d8bba9d9.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM main.sqlite_sequence WHERE name = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "e52ee5a2c980f153a9b3b901ab74e5646485b1a600c159dc7ac13370d8bba9d9" +} diff --git a/rust/.sqlx/query-e5cad0116aeef8212079f07546f156eabc8050171ad47073f389df070f767815.json b/rust/.sqlx/query-e5cad0116aeef8212079f07546f156eabc8050171ad47073f389df070f767815.json new file mode 100644 index 00000000..e740a756 --- /dev/null +++ b/rust/.sqlx/query-e5cad0116aeef8212079f07546f156eabc8050171ad47073f389df070f767815.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT COALESCE(MAX(kyber_pre_key_id), 0) AS \"id!: u32\" FROM signal_kyber_pre_keys", + "describe": { + "columns": [ + { + "name": "id!: u32", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "e5cad0116aeef8212079f07546f156eabc8050171ad47073f389df070f767815" +} diff --git a/rust/.sqlx/query-e71ab6c0bf78b31953f11acc15663b9dd81cf5cf1545c7545c8a7ab4070d44a5.json b/rust/.sqlx/query-e71ab6c0bf78b31953f11acc15663b9dd81cf5cf1545c7545c8a7ab4070d44a5.json new file mode 100644 index 00000000..73088a53 --- /dev/null +++ b/rust/.sqlx/query-e71ab6c0bf78b31953f11acc15663b9dd81cf5cf1545c7545c8a7ab4070d44a5.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT COUNT(*) FROM pragma_foreign_key_check", + "describe": { + "columns": [ + { + "name": "COUNT(*)", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "e71ab6c0bf78b31953f11acc15663b9dd81cf5cf1545c7545c8a7ab4070d44a5" +} diff --git a/rust/.sqlx/query-e9233f4bcd226a8536865b908ee9e36b6e8ebfd52fb3828c948693e9ca5c7dbb.json b/rust/.sqlx/query-e9233f4bcd226a8536865b908ee9e36b6e8ebfd52fb3828c948693e9ca5c7dbb.json new file mode 100644 index 00000000..db750f73 --- /dev/null +++ b/rust/.sqlx/query-e9233f4bcd226a8536865b908ee9e36b6e8ebfd52fb3828c948693e9ca5c7dbb.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE groups\n SET delete_messages_after_milliseconds = COALESCE(\n ?, delete_messages_after_milliseconds\n )\n WHERE group_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "e9233f4bcd226a8536865b908ee9e36b6e8ebfd52fb3828c948693e9ca5c7dbb" +} diff --git a/rust/.sqlx/query-eefe20114e1b151c48f308f2f197c9a81dfe0f348725c57f7e01205d68b687a9.json b/rust/.sqlx/query-eefe20114e1b151c48f308f2f197c9a81dfe0f348725c57f7e01205d68b687a9.json new file mode 100644 index 00000000..15bb1144 --- /dev/null +++ b/rust/.sqlx/query-eefe20114e1b151c48f308f2f197c9a81dfe0f348725c57f7e01205d68b687a9.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE receipts\n SET mark_for_retry = CAST(strftime('%s', 'now') AS INTEGER),\n retry_count = retry_count + 1\n WHERE receipt_id = ? AND contact_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "eefe20114e1b151c48f308f2f197c9a81dfe0f348725c57f7e01205d68b687a9" +} diff --git a/rust/.sqlx/query-ef4732ab68de219079b3f2a80bebbf5587148d472b5ae7d1b4db79fe4a2c0489.json b/rust/.sqlx/query-ef4732ab68de219079b3f2a80bebbf5587148d472b5ae7d1b4db79fe4a2c0489.json new file mode 100644 index 00000000..c8e69303 --- /dev/null +++ b/rust/.sqlx/query-ef4732ab68de219079b3f2a80bebbf5587148d472b5ae7d1b4db79fe4a2c0489.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE groups\n SET group_name = ?\n WHERE group_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "ef4732ab68de219079b3f2a80bebbf5587148d472b5ae7d1b4db79fe4a2c0489" +} diff --git a/rust/.sqlx/query-f14a47bb8c173ad393d4822151e129471217ba0bff76ae265b0e0d5b6f7c73fe.json b/rust/.sqlx/query-f14a47bb8c173ad393d4822151e129471217ba0bff76ae265b0e0d5b6f7c73fe.json new file mode 100644 index 00000000..d116f537 --- /dev/null +++ b/rust/.sqlx/query-f14a47bb8c173ad393d4822151e129471217ba0bff76ae265b0e0d5b6f7c73fe.json @@ -0,0 +1,38 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT user_id, username\n FROM contacts\n ORDER BY user_id\n ", + "describe": { + "columns": [ + { + "name": "user_id", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "contacts", + "name": "user_id" + } + } + }, + { + "name": "username", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "contacts", + "name": "username" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false + ] + }, + "hash": "f14a47bb8c173ad393d4822151e129471217ba0bff76ae265b0e0d5b6f7c73fe" +} diff --git a/rust/.sqlx/query-f435816601215c495454f2641a01972b70885339387e1017cd2013e5a35c60b0.json b/rust/.sqlx/query-f435816601215c495454f2641a01972b70885339387e1017cd2013e5a35c60b0.json new file mode 100644 index 00000000..b1abe9e2 --- /dev/null +++ b/rust/.sqlx/query-f435816601215c495454f2641a01972b70885339387e1017cd2013e5a35c60b0.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n DELETE FROM receipts\n WHERE receipt_id = ? AND contact_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "f435816601215c495454f2641a01972b70885339387e1017cd2013e5a35c60b0" +} diff --git a/rust/.sqlx/query-f50e328e40de6f4f886a524118b6ef21dacefd2a248a2a8256cd100451d9889a.json b/rust/.sqlx/query-f50e328e40de6f4f886a524118b6ef21dacefd2a248a2a8256cd100451d9889a.json new file mode 100644 index 00000000..0bc8b05e --- /dev/null +++ b/rust/.sqlx/query-f50e328e40de6f4f886a524118b6ef21dacefd2a248a2a8256cd100451d9889a.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO group_members(group_id, contact_id, member_state)\n VALUES (?, ?, 'member')\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "f50e328e40de6f4f886a524118b6ef21dacefd2a248a2a8256cd100451d9889a" +} diff --git a/rust/.sqlx/query-f579d5670f62ff8523d668a7789fca0fabae27202f09976f1c0159cc7c3b4329.json b/rust/.sqlx/query-f579d5670f62ff8523d668a7789fca0fabae27202f09976f1c0159cc7c3b4329.json new file mode 100644 index 00000000..ea0ada5d --- /dev/null +++ b/rust/.sqlx/query-f579d5670f62ff8523d668a7789fca0fabae27202f09976f1c0159cc7c3b4329.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO app_metadata(key, value)\n VALUES('legacy_import_complete', '1')\n ON CONFLICT(key) DO UPDATE SET value = '1'\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "f579d5670f62ff8523d668a7789fca0fabae27202f09976f1c0159cc7c3b4329" +} diff --git a/rust/.sqlx/query-f93ff998e086686be070b8d377dfd051f2adb7f7225fa97eae53172b0f5b19fa.json b/rust/.sqlx/query-f93ff998e086686be070b8d377dfd051f2adb7f7225fa97eae53172b0f5b19fa.json new file mode 100644 index 00000000..9a0aa1b1 --- /dev/null +++ b/rust/.sqlx/query-f93ff998e086686be070b8d377dfd051f2adb7f7225fa97eae53172b0f5b19fa.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO message_actions(message_id, contact_id, type)\n VALUES (?, ?, 'ackByUserAt')\n ON CONFLICT(message_id, contact_id, type)\n DO UPDATE SET action_at = CAST(strftime('%s', 'now') AS INTEGER)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "f93ff998e086686be070b8d377dfd051f2adb7f7225fa97eae53172b0f5b19fa" +} diff --git a/rust/.sqlx/query-fdc73a53a58eea5df4925e2a11ce1befa52f1dc0f709de124d9c6f5ec29c88f0.json b/rust/.sqlx/query-fdc73a53a58eea5df4925e2a11ce1befa52f1dc0f709de124d9c6f5ec29c88f0.json new file mode 100644 index 00000000..3b2afdea --- /dev/null +++ b/rust/.sqlx/query-fdc73a53a58eea5df4925e2a11ce1befa52f1dc0f709de124d9c6f5ec29c88f0.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO received_receipts(receipt_id)\n VALUES (?)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "fdc73a53a58eea5df4925e2a11ce1befa52f1dc0f709de124d9c6f5ec29c88f0" +} diff --git a/rust/.sqlx/query-febc50ab89aa3a9bbb2e9333d8547cb700bc3039c178e133def8a2dc2e8aabb6.json b/rust/.sqlx/query-febc50ab89aa3a9bbb2e9333d8547cb700bc3039c178e133def8a2dc2e8aabb6.json new file mode 100644 index 00000000..a6abc4a9 --- /dev/null +++ b/rust/.sqlx/query-febc50ab89aa3a9bbb2e9333d8547cb700bc3039c178e133def8a2dc2e8aabb6.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE groups\n SET last_message_exchange = MAX(last_message_exchange, ?)\n WHERE group_id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "febc50ab89aa3a9bbb2e9333d8547cb700bc3039c178e133def8a2dc2e8aabb6" +} diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 2f407546..4238e15f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -216,6 +216,12 @@ dependencies = [ "critical-section", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.1" @@ -254,7 +260,7 @@ dependencies = [ "clang-sys", "itertools 0.13.0", "log", - "prettyplease", + "prettyplease 0.2.37", "proc-macro2", "quote", "regex", @@ -311,6 +317,29 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "bon" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3fac94a66da67200398458a25412bcc3f9b6443b5119a6cad9cf3ccfcd8cc6" +dependencies = [ + "bon-macros", +] + +[[package]] +name = "bon-macros" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4654961ad0494e4774c5c60b4cb4cd0ae9b9d92d039d901638b1dba97ebebf5" +dependencies = [ + "darling", + "ident_case", + "prettyplease 0.3.0", + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "build-target" version = "0.4.0" @@ -381,6 +410,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chacha20" version = "0.9.1" @@ -722,6 +757,40 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.3", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core", + "quote", + "syn 3.0.3", +] + [[package]] name = "dart-sys" version = "4.1.5" @@ -870,6 +939,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.17.0" @@ -950,6 +1025,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "eyre" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3" +dependencies = [ + "autocfg", + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.5.0" @@ -1049,6 +1135,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1174,8 +1275,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1197,9 +1300,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -1414,6 +1519,45 @@ dependencies = [ "zeroize", ] +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + [[package]] name = "humantime" version = "2.4.0" @@ -1430,6 +1574,65 @@ dependencies = [ "zeroize", ] +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -1536,6 +1739,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1557,6 +1766,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + [[package]] name = "indexmap" version = "2.14.0" @@ -1586,6 +1801,12 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + [[package]] name = "is-terminal" version = "0.4.17" @@ -1928,6 +2149,12 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "matchers" version = "0.2.0" @@ -1996,6 +2223,23 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk-context" version = "0.1.1" @@ -2108,6 +2352,37 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-src" version = "300.6.1+3.6.3" @@ -2319,6 +2594,16 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn 3.0.3", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -2380,7 +2665,7 @@ dependencies = [ "log", "multimap", "petgraph", - "prettyplease", + "prettyplease 0.2.37", "prost", "prost-types", "regex", @@ -2410,6 +2695,62 @@ dependencies = [ "prost", ] +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.19", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.19", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.47" @@ -2507,6 +2848,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rayon" version = "1.12.0" @@ -2565,19 +2915,75 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rust_lib_twonly" version = "0.1.0" dependencies = [ "aes-gcm", "android-native-keyring-store", + "anyhow", "apple-native-keyring-store", "async-trait", "base64", "blahaj", + "bon", "chacha20poly1305", "chrono", + "flate2", "flutter_rust_bridge", + "futures-util", "hex", "hkdf 0.12.4", "hmac 0.13.0", @@ -2591,17 +2997,23 @@ dependencies = [ "prost-build", "rand 0.8.7", "rand 0.9.5", + "reqwest", + "rustls", "scrypt", "serde", "serde_json", "sha2 0.10.9", "sqlx", + "stream-tungstenite", "tempfile", "thiserror 2.0.19", "tokio", + "tokio-tungstenite", + "tokio-util", "tracing", "tracing-appender", "tracing-subscriber", + "uuid", "walkdir", "zeroize", "zip", @@ -2641,12 +3053,65 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "salsa20" version = "0.11.0" @@ -2666,6 +3131,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2756,6 +3230,29 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sha1" version = "0.11.0" @@ -2829,7 +3326,7 @@ dependencies = [ "libsignal-debug", "rand_chacha 0.9.0", "rand_core 0.9.5", - "sha1", + "sha1 0.11.0", "sha2 0.11.0", "subtle", "thiserror 2.0.19", @@ -3022,7 +3519,7 @@ dependencies = [ "log", "percent-encoding", "serde", - "sha1", + "sha1 0.11.0", "sha2 0.11.0", "sqlx-core", "thiserror 2.0.19", @@ -3096,6 +3593,29 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "stream-tungstenite" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "073b38894a4100c69c2b7bd8ac5acd379d1eddb927bd2b6610372d0ef26e5b79" +dependencies = [ + "async-trait", + "dyn-clone", + "eyre", + "futures", + "futures-util", + "native-tls", + "rand 0.9.5", + "thiserror 2.0.19", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tracing", + "tracing-subscriber", + "tungstenite", +] + [[package]] name = "stringprep" version = "0.1.5" @@ -3107,6 +3627,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -3141,6 +3667,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -3315,6 +3850,26 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.19" @@ -3324,6 +3879,38 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "native-tls", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] @@ -3356,6 +3943,51 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -3431,6 +4063,32 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.9.5", + "rustls", + "rustls-pki-types", + "sha1 0.10.7", + "thiserror 2.0.19", + "utf-8", +] + [[package]] name = "typenum" version = "1.20.1" @@ -3484,6 +4142,12 @@ dependencies = [ "ctutils", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -3496,6 +4160,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -3541,6 +4211,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3621,6 +4300,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "2.1.2" @@ -3701,7 +4399,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -3719,13 +4426,29 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -3734,42 +4457,90 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "1.0.4" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a71b9565..69be76db 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -47,7 +47,19 @@ walkdir = "2.5.0" libsignal-protocol = { git = "https://github.com/signalapp/libsignal", version = "0.1.0", rev = "44a6dd8fc9f9d6903b32842b80b3894a75678418" } rand08 = { version = "0.8.5", package = "rand" } rand = "0.9.4" +uuid = { version = "1", features = ["v4"] } +flate2 = "1" +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } async-trait = "0.1.91" +futures-util = "0.3.31" +tokio-tungstenite = { version = "0.28.0", default-features = false, features = [ + "connect", + "rustls-tls-native-roots", +] } +rustls = { version = "0.23.43", default-features = false, features = ["ring", "std"] } +tokio-util = "0.7.16" +bon = "3.9.3" +stream-tungstenite = "0.6.1" [target.'cfg(target_os = "ios")'.dependencies] # iOS backend: Requires the 'protected' feature for Data Protection Keychain apple-native-keyring-store = { version = "1", features = ["protected"] } @@ -56,6 +68,7 @@ apple-native-keyring-store = { version = "1", features = ["protected"] } android-native-keyring-store = "1" [dev-dependencies] +anyhow = "1.0.104" pretty_env_logger = "0.5.0" tempfile = "3.27.0" diff --git a/rust/build.rs b/rust/build.rs index 7c7bd7e5..3bd84a09 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -1,11 +1,45 @@ use std::io::Result; fn main() -> Result<()> { + println!("cargo:rerun-if-changed=../pubspec.yaml"); + if let Ok(pubspec) = std::fs::read_to_string("../pubspec.yaml") { + if let Some(version) = pubspec + .lines() + .find_map(|line| line.strip_prefix("version: ")) + { + println!( + "cargo:rustc-env=TWONLY_APP_VERSION={}", + version.split('+').next().unwrap_or(version) + ); + } + } + let websocket_proto_root = "src/api/proto"; + let websocket_protos = [ + "src/api/proto/api/websocket/client_to_server.proto", + "src/api/proto/api/websocket/server_to_client.proto", + "src/api/proto/api/websocket/error.proto", + ]; + for proto in websocket_protos { + println!("cargo:rerun-if-changed={proto}"); + } + prost_build::Config::new() + .include_file("websocket_protocol.rs") + .compile_protos(&websocket_protos, &[websocket_proto_root])?; + prost_build::compile_protos(&["src/user_discovery/types.proto"], &["src/"])?; prost_build::Config::new() .include_file("client_messages.rs") .compile_protos( - &["../lib/src/model/protobuf/client/messages.proto"], + &[ + "../lib/src/model/protobuf/client/messages.proto", + "../lib/src/model/protobuf/client/data.proto", + "../lib/src/model/protobuf/client/push_notification.proto", + "../lib/src/model/protobuf/client/groups.proto", + ], &["../lib/src/model/protobuf/client/"], )?; + prost_build::compile_protos( + &["src/api/proto/api/http/http_requests.proto"], + &["src/api/proto/"], + )?; Ok(()) } diff --git a/rust/scripts/prepare_sqlx.sh b/rust/scripts/prepare_sqlx.sh index 82c39f91..f6f10295 100755 --- a/rust/scripts/prepare_sqlx.sh +++ b/rust/scripts/prepare_sqlx.sh @@ -16,6 +16,6 @@ for migration in "$rust_dir"/src/database/signal/migrations/*.sql; do done cd "$rust_dir" -cargo sqlx prepare -- --lib +cargo sqlx prepare -- --all-targets echo "Prepared SQLx schema database and offline query metadata." diff --git a/rust/src/api/groups.rs b/rust/src/api/groups.rs new file mode 100644 index 00000000..7f01ae69 --- /dev/null +++ b/rust/src/api/groups.rs @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::EncryptedGroupState; +use crate::api::proto::http_requests::update_group_state; +use crate::api::proto::http_requests::{ + AppendGroupState, GroupState, NewGroupState, UpdateGroupState, +}; +use crate::bridge::api::RustApi; +use crate::error::{Result, TwonlyError}; +use crate::services::groups::crypto; +use crate::services::groups::model::GroupRecord; +use prost::Message; +use rand::SeedableRng; + +pub struct GroupApi; + +impl GroupApi { + pub(crate) async fn fetch_group_state(group_id: &str) -> Result> { + let response = client()? + .get(url(&format!("state/{group_id}"))) + .send() + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?; + if response.status() == reqwest::StatusCode::NOT_FOUND { + return Ok(None); + } + + if !response.status().is_success() { + return Err(TwonlyError::Generic(format!( + "group state returned {}", + response.status() + ))); + } + + Ok(Some( + GroupState::decode( + response + .bytes() + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?, + ) + .map_err(|e| TwonlyError::Generic(e.to_string()))?, + )) + } + + pub(crate) async fn update_remote( + group: &GroupRecord, + version: u64, + state: &EncryptedGroupState, + add_admin: Option>, + remove_admin: Option>, + ) -> Result<()> { + let identity = group.identity()?; + let public_key = identity.identity_key().serialize().to_vec(); + let update = update_group_state::UpdateTbs { + version_id: version + 1, + encrypted_group_state: crypto::encrypt(group.state_key()?, &state.encode_to_vec())?, + public_key: public_key.clone(), + remove_admin, + add_admin, + nonce: Self::get_challenge(&public_key).await?, + }; + let mut rng = rand::rngs::StdRng::from_os_rng(); + let signature = identity + .private_key() + .calculate_signature(&update.encode_to_vec(), &mut rng) + .map_err(|e| TwonlyError::Signal(e.to_string()))? + .into_vec(); + update_state(UpdateGroupState { + update: Some(update), + signature, + }) + .await + } + + pub(crate) async fn load_state(group: &GroupRecord) -> Result<(u64, EncryptedGroupState)> { + let server = GroupApi::fetch_group_state(&group.group_id) + .await? + .ok_or_else(|| TwonlyError::Generic("group state does not exist".into()))?; + let raw = crypto::decrypt(group.state_key()?, &server.encrypted_group_state)?; + Ok(( + server.version_id, + EncryptedGroupState::decode(raw.as_slice()) + .map_err(|error| TwonlyError::Generic(error.to_string()))?, + )) + } + + pub(crate) async fn create(value: NewGroupState) -> Result<()> { + success( + client()? + .post(url("state")) + .body(value.encode_to_vec()) + .send() + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?, + ) + .await + } + pub(crate) async fn append(value: AppendGroupState) -> Result<()> { + success( + client()? + .post(url("state/append")) + .body(value.encode_to_vec()) + .send() + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?, + ) + .await + } + + pub(crate) async fn get_challenge(public_key: &[u8]) -> Result> { + let response = client()? + .get(url(&format!("challenge/{}", hex::encode(public_key)))) + .send() + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?; + if !response.status().is_success() { + return Err(TwonlyError::Generic(format!( + "group challenge returned {}", + response.status() + ))); + } + response + .bytes() + .await + .map(|v| v.to_vec()) + .map_err(|e| TwonlyError::Generic(e.to_string())) + } +} + +fn url(path: &str) -> String { + format!("{}group/{path}", RustApi::api_base_url("https".into())) +} + +fn client() -> Result { + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(10)) + .build() + .map_err(|e| TwonlyError::Generic(e.to_string())) +} + +async fn success(response: reqwest::Response) -> Result<()> { + if response.status().is_success() { + Ok(()) + } else { + Err(TwonlyError::Generic(format!( + "group server returned {}", + response.status() + ))) + } +} + +async fn update_state(value: UpdateGroupState) -> Result<()> { + success( + client()? + .patch(url("state")) + .body(value.encode_to_vec()) + .send() + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?, + ) + .await +} diff --git a/rust/src/api/messages/incoming/client2client/additional_data.rs b/rust/src/api/messages/incoming/client2client/additional_data.rs new file mode 100644 index 00000000..34f589a1 --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/additional_data.rs @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::{self as proto, encrypted_content}; +use crate::context::Context; +use crate::database::app::tables::Group; +use crate::database::app::tables::KeyVerificationType; +use crate::database::app::tables::Message; +use crate::database::app::tables::NewKeyVerification; +use crate::database::app::tables::{MessageType, NewMessage}; +use crate::error::Result; +use crate::utils::milliseconds_to_seconds; +use prost::Message as ProstMessage; +use sqlx::{Sqlite, Transaction}; + +pub(crate) async fn handle_additional_data_message( + ctx: &Context, + tr: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, + message: encrypted_content::AdditionalDataMessage, +) -> Result<()> { + Message::check_message_owner(tr, &message.sender_message_id, from_user_id).await?; + let timestamp = milliseconds_to_seconds(message.timestamp); + + if let Some(data) = message.additional_message_data.as_deref() { + // Additional metadata is optional. Keep accepting the containing + // message if it is malformed or contact verification cannot complete. + if let Err(error) = verify_shared_contacts(ctx, tr, from_user_id, data).await { + tracing::warn!("failed to handle additional message data: {error}"); + } + } + + NewMessage::builder() + .group_id(group_id) + .message_id(&message.sender_message_id) + .message_type(MessageType::Other(&message.r#type)) + .created_at(timestamp) + .sender_id(from_user_id) + .maybe_additional_message_data(message.additional_message_data.as_deref()) + .ack_by_server(chrono::Utc::now().timestamp()) + .build() + .insert(tr) + .await?; + + Group::increase_last_message_exchange(tr, group_id, timestamp).await?; + + Ok(()) +} + +async fn verify_shared_contacts( + ctx: &Context, + tr: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + bytes: &[u8], +) -> Result<()> { + let data = proto::AdditionalMessageData::decode(bytes)?; + + if data.r#type != proto::additional_message_data::Type::Contacts as i32 { + return Ok(()); + } + + let signal_database = ctx.get_rust_db().await; + + for contact in data.contacts { + let stored_identity = sqlx::query_scalar!( + "SELECT identity_key FROM signal_identities WHERE name = ?", + contact.user_id.to_string(), + ) + .fetch_optional(&signal_database.pool) + .await?; + + let Some(stored_identity) = stored_identity else { + tracing::info!("no public key stored for contact"); + continue; + }; + + if stored_identity != contact.public_identity_key { + tracing::error!("shared contact public identity key does not match"); + continue; + } + + NewKeyVerification::new( + contact.user_id, + KeyVerificationType::ContactSharedByVerified, + ) + .verified_by(from_user_id) + .insert(tr) + .await?; + + let verified_at = chrono::Utc::now().timestamp_millis(); + + ctx.get_user_discovery() + .get() + .await + .update_verification_state_for_user(contact.user_id, Some(verified_at)) + .await?; + + tracing::info!("verified a contact from shared additional data"); + } + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/contact.rs b/rust/src/api/messages/incoming/client2client/contact.rs new file mode 100644 index 00000000..e9ef55fd --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/contact.rs @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::messages::queue_encrypted_content; +use crate::api::proto::client::encrypted_content; +use crate::api::proto::client::EncryptedContent; +use crate::api::Server; +use crate::bridge::api::ServerResult; +use crate::bridge::callbacks::get_callbacks; +use crate::context::Context; +use crate::database::app::tables::{ + Contact, Group, GroupHistoryType, InsertGroupHistories, UpdateContact, +}; +use crate::error::{Result, TwonlyError}; +use crate::user_config::UserConfig; +use encrypted_content::contact_request::Type; +use sqlx::{Sqlite, Transaction}; +use std::io::Write as _; +use std::sync::Arc; + +pub(crate) async fn handle_contact_request( + ctx: &Arc, + tr: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + request: encrypted_content::ContactRequest, +) -> Result<()> { + match Type::try_from(request.r#type)? { + Type::Request => { + let contact = Contact::get_contact_by_id(tr, from_user_id).await?; + + // Either the contact has accepted the fromUserId already: Then just blindly accept the request. + // Or the user has also requested fromUserId. This means that both user have requested each other (while been + // offline for example): In this case the contact can also be accepted blindly. + let auto_accept = contact.as_ref().is_some_and(|contact| { + contact.accepted != 0 || (contact.requested == 0 && contact.deleted_by_user == 0) + }); + + if auto_accept { + UpdateContact::builder() + .user_id(from_user_id) + .requested(false) + .accepted(true) + .deleted_by_user(false) + .build() + .update(tr) + .await?; + + if let Some(contact) = contact { + Group::create_direct_chat(ctx, tr, contact).await?; + } + + queue_encrypted_content( + tr, + from_user_id, + EncryptedContent { + contact_request: Some(encrypted_content::ContactRequest { + r#type: Type::Accept as i32, + }), + ..Default::default() + }, + true, + ) + .await?; + } else { + let user = match Server::get_user_by_id(ctx, from_user_id).await? { + ServerResult::Ok(u) => u, + ServerResult::ErrorCode(code) => { + return Err(TwonlyError::Generic(format!( + "Failed to get user by id: {}", + code + ))); + } + }; + let username = user + .username + .ok_or_else(|| TwonlyError::Generic("user response has no username".into()))?; + + UpdateContact::builder() + .user_id(from_user_id) + .username(String::from_utf8(username)?) + .requested(true) + .deleted_by_user(false) + .build() + .insert_on_conflict_update(tr) + .await?; + } + } + Type::Accept => { + let Some(contact) = Contact::get_contact_by_id(tr, from_user_id).await? else { + return Ok(()); + }; + + if contact.requested != 0 || contact.deleted_by_user != 0 { + return Ok(()); + } + + UpdateContact::builder() + .user_id(from_user_id) + .requested(false) + .accepted(true) + .deleted_by_user(false) + .only_if_not_requested(true) + .build() + .update(tr) + .await?; + + Group::create_direct_chat(ctx, tr, contact).await?; + } + Type::Reject => { + UpdateContact::builder() + .user_id(from_user_id) + .requested(false) + .accepted(false) + .deleted_by_user(true) + .build() + .update(tr) + .await?; + } + } + Ok(()) +} + +pub(crate) async fn handle_contact_update( + ctx: &Arc, + tr: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + sender_profile_counter: Option, + update: encrypted_content::ContactUpdate, +) -> Result<()> { + if update.r#type == encrypted_content::contact_update::Type::Request as i32 { + let user = UserConfig::load_required_from(ctx)?; + + let avatar_svg_compressed = user + .avatar_svg + .as_deref() + .map(|avatar| { + let mut encoder = + flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); + encoder.write_all(avatar.as_bytes())?; + encoder.finish() + }) + .transpose()?; + + queue_encrypted_content( + tr, + from_user_id, + EncryptedContent { + contact_update: Some(encrypted_content::ContactUpdate { + r#type: encrypted_content::contact_update::Type::Update as i32, + username: user.username, + display_name: user.display_name, + avatar_svg_compressed, + }), + sender_profile_counter: Some(user.avatar_counter), + ..Default::default() + }, + true, + ) + .await?; + return Ok(()); + } + + if update.r#type != encrypted_content::contact_update::Type::Update as i32 { + return Err(TwonlyError::Generic("invalid contact update".into())); + } + + let previous = sqlx::query!( + "SELECT username, display_name FROM contacts WHERE user_id = ?", + from_user_id, + ) + .fetch_optional(&mut **tr) + .await?; + + if let (Some(previous), Some(username), Some(display_name), Some(_)) = ( + previous, + update.username.as_ref(), + update.display_name.as_ref(), + sender_profile_counter, + ) { + if previous.username != *username { + InsertGroupHistories::new( + from_user_id, + &previous.username, + username, + GroupHistoryType::UpdatedContactUsername, + ) + .insert(tr) + .await?; + } + let old_display = previous + .display_name + .unwrap_or_else(|| previous.username.clone()); + if old_display != *display_name { + InsertGroupHistories::new( + from_user_id, + &old_display, + display_name, + GroupHistoryType::UpdatedContactDisplayName, + ) + .insert(tr) + .await?; + } + } + + UpdateContact::builder() + .user_id(from_user_id) + .maybe_username(update.username) + .display_name(update.display_name) + .avatar_svg_compressed(update.avatar_svg_compressed) + .maybe_sender_profile_counter(sender_profile_counter) + .build() + .update(tr) + .await?; + + if let Ok(callbacks) = get_callbacks() { + tokio::spawn(async move { + (callbacks.api.create_push_avatars)(from_user_id).await; + }); + } + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/errors.rs b/rust/src/api/messages/incoming/client2client/errors.rs new file mode 100644 index 00000000..10099024 --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/errors.rs @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::encrypted_content; +use crate::database::app::tables::UpdateContact; +use crate::error::Result; +use encrypted_content::error_messages::Type; +use sqlx::{Sqlite, Transaction}; + +pub(crate) async fn handle_error_message( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + _receipt_id: &str, + group_id: Option<&str>, + error: encrypted_content::ErrorMessages, +) -> Result<()> { + match Type::try_from(error.r#type)? { + Type::ErrorProcessingMessageCreatedAccountRequestInstead => { + sqlx::query!( + r#" + UPDATE receipts + SET mark_for_retry_after_accepted = CAST(strftime('%s', 'now') AS INTEGER) + WHERE receipt_id = ? AND contact_id = ? + "#, + error.related_receipt_id, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + UpdateContact::builder() + .user_id(from_user_id) + .accepted(false) + .requested(true) + .build() + .update(transaction) + .await?; + } + Type::GroupNotFoundOrNotAMember => { + if let Some(group_id) = group_id { + if let Ok(callbacks) = crate::bridge::callbacks::get_callbacks() { + let group_id = group_id.to_owned(); + let related_receipt_id = error.related_receipt_id.clone(); + tokio::spawn(async move { + (callbacks.api.group_membership_error)( + from_user_id, + group_id, + related_receipt_id, + ) + .await; + }); + } + } + } + Type::SessionOutOfSync | Type::UnknownMessageType => {} + } + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/groups.rs b/rust/src/api/messages/incoming/client2client/groups.rs new file mode 100644 index 00000000..74d32bbf --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/groups.rs @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::encrypted_content; +use crate::error::{Result, TwonlyError}; +use crate::utils::{milliseconds_to_seconds, new_uuid_v4}; +use rand::SeedableRng; +use sqlx::{Sqlite, Transaction}; + +pub(crate) async fn ensure_group_member( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, +) -> Result<()> { + let allowed = sqlx::query_scalar!( + r#" + SELECT EXISTS( + SELECT 1 + FROM group_members + WHERE group_id = ? AND contact_id = ? + ) + "#, + group_id, + from_user_id, + ) + .fetch_one(&mut **transaction) + .await? + != 0; + if !allowed { + return Err(TwonlyError::Generic(format!( + "user {from_user_id} is not a member of group {group_id}" + ))); + } + Ok(()) +} + +pub(crate) async fn handle_group_create( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, + create: encrypted_content::GroupCreate, +) -> Result<()> { + let contact_exists = sqlx::query_scalar!( + r#" + SELECT EXISTS( + SELECT 1 FROM contacts WHERE user_id = ? + ) + "#, + from_user_id, + ) + .fetch_one(&mut **transaction) + .await? + != 0; + if !contact_exists { + return Err(TwonlyError::Generic( + "only known contacts may create a group".into(), + )); + } + + let mut rng = rand::rngs::StdRng::from_os_rng(); + let identity = libsignal_protocol::IdentityKeyPair::generate(&mut rng); + let private_key = identity.serialize().to_vec(); + let group_name = create.group_name.unwrap_or_default(); + sqlx::query!( + r#" + INSERT INTO groups( + group_id, + state_encryption_key, + my_group_private_key, + group_name, + joined_group + ) VALUES (?, ?, ?, ?, 0) + ON CONFLICT(group_id) DO UPDATE SET + state_encryption_key = excluded.state_encryption_key, + my_group_private_key = excluded.my_group_private_key, + left_group = 0, + deleted_content = 0 + "#, + group_id, + create.state_key, + private_key, + group_name, + ) + .execute(&mut **transaction) + .await?; + sqlx::query!( + r#" + INSERT INTO group_members(group_id, contact_id, group_public_key) + VALUES (?, ?, ?) + ON CONFLICT(group_id, contact_id) + DO UPDATE SET group_public_key = excluded.group_public_key + "#, + group_id, + from_user_id, + create.group_public_key, + ) + .execute(&mut **transaction) + .await?; + sqlx::query!( + r#"INSERT INTO group_histories(group_history_id, group_id, contact_id, type) + VALUES (?, ?, ?, 'addMember')"#, + new_uuid_v4(), + group_id, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + if let Ok(callbacks) = crate::bridge::callbacks::get_callbacks() { + let group_id = group_id.to_owned(); + tokio::spawn(async move { + (callbacks.api.group_state_refresh)(group_id, true).await; + }); + } + Ok(()) +} + +pub(crate) async fn handle_group_join( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, + join: encrypted_content::GroupJoin, +) -> Result<()> { + let group_exists = sqlx::query_scalar!( + r#"SELECT EXISTS(SELECT 1 FROM groups WHERE group_id = ?)"#, + group_id, + ) + .fetch_one(&mut **transaction) + .await? + != 0; + let contact_exists = sqlx::query_scalar!( + r#"SELECT EXISTS(SELECT 1 FROM contacts WHERE user_id = ?)"#, + from_user_id, + ) + .fetch_one(&mut **transaction) + .await? + != 0; + if !group_exists || !contact_exists { + return Err(TwonlyError::Generic( + "group join arrived before group/contact state".into(), + )); + } + + sqlx::query!( + r#" + INSERT INTO group_members(group_id, contact_id, group_public_key) + VALUES (?, ?, ?) + ON CONFLICT(group_id, contact_id) + DO UPDATE SET group_public_key = excluded.group_public_key + "#, + group_id, + from_user_id, + join.group_public_key, + ) + .execute(&mut **transaction) + .await?; + Ok(()) +} + +pub(crate) async fn handle_resend_group_public_key( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, +) -> Result<()> { + let private_key = sqlx::query_scalar!( + r#" + SELECT my_group_private_key + FROM groups + WHERE group_id = ? + "#, + group_id, + ) + .fetch_optional(&mut **transaction) + .await? + .flatten(); + let Some(private_key) = private_key else { + return Err(TwonlyError::Generic(format!( + "cannot resend the group public key for {group_id} to {from_user_id}" + ))); + }; + let identity = libsignal_protocol::IdentityKeyPair::try_from(private_key.as_slice()) + .map_err(|error| TwonlyError::Signal(error.to_string()))?; + super::messages::queue_encrypted_content( + transaction, + from_user_id, + crate::api::proto::client::EncryptedContent { + group_id: Some(group_id.to_owned()), + group_join: Some(encrypted_content::GroupJoin { + group_public_key: identity.identity_key().serialize().to_vec(), + }), + ..Default::default() + }, + true, + ) + .await?; + Ok(()) +} + +pub(crate) async fn handle_group_update( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, + update: encrypted_content::GroupUpdate, +) -> Result<()> { + let is_direct = sqlx::query_scalar!( + "SELECT is_direct_chat FROM groups WHERE group_id = ?", + group_id + ) + .fetch_optional(&mut **transaction) + .await? + .unwrap_or(0) + != 0; + if !is_direct { + if let Ok(callbacks) = crate::bridge::callbacks::get_callbacks() { + let group_id = group_id.to_owned(); + tokio::spawn(async move { + (callbacks.api.group_state_refresh)(group_id, false).await; + }); + } + } + if update.group_action_type == "updatedGroupName" { + sqlx::query!( + r#" + UPDATE groups + SET group_name = ? + WHERE group_id = ? + "#, + update.new_group_name, + group_id, + ) + .execute(&mut **transaction) + .await?; + } else if update.group_action_type == "changeDisplayMaxTime" && is_direct { + sqlx::query!( + r#" + UPDATE groups + SET delete_messages_after_milliseconds = COALESCE( + ?, delete_messages_after_milliseconds + ) + WHERE group_id = ? + "#, + update.new_delete_messages_after_milliseconds, + group_id, + ) + .execute(&mut **transaction) + .await?; + } + + sqlx::query!( + r#" + INSERT INTO group_histories( + group_history_id, + group_id, + contact_id, + affected_contact_id, + new_group_name, + new_delete_messages_after_milliseconds, + type + ) VALUES (?, ?, ?, ?, ?, ?, ?) + "#, + new_uuid_v4(), + group_id, + from_user_id, + update.affected_contact_id, + update.new_group_name, + update.new_delete_messages_after_milliseconds, + update.group_action_type, + ) + .execute(&mut **transaction) + .await?; + Ok(()) +} + +pub(crate) async fn handle_flame_sync( + transaction: &mut Transaction<'_, Sqlite>, + group_id: &str, + flame: encrypted_content::FlameSync, +) -> Result<()> { + let last_flame_counter_change = milliseconds_to_seconds(flame.last_flame_counter_change); + sqlx::query!( + r#" + UPDATE groups + SET also_best_friend = ?, + flame_counter = CASE WHEN ( + (date(last_flame_counter_change, 'unixepoch', 'localtime') = date('now', 'localtime') + AND date(?, 'unixepoch', 'localtime') = date('now', 'localtime')) + OR ? + ) THEN MAX(flame_counter, ?) ELSE flame_counter END, + max_flame_counter = CASE WHEN ( + (date(last_flame_counter_change, 'unixepoch', 'localtime') = date('now', 'localtime') + AND date(?, 'unixepoch', 'localtime') = date('now', 'localtime')) + OR ? + ) THEN MAX(max_flame_counter, ?) ELSE max_flame_counter END + WHERE group_id = ? AND last_flame_counter_change IS NOT NULL + "#, + flame.best_friend, + last_flame_counter_change, + flame.force_update, + flame.flame_counter, + last_flame_counter_change, + flame.force_update, + flame.flame_counter, + group_id, + ) + .execute(&mut **transaction) + .await?; + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/media.rs b/rust/src/api/messages/incoming/client2client/media.rs new file mode 100644 index 00000000..c5ed5f05 --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/media.rs @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::encrypted_content; +use crate::bridge::callbacks::get_callbacks; +use crate::database::app::tables::Group; +use crate::error::{Result, TwonlyError}; +use crate::utils::{milliseconds_to_seconds, new_uuid_v4}; +use encrypted_content::media::Type as MediaType; +use encrypted_content::media_update::Type as MediaUpdateType; +use sqlx::{Sqlite, Transaction}; + +fn spawn_media_action(kind: &'static str, media_id: String, contact_id: i64, message_id: String) { + if let Ok(callbacks) = get_callbacks() { + tokio::spawn(async move { + (callbacks.api.media_action)(kind.to_owned(), media_id, contact_id, message_id).await; + }); + } +} + +pub(crate) async fn handle_media( + t: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, + media: encrypted_content::Media, +) -> Result<()> { + let media_type = MediaType::try_from(media.r#type)?; + + if media_type == MediaType::Reupload { + let media_id = sqlx::query_scalar!( + r#" + SELECT media_id + FROM messages + WHERE message_id = ? AND sender_id = ? + "#, + media.sender_message_id, + from_user_id, + ) + .fetch_optional(&mut **t) + .await? + .flatten(); + + let Some(media_id) = media_id else { + return Err(TwonlyError::Generic("invalid media reupload target".into())); + }; + + sqlx::query!( + r#" + UPDATE media_files + SET download_state = 'pending', + download_token = ?, + encryption_key = ?, + encryption_mac = ?, + encryption_nonce = ? + WHERE media_id = ? + "#, + media.download_token, + media.encryption_key, + media.encryption_mac, + media.encryption_nonce, + media_id, + ) + .execute(&mut **t) + .await?; + + spawn_media_action("download", media_id, from_user_id, media.sender_message_id); + + return Ok(()); + } + + let timestamp = milliseconds_to_seconds(media.timestamp); + + let existing = sqlx::query!( + r#" + SELECT m.sender_id, m.media_id, f.download_state + FROM messages m + LEFT JOIN media_files f ON f.media_id = m.media_id + WHERE message_id = ? + "#, + media.sender_message_id, + ) + .fetch_optional(&mut **t) + .await?; + + if existing + .as_ref() + .is_some_and(|row| row.sender_id != Some(from_user_id)) + { + return Err(TwonlyError::Generic( + "attempted cross-sender media overwrite".into(), + )); + } + + let media_type = media_type.as_str_name().to_ascii_lowercase(); + + if let Some(existing) = existing { + let Some(media_id) = existing.media_id else { + return Ok(()); + }; + + if existing.download_state.as_deref() != Some("reuploadRequested") { + return Ok(()); + } + + sqlx::query!( + r#"UPDATE media_files SET type = ?, download_state = 'pending', + requires_authentication = ?, display_limit_in_milliseconds = ?, + download_token = ?, encryption_key = ?, encryption_mac = ?, + encryption_nonce = ?, created_at = ? WHERE media_id = ?"#, + media_type, + media.requires_authentication, + media.display_limit_in_milliseconds, + media.download_token, + media.encryption_key, + media.encryption_mac, + media.encryption_nonce, + timestamp, + media_id, + ) + .execute(&mut **t) + .await?; + + spawn_media_action("download", media_id, from_user_id, media.sender_message_id); + + return Ok(()); + } + + let media_id = new_uuid_v4(); + sqlx::query!( + r#" + INSERT INTO media_files( + media_id, + type, + download_state, + requires_authentication, + display_limit_in_milliseconds, + download_token, + encryption_key, + encryption_mac, + encryption_nonce, + created_at + ) VALUES (?, ?, 'pending', ?, ?, ?, ?, ?, ?, ?) + "#, + media_id, + media_type, + media.requires_authentication, + media.display_limit_in_milliseconds, + media.download_token, + media.encryption_key, + media.encryption_mac, + media.encryption_nonce, + timestamp, + ) + .execute(&mut **t) + .await?; + sqlx::query!( + r#" + INSERT INTO messages( + group_id, + message_id, + sender_id, + type, + media_id, + additional_message_data, + quotes_message_id, + created_at + ) VALUES (?, ?, ?, 'media', ?, ?, ?, ?) + "#, + group_id, + media.sender_message_id, + from_user_id, + media_id, + media.additional_message_data, + media.quote_message_id, + timestamp, + ) + .execute(&mut **t) + .await?; + Group::increase_last_message_exchange(t, group_id, timestamp).await?; + if let Ok(callbacks) = get_callbacks() { + let group_id = group_id.to_owned(); + let timestamp = media.timestamp; + tokio::spawn(async move { + (callbacks.api.media_received)(group_id, timestamp).await; + }); + } + spawn_media_action("download", media_id, from_user_id, media.sender_message_id); + Ok(()) +} + +pub(crate) async fn handle_media_update( + t: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + update: encrypted_content::MediaUpdate, +) -> Result<()> { + let media_id = sqlx::query_scalar!( + r#" + SELECT media_id + FROM messages + WHERE message_id = ? AND sender_id = ? + "#, + update.target_message_id, + from_user_id, + ) + .fetch_optional(&mut **t) + .await? + .flatten(); + let Some(media_id) = media_id else { + return Ok(()); + }; + + match MediaUpdateType::try_from(update.r#type)? { + MediaUpdateType::Reopened => { + sqlx::query!( + r#" + UPDATE messages + SET media_reopened = 1 + WHERE message_id = ? + "#, + update.target_message_id, + ) + .execute(&mut **t) + .await?; + } + MediaUpdateType::Stored => { + sqlx::query!( + r#" + UPDATE messages + SET media_stored = 1 + WHERE message_id = ? + "#, + update.target_message_id, + ) + .execute(&mut **t) + .await?; + + spawn_media_action( + "stored", + media_id.clone(), + from_user_id, + update.target_message_id.clone(), + ); + } + MediaUpdateType::DecryptionError => { + let requested_by = from_user_id.to_string(); + sqlx::query!( + r#" + UPDATE media_files + SET upload_state = 'reuploadRequested', + reupload_requested_by = ? + WHERE media_id = ? + "#, + requested_by, + media_id, + ) + .execute(&mut **t) + .await?; + + spawn_media_action("reupload", media_id, from_user_id, update.target_message_id); + } + } + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/messages.rs b/rust/src/api/messages/incoming/client2client/messages.rs new file mode 100644 index 00000000..3831dc33 --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/messages.rs @@ -0,0 +1,778 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::handle_encrypted; +use crate::api::proto::client::{self as proto, encrypted_content}; + +use crate::api::Server; +use crate::bridge::api::ServerResult; +use crate::bridge::callbacks::get_callbacks; +use crate::context::Context; +use crate::database::app::tables::{Contact, NewReceipt, Receipt}; +use crate::error::{twonly_error, Result, TwonlyError}; +use crate::utils::new_uuid_v4; +use prost::Message as ProstMessage; +use proto::encrypted_content::error_messages::Type; +use sqlx::{Sqlite, Transaction}; +use std::sync::Arc; + +#[cfg(not(debug_assertions))] +use std::{collections::HashMap, sync::LazyLock}; + +#[cfg(not(debug_assertions))] +static ALREADY_QUEUED_RECEIPTS: LazyLock>> = + LazyLock::new(|| std::sync::Mutex::new(HashMap::new())); + +async fn native_push_data( + _ctx: &Context, + contact_id: i64, + message_id: Option, + plaintext: &[u8], + message_type: i32, +) -> Result>> { + let callbacks = match get_callbacks() { + Ok(callbacks) => callbacks, + Err(TwonlyError::MissingCallbackInitialization) => { + return Ok(None); + } + Err(error) => return Err(error), + }; + Ok( + (callbacks.api.create_push_data)(contact_id, message_id, plaintext.to_vec(), message_type) + .await, + ) +} + +pub(crate) async fn queue_encrypted_content( + transaction: &mut Transaction<'_, Sqlite>, + target_user_id: i64, + content: proto::EncryptedContent, + contact_will_send_receipt: bool, +) -> Result { + let Ok(Some(contact)) = Contact::get_contact_by_id(transaction, target_user_id).await else { + return Err(twonly_error!("missing contact")); + }; + + let message = proto::Message { + r#type: if contact.signal_version == "v2" { + proto::message::Type::CiphertextV2 as i32 + } else { + proto::message::Type::Ciphertext as i32 + }, + receipt_id: String::new(), + encrypted_content: Some(content.encode_to_vec()), + plaintext_content: None, + } + .encode_to_vec(); + + let receipt_id = new_uuid_v4(); + NewReceipt::new(&receipt_id, target_user_id, &message) + .contact_will_send_receipt(contact_will_send_receipt) + .insert(transaction) + .await?; + + Ok(receipt_id) +} + +pub(crate) async fn process_encrypted_or_queue_error( + ctx: &Arc, + tr: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + receipt_id: &str, + content: proto::EncryptedContent, +) -> Result> { + let group_id = content.group_id.clone(); + + match handle_encrypted(ctx, tr, from_user_id, receipt_id, content).await { + Ok(()) => Ok(None), + Err(error) => { + let description = error.to_string(); + if description.contains("group join arrived before") { + queue_retry_control(tr, from_user_id, receipt_id).await?; + return Ok(Some(receipt_id.to_owned())); + } + let error_type = if description.contains("not a member") { + Some(Type::GroupNotFoundOrNotAMember) + } else if description.contains("not implemented in Rust") { + Some(Type::UnknownMessageType) + } else { + None + }; + let Some(error_type) = error_type else { + return Err(error); + }; + let outgoing_receipt_id = uuid::Uuid::new_v4().to_string(); + let response_content = proto::EncryptedContent { + group_id, + error_messages: Some(proto::encrypted_content::ErrorMessages { + r#type: error_type as i32, + related_receipt_id: receipt_id.to_owned(), + }), + ..Default::default() + }; + let response = proto::Message { + r#type: proto::message::Type::Ciphertext as i32, + receipt_id: String::new(), + encrypted_content: Some(response_content.encode_to_vec()), + plaintext_content: None, + } + .encode_to_vec(); + sqlx::query!( + r#" + INSERT INTO receipts(receipt_id, contact_id, message, contact_will_sends_receipt) + VALUES (?, ?, ?, 0) + "#, + outgoing_receipt_id, + from_user_id, + response, + ) + .execute(&mut **tr) + .await?; + Ok(Some(outgoing_receipt_id)) + } + } +} + +async fn queue_retry_control( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + receipt_id: &str, +) -> Result<()> { + let response = proto::Message { + r#type: proto::message::Type::PlaintextContent as i32, + receipt_id: String::new(), + encrypted_content: None, + plaintext_content: Some(proto::PlaintextContent { + decryption_error_message: None, + retry_control_error: Some(proto::plaintext_content::RetryErrorMessage {}), + }), + } + .encode_to_vec(); + sqlx::query!( + r#" + INSERT OR REPLACE INTO receipts(receipt_id, contact_id, message, contact_will_sends_receipt) + VALUES (?, ?, ?, 0) + "#, + receipt_id, + from_user_id, + response, + ) + .execute(&mut **transaction) + .await?; + Ok(()) +} + +pub(crate) async fn ensure_contact_exists(ctx: &Arc, from_user_id: i64) -> Result<()> { + let db_app = ctx.get_app_database().await; + + let exists = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM contacts WHERE user_id = ?)", + from_user_id, + ) + .fetch_one(&db_app.pool) + .await?; + + if exists != 0 { + return Ok(()); + } + + tracing::info!("Contact not found locally. Requesting user from the server."); + + let user = match Server::get_user_by_id(ctx, from_user_id).await? { + ServerResult::Ok(u) => u, + ServerResult::ErrorCode(code) => { + return Err(TwonlyError::Generic(format!( + "Failed to get user by id: {}", + code + ))); + } + }; + + let username = user + .username + .map(String::from_utf8) + .transpose()? + .unwrap_or_else(|| "[Unknown]".into()); + let signal_version = if user.pqc_bundle.is_some() { + "v2" + } else { + "v1" + }; + + tracing::info!("Loaded username: {username}"); + + sqlx::query!( + r#" + INSERT OR IGNORE INTO contacts(user_id, username, signal_version, accepted, requested) + VALUES (?, ?, ?, 0, 1) + "#, + from_user_id, + username, + signal_version, + ) + .execute(&db_app.pool) + .await?; + + if let Some(identity_key) = user.public_identity_key { + let rust_database = ctx.get_rust_db().await; + sqlx::query!( + r#" + INSERT INTO signal_identities(name, identity_key, timestamp) + VALUES (?, ?, CAST(strftime('%s', 'now') AS INTEGER)) + ON CONFLICT(name) DO NOTHING + "#, + from_user_id.to_string(), + identity_key, + ) + .execute(&rust_database.pool) + .await?; + } + + db_app.notify_committed(["contacts"]); + Ok(()) +} + +pub(crate) async fn queue_sender_delivery_receipt( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + receipt_id: &str, +) -> Result<()> { + let response = proto::Message { + r#type: proto::message::Type::SenderDeliveryReceipt as i32, + receipt_id: String::new(), + encrypted_content: None, + plaintext_content: None, + } + .encode_to_vec(); + sqlx::query!( + r#" + INSERT OR IGNORE INTO receipts( + receipt_id, contact_id, message, contact_will_sends_receipt + ) VALUES (?, ?, ?, 0) + "#, + receipt_id, + from_user_id, + response, + ) + .execute(&mut **transaction) + .await?; + Ok(()) +} + +pub(crate) fn spawn_receipt_delivery(ctx: &Arc, receipt_id: String) { + // The unsolicited-message handler runs on the socket reader task. Waiting + // for a request response there would deadlock that reader, so delivery is + // started only after the transaction commits and the handler can return. + let ctx = ctx.clone(); + tokio::spawn(async move { + if let Err(error) = send_queued_receipt(&ctx, &receipt_id).await { + tracing::warn!(receipt_id, "failed to send delivery receipt: {error}"); + } + }); +} + +pub(crate) async fn send_queued_receipt(ctx: &Arc, receipt_id: &str) -> Result<()> { + #[cfg(not(debug_assertions))] + { + let mut locks = ALREADY_QUEUED_RECEIPTS.lock().unwrap(); + if let Some(time) = locks.get(receipt_id) { + if time.elapsed() < std::time::Duration::from_secs(120) { + tracing::info!( + "Blocking queued receipt, as it was already sent within the last 120 seconds" + ); + return Ok(()); + } + } + locks.insert(receipt_id.to_owned(), std::time::Instant::now()); + locks.retain(|_, time| time.elapsed() < std::time::Duration::from_secs(120)); + } + + let app_db = ctx.get_app_database().await; + let row = sqlx::query!( + r#" + SELECT r.contact_id, r.message, r.message_id, r.contact_will_sends_receipt, + r.retry_count, c.account_deleted + FROM receipts r + JOIN contacts c ON c.user_id = r.contact_id + WHERE r.receipt_id = ? + "#, + receipt_id, + ) + .fetch_optional(&app_db.pool) + .await?; + + let Some(row) = row else { + return Ok(()); + }; + + if row.account_deleted != 0 { + Receipt::delete(&app_db.pool, receipt_id).await?; + app_db.notify_committed(["receipts"]); + return Ok(()); + } + + let mut message = proto::Message::decode(row.message.as_slice())?; + message.receipt_id = receipt_id.to_owned(); + + let message_type = proto::message::Type::try_from(message.r#type)?; + + let push_data = if row.retry_count == 0 { + native_push_data( + ctx, + row.contact_id, + row.message_id.clone(), + &message.encrypted_content.clone().unwrap_or_default(), + message.r#type, + ) + .await? + } else { + None + }; + + match message_type { + proto::message::Type::Ciphertext | proto::message::Type::PrekeyBundle => { + let plaintext = message.encrypted_content.take().ok_or_else(|| { + TwonlyError::Generic("queued legacy message has no plaintext".into()) + })?; + let (ciphertext, encrypted_type) = + encrypt_legacy_signal(row.contact_id, plaintext).await?; + message.encrypted_content = Some(ciphertext); + message.r#type = encrypted_type; + } + proto::message::Type::CiphertextV2 => { + let plaintext = message + .encrypted_content + .take() + .ok_or_else(|| TwonlyError::Generic("queued V2 message has no plaintext".into()))?; + let engine = ctx.get_signal_engine().lock().await; + message.encrypted_content = Some( + engine + .as_ref() + .ok_or(TwonlyError::SignalIdentityNotFound)? + .encrypt_message(row.contact_id.to_string(), 1, plaintext) + .await?, + ); + } + _ => {} + } + + match Server::send_text_message(ctx, row.contact_id, message.encode_to_vec(), push_data).await? + { + ServerResult::Ok(()) => {} + ServerResult::ErrorCode(code) => { + return Err(TwonlyError::Generic(format!( + "server rejected sender delivery receipt with code: {}", + code + ))); + } + } + + let mut t = app_db.pool.begin().await?; + if let Some(message_id) = row.message_id { + sqlx::query!( + r#" + INSERT INTO message_actions(message_id, contact_id, type) + VALUES (?, ?, 'ackByServerAt') + ON CONFLICT(message_id, contact_id, type) + DO UPDATE SET action_at = CAST(strftime('%s', 'now') AS INTEGER) + "#, + message_id, + row.contact_id, + ) + .execute(&mut *t) + .await?; + } + if row.contact_will_sends_receipt == 0 { + Receipt::delete(&mut *t, receipt_id).await?; + } else { + sqlx::query!( + r#" + UPDATE receipts SET + ack_by_server_at = CAST(strftime('%s', 'now') AS INTEGER), + retry_count = retry_count + 1, + last_retry = CAST(strftime('%s', 'now') AS INTEGER), + mark_for_retry = NULL + WHERE receipt_id = ? + "#, + receipt_id, + ) + .execute(&mut *t) + .await?; + } + t.commit().await?; + app_db.notify_committed(["receipts", "message_actions"]); + Ok(()) +} + +pub(crate) async fn prepare_queued_receipt( + ctx: &Context, + receipt_id: &str, +) -> Result, Option>)>> { + let database = ctx.get_app_database().await; + let row = sqlx::query!( + r#"SELECT contact_id, message, message_id, retry_count + FROM receipts WHERE receipt_id = ?"#, + receipt_id, + ) + .fetch_optional(&database.pool) + .await?; + let Some(row) = row else { return Ok(None) }; + let mut message = proto::Message::decode(row.message.as_slice()) + .map_err(|error| TwonlyError::Generic(format!("invalid queued message: {error}")))?; + message.receipt_id = receipt_id.to_owned(); + let push_data = if row.retry_count == 0 { + native_push_data( + ctx, + row.contact_id, + row.message_id, + &message.encrypted_content.clone().unwrap_or_default(), + message.r#type, + ) + .await? + } else { + None + }; + + match proto::message::Type::try_from(message.r#type) + .map_err(|_| TwonlyError::Generic("queued message has invalid type".into()))? + { + proto::message::Type::Ciphertext => { + let plaintext = message + .encrypted_content + .take() + .ok_or_else(|| TwonlyError::Generic("queued message has no content".into()))?; + + let (ciphertext, message_type) = + encrypt_legacy_signal(row.contact_id, plaintext).await?; + + message.encrypted_content = Some(ciphertext); + message.r#type = message_type; + } + proto::message::Type::CiphertextV2 => { + let plaintext = message + .encrypted_content + .take() + .ok_or_else(|| TwonlyError::Generic("queued message has no content".into()))?; + + let engine = ctx.get_signal_engine().lock().await; + + message.encrypted_content = Some( + engine + .as_ref() + .ok_or(TwonlyError::SignalIdentityNotFound)? + .encrypt_message(row.contact_id.to_string(), 1, plaintext) + .await?, + ); + } + _ => {} + } + Ok(Some((message.encode_to_vec(), push_data))) +} + +pub(crate) async fn retransmit_queued_receipts(ctx: &Arc) -> Result<()> { + let database = ctx.get_app_database().await; + let receipt_ids = sqlx::query_scalar!( + r#" + SELECT receipt_id FROM receipts + WHERE will_be_retried_by_media_upload = 0 + AND (ack_by_server_at IS NULL OR mark_for_retry IS NOT NULL) + AND (mark_for_retry_after_accepted IS NULL OR EXISTS( + SELECT 1 FROM contacts + WHERE contacts.user_id = receipts.contact_id AND contacts.accepted = 1 + )) + ORDER BY created_at + "# + ) + .fetch_all(&database.pool) + .await?; + for receipt_id in receipt_ids { + if let Err(error) = send_queued_receipt(ctx, &receipt_id).await { + tracing::warn!(receipt_id, "queued delivery receipt retry failed: {error}"); + } + } + Ok(()) +} + +pub(crate) async fn decrypt_legacy_signal_with_error( + from_user_id: i64, + ciphertext: Vec, + message_type: i32, +) -> Result> { + let callbacks = get_callbacks()?; + let decrypted = (callbacks.legacy_signal.decrypt)(from_user_id, ciphertext, message_type).await; + + let Some(plaintext) = decrypted.plaintext else { + return Ok(Err(decrypted.decryption_error_type.unwrap_or(0))); + }; + + proto::EncryptedContent::decode(plaintext.as_slice()) + .map(Ok) + .map_err(|error| { + TwonlyError::Generic(format!( + "Flutter returned invalid legacy Signal plaintext: {error}" + )) + }) +} + +pub(crate) async fn queue_decryption_error( + tr: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + receipt_id: &str, + error_type: i32, +) -> Result<()> { + let response = proto::Message { + r#type: proto::message::Type::PlaintextContent as i32, + receipt_id: String::new(), + encrypted_content: None, + plaintext_content: Some(proto::PlaintextContent { + decryption_error_message: Some(proto::plaintext_content::DecryptionErrorMessage { + r#type: error_type, + }), + retry_control_error: None, + }), + } + .encode_to_vec(); + NewReceipt::new(receipt_id, from_user_id, &response) + .contact_will_send_receipt(false) + .insert_or_replace(tr) + .await?; + Ok(()) +} + +pub(crate) async fn encrypt_legacy_signal( + target_user_id: i64, + plaintext: Vec, +) -> Result<(Vec, i32)> { + let callbacks = get_callbacks()?; + let encrypted = (callbacks.legacy_signal.encrypt)(target_user_id, plaintext) + .await + .ok_or_else(|| TwonlyError::Generic("Flutter legacy Signal encryption failed".into()))?; + if !is_legacy_signal_type(encrypted.message_type) { + return Err(TwonlyError::Generic(format!( + "Flutter returned non-legacy Signal message type {}", + encrypted.message_type + ))); + } + Ok((encrypted.ciphertext, encrypted.message_type)) +} + +fn is_legacy_signal_type(message_type: i32) -> bool { + use proto::message::Type; + message_type == Type::Ciphertext as i32 || message_type == Type::PrekeyBundle as i32 +} + +pub(crate) async fn handle_sender_delivery_receipt( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + receipt_id: &str, +) -> Result<()> { + let message_id = sqlx::query_scalar!( + r#" + SELECT message_id + FROM receipts + WHERE receipt_id = ? AND contact_id = ? + "#, + receipt_id, + from_user_id, + ) + .fetch_optional(&mut **transaction) + .await? + .flatten(); + if let Some(message_id) = message_id { + sqlx::query!( + r#" + INSERT INTO message_actions(message_id, contact_id, type) + VALUES (?, ?, 'ackByUserAt') + ON CONFLICT(message_id, contact_id, type) + DO UPDATE SET action_at = CAST(strftime('%s', 'now') AS INTEGER) + "#, + message_id, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + } + sqlx::query!( + r#" + DELETE FROM receipts + WHERE receipt_id = ? AND contact_id = ? + "#, + receipt_id, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + Ok(()) +} + +pub async fn handle_plaintext_content( + _ctx: &Context, + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + receipt_id: &str, + plaintext: proto::PlaintextContent, +) -> Result> { + if plaintext.decryption_error_message.is_some() || plaintext.retry_control_error.is_some() { + let new_receipt_id = uuid::Uuid::new_v4().to_string(); + sqlx::query!( + r#" + UPDATE receipts + SET receipt_id = ?, + mark_for_retry = CAST(strftime('%s', 'now') AS INTEGER), + retry_count = retry_count + 1, + ack_by_server_at = NULL + WHERE receipt_id = ? AND contact_id = ? + "#, + new_receipt_id, + receipt_id, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + return Ok(Some(new_receipt_id)); + } + Ok(None) +} + +pub(crate) async fn handle_message_update( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + update: encrypted_content::MessageUpdate, +) -> Result<()> { + use encrypted_content::message_update::Type; + let timestamp = crate::utils::milliseconds_to_seconds(update.timestamp); + + match Type::try_from(update.r#type) + .map_err(|_| TwonlyError::Generic("invalid message update".into()))? + { + Type::Opened => { + for message_id in update.multiple_target_message_ids { + let action_at = sqlx::query_scalar::<_, i64>( + "SELECT MAX(created_at, ?) FROM messages WHERE message_id = ?", + ) + .bind(timestamp) + .bind(&message_id) + .fetch_optional(&mut **transaction) + .await?; + let Some(action_at) = action_at else { continue }; + sqlx::query!( + r#" + INSERT INTO message_actions(message_id, contact_id, type, action_at) + VALUES (?, ?, 'openedAt', ?) + ON CONFLICT(message_id, contact_id, type) + DO UPDATE SET action_at = excluded.action_at + "#, + message_id, + from_user_id, + action_at, + ) + .execute(&mut **transaction) + .await?; + sqlx::query!( + r#"UPDATE messages SET opened_at = ?, opened_by_all = CASE WHEN NOT EXISTS( + SELECT 1 FROM group_members gm + WHERE gm.group_id = messages.group_id AND NOT EXISTS( + SELECT 1 FROM message_actions ma + WHERE ma.message_id = messages.message_id + AND ma.contact_id = gm.contact_id AND ma.type = 'openedAt' + ) + ) THEN ? ELSE NULL END + WHERE message_id = ?"#, + action_at, + action_at, + message_id, + ) + .execute(&mut **transaction) + .await?; + } + } + Type::Delete => { + let media_id = sqlx::query_scalar!( + "SELECT media_id FROM messages WHERE message_id = ? AND sender_id = ?", + update.sender_message_id, + from_user_id, + ) + .fetch_optional(&mut **transaction) + .await? + .flatten(); + sqlx::query!( + "DELETE FROM message_histories WHERE message_id = ?", + update.sender_message_id + ) + .execute(&mut **transaction) + .await?; + sqlx::query!( + "DELETE FROM receipts WHERE message_id = ?", + update.sender_message_id + ) + .execute(&mut **transaction) + .await?; + sqlx::query!( + r#" + UPDATE messages + SET is_deleted_from_sender = 1, content = NULL, media_id = NULL, modified_at = ? + WHERE message_id = ? AND sender_id = ? + "#, + timestamp, + update.sender_message_id, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + if let Some(media_id) = media_id { + let references = sqlx::query_scalar!( + "SELECT COUNT(*) FROM messages WHERE media_id = ?", + media_id, + ) + .fetch_one(&mut **transaction) + .await?; + if references == 0 { + sqlx::query!("DELETE FROM media_files WHERE media_id = ?", media_id) + .execute(&mut **transaction) + .await?; + if let Ok(callbacks) = get_callbacks() { + if let Some(message_id) = update.sender_message_id.clone() { + tokio::spawn(async move { + (callbacks.api.media_action)( + "delete".into(), + media_id, + from_user_id, + message_id, + ) + .await; + }); + } + } + } + } + } + Type::EditText => { + sqlx::query!( + r#"INSERT INTO message_histories(message_id, content, created_at) + SELECT message_id, content, ? FROM messages + WHERE message_id = ? AND sender_id = ? AND content IS NOT NULL"#, + timestamp, + update.sender_message_id, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + sqlx::query!( + r#" + UPDATE messages + SET content = ?, modified_at = ? + WHERE message_id = ? AND sender_id = ? AND content IS NOT NULL + "#, + update.text, + timestamp, + update.sender_message_id, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + } + } + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/mod.rs b/rust/src/api/messages/incoming/client2client/mod.rs new file mode 100644 index 00000000..fae930e3 --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/mod.rs @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::messages::content_type_kind; +use crate::api::proto::client as proto; +use crate::context::Context; +use crate::database::app::tables::{Contact, Group, Receipt}; +use crate::error::{Result, TwonlyError}; +use sqlx::{Sqlite, Transaction}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex, OnceLock}; +use typing_indicator::handle_typing_indicator; + +static REQUESTED_PROFILES: OnceLock>> = OnceLock::new(); + +mod additional_data; +pub(crate) mod contact; +mod errors; +mod groups; +mod media; +pub mod messages; +mod pushkeys; +mod reaction; +mod recovery; +mod text_message; +mod typing_indicator; +mod user_discovery; +mod verification; + +/// Dispatches already-decrypted client-to-client content to its concrete +/// feature module. Transport decoding and Signal decryption do not belong in +/// this dispatcher. +pub(crate) async fn handle_encrypted( + ctx: &Arc, + tr: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + receipt_id: &str, + content: proto::EncryptedContent, +) -> Result<()> { + Receipt::mark_all_for_retry(tr, from_user_id).await?; + + if let Some(version) = content.sender_user_discovery_version.clone() { + user_discovery::check_sender_version(ctx, tr, from_user_id, version).await?; + } + + if let Some(sender_profile_counter) = content.sender_profile_counter { + let current_counter = sqlx::query_scalar!( + "SELECT sender_profile_counter FROM contacts WHERE user_id = ?", + from_user_id, + ) + .fetch_optional(&mut **tr) + .await? + .unwrap_or(0); + + if content.contact_update.is_none() && sender_profile_counter > current_counter { + let should_request = { + let mut requested = REQUESTED_PROFILES + .get_or_init(|| Mutex::new(HashMap::new())) + .lock() + .unwrap(); + let last_requested = requested.get(&from_user_id).copied().unwrap_or(0); + + if sender_profile_counter > last_requested { + requested.insert(from_user_id, sender_profile_counter); + true + } else { + false + } + }; + + if should_request { + messages::queue_encrypted_content( + tr, + from_user_id, + proto::EncryptedContent { + contact_update: Some(proto::encrypted_content::ContactUpdate { + r#type: proto::encrypted_content::contact_update::Type::Request as i32, + username: None, + display_name: None, + avatar_svg_compressed: None, + }), + ..Default::default() + }, + true, + ) + .await?; + } + } + } + + if content.ask_for_friend_promotions == Some(true) { + Contact::update_ask_for_friend_promotions(tr, from_user_id).await?; + } + + let type_kind = content_type_kind(&content); + + tracing::Span::current().record("kind", type_kind); + tracing::info!("Handling incoming message: {type_kind}"); + + if let Some(request) = content.contact_request { + return contact::handle_contact_request(ctx, tr, from_user_id, request).await; + } + + if let Some(update) = content.contact_update { + return contact::handle_contact_update( + ctx, + tr, + from_user_id, + content.sender_profile_counter, + update, + ) + .await; + } + + if let Some(update) = content.message_update { + return messages::handle_message_update(tr, from_user_id, update).await; + } + + if let Some(update) = content.media_update { + return media::handle_media_update(tr, from_user_id, update).await; + } + + if let Some(error) = content.error_messages { + return errors::handle_error_message( + tr, + from_user_id, + receipt_id, + content.group_id.as_deref(), + error, + ) + .await; + } + + if let Some(push_keys) = content.push_keys { + return pushkeys::handle_push_key(tr, from_user_id, push_keys).await; + } + + if let Some(update) = content.user_discovery_update { + return user_discovery::handle_user_discovery_update(ctx, from_user_id, update).await; + } + + if let Some(request) = content.user_discovery_request { + return user_discovery::handle_user_discovery_request(ctx, tr, from_user_id, request).await; + } + + if let Some(proof) = content.key_verification_proof { + return verification::handle_key_verification_proof(tr, from_user_id, proof).await; + } + + if let Some(recovery) = content.passwordless_recovery { + return recovery::handle_passwordless_recovery(tr, from_user_id, recovery).await; + } + + if let Some(heartbeat) = content.passwordless_recovery_heartbeat { + return recovery::handle_passwordless_recovery_heartbeat(tr, from_user_id, heartbeat).await; + } + + let group_id = content + .group_id + .ok_or_else(|| TwonlyError::Generic("group-scoped message has no group ID".into()))?; + + if let Some(create) = content.group_create { + return groups::handle_group_create(tr, from_user_id, &group_id, create).await; + } + + if let Some(join) = content.group_join { + return groups::handle_group_join(tr, from_user_id, &group_id, join).await; + } + + let is_member = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM group_members WHERE group_id = ? AND contact_id = ?)", + group_id, + from_user_id, + ) + .fetch_one(&mut **tr) + .await? + != 0; + + if !is_member { + let local_user_id = ctx.user_id().await?; + + if Group::direct_chat_id(local_user_id, from_user_id) == group_id { + let contact = Contact::get_contact_by_id(tr, from_user_id).await?; + + if let Some(contact) = + contact.filter(|value| value.accepted != 0 && value.deleted_by_user == 0) + { + Group::create_direct_chat(ctx, tr, contact).await?; + } else { + sqlx::query!( + "UPDATE contacts SET requested = 1, deleted_by_user = 0 WHERE user_id = ?", + from_user_id, + ) + .execute(&mut **tr) + .await?; + messages::queue_encrypted_content( + tr, + from_user_id, + proto::EncryptedContent { + error_messages: Some(proto::encrypted_content::ErrorMessages { + r#type: proto::encrypted_content::error_messages::Type::ErrorProcessingMessageCreatedAccountRequestInstead as i32, + related_receipt_id: receipt_id.to_owned(), + }), + ..Default::default() + }, + false, + ) + .await?; + return Ok(()); + } + } + } + + groups::ensure_group_member(tr, from_user_id, &group_id).await?; + + if content.resend_group_public_key.is_some() { + return groups::handle_resend_group_public_key(tr, from_user_id, &group_id).await; + } + + if let Some(update) = content.group_update { + return groups::handle_group_update(tr, from_user_id, &group_id, update).await; + } + + if let Some(flame) = content.flame_sync { + return groups::handle_flame_sync(tr, &group_id, flame).await; + } + + if let Some(message) = content.text_message { + return text_message::handle_text_message(tr, from_user_id, &group_id, message).await; + } + + if let Some(message) = content.additional_data_message { + return additional_data::handle_additional_data_message( + ctx, + tr, + from_user_id, + &group_id, + message, + ) + .await; + } + + if let Some(media) = content.media { + return media::handle_media(tr, from_user_id, &group_id, media).await; + } + + if let Some(reaction) = content.reaction { + return reaction::handle_reaction(tr, from_user_id, &group_id, reaction).await; + } + + if let Some(indicator) = content.typing_indicator { + return handle_typing_indicator(tr, from_user_id, &group_id, indicator).await; + } + + Err(TwonlyError::Generic(format!( + "client2client content in receipt {receipt_id} is not implemented in Rust" + ))) +} diff --git a/rust/src/api/messages/incoming/client2client/pushkeys.rs b/rust/src/api/messages/incoming/client2client/pushkeys.rs new file mode 100644 index 00000000..b31bcf6c --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/pushkeys.rs @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::encrypted_content; +use crate::error::{Result, TwonlyError}; +use crate::utils::milliseconds_to_seconds; +use sqlx::{Sqlite, Transaction}; +use std::sync::atomic::{AtomicI64, Ordering}; + +static LAST_PUSH_KEY_REQUEST: AtomicI64 = AtomicI64::new(0); +pub(crate) async fn handle_push_key( + tx: &mut Transaction<'_, Sqlite>, + user: i64, + value: encrypted_content::PushKeys, +) -> Result<()> { + use encrypted_content::push_keys::Type; + match Type::try_from(value.r#type) + .map_err(|_| TwonlyError::Generic("invalid push-key message".into()))? + { + Type::Update => { + let (Some(id), Some(key), Some(created_at)) = + (value.key_id, value.key, value.created_at) + else { + return Err(TwonlyError::Generic("incomplete push-key update".into())); + }; + sqlx::query!( + r#" + INSERT INTO contact_push_keys(contact_id, key_id, key, created_at) + VALUES (?, ?, ?, ?) + ON CONFLICT(contact_id, key_id) + DO UPDATE SET key = excluded.key, created_at = excluded.created_at + "#, + user, + id, + key, + milliseconds_to_seconds(created_at), + ) + .execute(&mut **tx) + .await?; + Ok(()) + } + Type::Request => { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64; + let previous = LAST_PUSH_KEY_REQUEST.load(Ordering::Relaxed); + if now - previous < 60 + || LAST_PUSH_KEY_REQUEST + .compare_exchange(previous, now, Ordering::AcqRel, Ordering::Relaxed) + .is_err() + { + return Ok(()); + } + if let Ok(callbacks) = crate::bridge::callbacks::get_callbacks() { + tokio::spawn(async move { + (callbacks.api.push_key_requested)(user).await; + }); + } + Ok(()) + } + } +} diff --git a/rust/src/api/messages/incoming/client2client/reaction.rs b/rust/src/api/messages/incoming/client2client/reaction.rs new file mode 100644 index 00000000..90d7d0af --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/reaction.rs @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::encrypted_content; +use crate::bridge::callbacks::get_callbacks; +use crate::database::app::tables::Group; +use crate::error::Result; +use sqlx::{Sqlite, Transaction}; + +pub(crate) async fn handle_reaction( + t: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, + reaction: encrypted_content::Reaction, +) -> Result<()> { + if reaction.remove { + sqlx::query!( + r#" + DELETE FROM reactions + WHERE message_id = ? AND sender_id = ? AND emoji = ? + "#, + reaction.target_message_id, + from_user_id, + reaction.emoji, + ) + .execute(&mut **t) + .await?; + } else { + sqlx::query!( + r#" + INSERT INTO reactions(message_id, emoji, sender_id) + VALUES (?, ?, ?) + ON CONFLICT(message_id, sender_id, emoji) DO NOTHING + "#, + reaction.target_message_id, + reaction.emoji, + from_user_id, + ) + .execute(&mut **t) + .await?; + } + + if !reaction.remove { + Group::increase_last_message_exchange_to_now(t, group_id).await?; + } + + if let Ok(callbacks) = get_callbacks() { + let message_id = reaction.target_message_id; + tokio::spawn(async move { + (callbacks.api.media_action)( + "response".into(), + String::new(), + from_user_id, + message_id, + ) + .await; + }); + } + + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/recovery.rs b/rust/src/api/messages/incoming/client2client/recovery.rs new file mode 100644 index 00000000..3b643184 --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/recovery.rs @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::encrypted_content; +use crate::error::Result; +use sha2::{Digest, Sha256}; +use sqlx::{Sqlite, Transaction}; + +pub(crate) async fn handle_passwordless_recovery( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + recovery: encrypted_content::PasswordLessRecovery, +) -> Result<()> { + if recovery.delete { + sqlx::query!( + r#" + UPDATE contacts + SET recovery_contacts_secret_share = NULL, + recovery_contacts_last_heartbeat = NULL, + recovery_contacts_threshold = NULL + WHERE user_id = ? + "#, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + } else if let Some(share) = recovery.recovery_secret_share { + sqlx::query!( + r#" + UPDATE contacts + SET recovery_contacts_secret_share = ?, + recovery_contacts_threshold = ?, + recovery_contacts_last_heartbeat = NULL + WHERE user_id = ? + "#, + share, + recovery.threshold, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + } + if let Ok(callbacks) = crate::bridge::callbacks::get_callbacks() { + tokio::spawn(async move { + (callbacks.api.recovery_changed)().await; + }); + } + Ok(()) +} + +pub(crate) async fn handle_passwordless_recovery_heartbeat( + transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + heartbeat: encrypted_content::PasswordLessRecoveryHeartbeat, +) -> Result<()> { + let share = sqlx::query_scalar!( + r#" + SELECT recovery_secret_share + FROM contacts + WHERE user_id = ? + "#, + from_user_id, + ) + .fetch_optional(&mut **transaction) + .await? + .flatten(); + let valid = share + .as_deref() + .is_some_and(|share| Sha256::digest(share).as_slice() == heartbeat.hash); + if share.is_none() { + super::messages::queue_encrypted_content( + transaction, + from_user_id, + crate::api::proto::client::EncryptedContent { + passwordless_recovery: Some(encrypted_content::PasswordLessRecovery { + recovery_secret_share: None, + threshold: 0, + delete: true, + }), + ..Default::default() + }, + true, + ) + .await?; + } + sqlx::query!( + r#" + UPDATE contacts + SET recovery_last_heartbeat = CASE + WHEN ? THEN CAST(strftime('%s', 'now') AS INTEGER) + ELSE NULL + END + WHERE user_id = ? + "#, + valid, + from_user_id, + ) + .execute(&mut **transaction) + .await?; + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/text_message.rs b/rust/src/api/messages/incoming/client2client/text_message.rs new file mode 100644 index 00000000..5f1cfcfe --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/text_message.rs @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::encrypted_content; +use crate::database::app::tables::{Group, Message, MessageType, NewMessage}; +use crate::error::Result; +use crate::utils::milliseconds_to_seconds; +use sqlx::{Sqlite, Transaction}; + +pub(crate) async fn handle_text_message( + t: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, + message: encrypted_content::TextMessage, +) -> Result<()> { + Message::check_message_owner(t, &message.sender_message_id, from_user_id).await?; + + let timestamp = milliseconds_to_seconds(message.timestamp); + + NewMessage::builder() + .group_id(group_id) + .message_id(&message.sender_message_id) + .message_type(MessageType::Text) + .created_at(timestamp) + .sender_id(from_user_id) + .content(&message.text) + .maybe_quotes_message_id(message.quote_message_id.as_deref()) + .ack_by_server(chrono::Utc::now().timestamp()) + .build() + .insert(t) + .await?; + + Group::increase_last_message_exchange(t, group_id, timestamp).await?; + + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/typing_indicator.rs b/rust/src/api/messages/incoming/client2client/typing_indicator.rs new file mode 100644 index 00000000..56ee39f8 --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/typing_indicator.rs @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::encrypted_content; +use crate::error::Result; +use crate::utils::milliseconds_to_seconds; +use sqlx::{Sqlite, Transaction}; + +pub(crate) async fn handle_typing_indicator( + t: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + group_id: &str, + indicator: encrypted_content::TypingIndicator, +) -> Result<()> { + let created_at = milliseconds_to_seconds(indicator.created_at); + let typing = indicator.is_typing.then_some(created_at); + sqlx::query!( + r#" + UPDATE group_members + SET last_chat_opened = ?, last_type_indicator = ? + WHERE group_id = ? AND contact_id = ? + "#, + created_at, + typing, + group_id, + from_user_id, + ) + .execute(&mut **t) + .await?; + Ok(()) +} diff --git a/rust/src/api/messages/incoming/client2client/user_discovery.rs b/rust/src/api/messages/incoming/client2client/user_discovery.rs new file mode 100644 index 00000000..da8ad029 --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/user_discovery.rs @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::messages::queue_encrypted_content; +use crate::api::proto::client::{self as proto, encrypted_content}; +use crate::context::Context; +use crate::database::app::tables::Contact; +use crate::error::{twonly_error, Result, TwonlyError}; +use crate::user_config::UserConfig; +use sqlx::{Sqlite, Transaction}; +use std::collections::HashSet; +use std::sync::LazyLock; +use tokio::sync::Mutex; + +static REQUESTED_UPDATES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); + +pub(crate) async fn check_sender_version( + ctx: &Context, + t: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + version: Vec, +) -> Result<()> { + if !UserConfig::load_required_from(ctx)?.is_user_discovery_enabled { + return Ok(()); + } + + if version.len() > 64 { + return Err(twonly_error!( + "sender user-discovery version is unexpectedly large" + )); + } + + let Some(current_version) = ctx + .get_user_discovery() + .get() + .await + .should_request_new_messages(from_user_id, &version) + .await? + else { + return Ok(()); + }; + + if !REQUESTED_UPDATES.lock().await.insert(from_user_id) { + return Ok(()); + } + + queue_encrypted_content( + t, + from_user_id, + proto::EncryptedContent { + user_discovery_request: Some(encrypted_content::UserDiscoveryRequest { + current_version, + }), + ..Default::default() + }, + true, + ) + .await?; + + Ok(()) +} +pub(crate) async fn handle_user_discovery_request( + ctx: &Context, + t: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + request: encrypted_content::UserDiscoveryRequest, +) -> Result<()> { + if !UserConfig::load_required_from(ctx)?.is_user_discovery_enabled + || !Contact::is_user_discovery_allowed(ctx, t, from_user_id).await? + { + return Ok(()); + } + if request.current_version.len() > 64 { + return Err(TwonlyError::Generic( + "user-discovery version is unexpectedly large".into(), + )); + } + let messages = ctx + .get_user_discovery() + .get() + .await + .get_new_messages(from_user_id, &request.current_version) + .await?; + if !messages.is_empty() { + queue_encrypted_content( + t, + from_user_id, + proto::EncryptedContent { + user_discovery_update: Some(encrypted_content::UserDiscoveryUpdate { messages }), + ..Default::default() + }, + true, + ) + .await?; + } + Ok(()) +} +pub(crate) async fn handle_user_discovery_update( + ctx: &Context, + from_user_id: i64, + update: encrypted_content::UserDiscoveryUpdate, +) -> Result<()> { + if !UserConfig::load_required_from(ctx)?.is_user_discovery_enabled { + return Ok(()); + } + if update.messages.iter().any(|message| message.is_empty()) { + return Err(TwonlyError::Generic( + "user-discovery update contains an empty message".into(), + )); + } + Ok(ctx + .get_user_discovery() + .get() + .await + .handle_new_messages(from_user_id, None, update.messages) + .await?) +} diff --git a/rust/src/api/messages/incoming/client2client/verification.rs b/rust/src/api/messages/incoming/client2client/verification.rs new file mode 100644 index 00000000..f7ffd7a7 --- /dev/null +++ b/rust/src/api/messages/incoming/client2client/verification.rs @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::encrypted_content; +use crate::bridge::callbacks::get_callbacks; +use crate::error::Result; +use sqlx::{Sqlite, Transaction}; + +pub(crate) async fn handle_key_verification_proof( + _transaction: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + proof: encrypted_content::KeyVerificationProof, +) -> Result<()> { + if let Ok(callbacks) = get_callbacks() { + tokio::spawn(async move { + (callbacks.api.verification_proof)(from_user_id, proof.calculated_mac).await; + }); + } + Ok(()) +} diff --git a/rust/src/api/messages/incoming/mod.rs b/rust/src/api/messages/incoming/mod.rs new file mode 100644 index 00000000..565008c4 --- /dev/null +++ b/rust/src/api/messages/incoming/mod.rs @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +pub(crate) mod client2client; + +use crate::api::messages::incoming::client2client::messages::{ + decrypt_legacy_signal_with_error, ensure_contact_exists, handle_plaintext_content, + handle_sender_delivery_receipt, process_encrypted_or_queue_error, queue_decryption_error, + queue_sender_delivery_receipt, retransmit_queued_receipts, spawn_receipt_delivery, +}; +use crate::api::proto::client as proto; +use crate::api::proto::server_to_client::NewMessage; +use crate::api::proto::{client_to_server, server_to_client}; +use crate::bridge::callbacks::get_callbacks; +use crate::context::Context; +use crate::database::app::tables::Receipt; +use crate::error::{Result, TwonlyError}; +use crate::sealed_sender::SealedSender; +use client_to_server::response::{ok, Response}; +use prost::Message as _; +use proto::message::Type; +use server_to_client::v0::Kind; +use std::sync::Arc; + +pub(crate) async fn handle_server_message( + ctx: &Arc, + kind: server_to_client::v0::Kind, +) -> Result { + let ok = match kind { + // These booleans are presence-only request markers. Their value is not part of + // the protocol; the Dart client likewise checks hasRequestNewPreKeys() only. + Kind::RequestNewPreKeys(_) => match handle_request_new_prekeys(ctx).await { + Ok(response) => response, + Err(error) => { + tracing::error!("failed to generate requested prekeys: {error}"); + ok::Ok::None(true) + } + }, + Kind::RequestNewPqcPreKeys(_) => match handle_request_new_pqc_prekeys(ctx).await { + Ok(response) => response, + Err(error) => { + tracing::error!("failed to generate requested PQC prekeys: {error}"); + ok::Ok::None(true) + } + }, + Kind::NewMessage(message) => { + if let Err(error) = handle_new_server_message(ctx, message).await { + tracing::error!("failed to process client message: {error}"); + } + ok::Ok::None(true) + } + Kind::NewMessages(messages) => { + for message in messages.new_messages { + if let Err(error) = handle_new_server_message(ctx, message).await { + // One bad item must not block the rest of a server batch. + tracing::error!("failed to process client message in batch: {error}"); + } + } + ok::Ok::None(true) + } + Kind::SealedSenderMessage(message) => { + if let Err(error) = handle_sealed_message(ctx, message.body).await { + tracing::error!("failed to process sealed-sender message: {error}"); + } + ok::Ok::None(true) + } + Kind::SealedSenderMessages(messages) => { + for message in messages.messages { + if let Err(error) = handle_sealed_message(ctx, message.body).await { + tracing::error!("failed to process sealed-sender message in batch: {error}"); + } + } + ok::Ok::None(true) + } + other => { + // Dart logged unknown unsolicited messages but still acknowledged + // their envelope, preventing an infinite server redelivery loop. + tracing::error!("unsupported unsolicited server message: {other:?}"); + ok::Ok::None(true) + } + }; + + Ok(client_to_server::Response { + response: Some(Response::Ok(client_to_server::response::Ok { + ok: Some(ok), + })), + }) +} + +pub(crate) async fn handle_new_server_message( + ctx: &Arc, + server_message: NewMessage, +) -> Result<()> { + let message = proto::Message::decode(server_message.body.as_slice())?; + handle_decoded_server_message(ctx, server_message.from_user_id, message).await +} + +pub(crate) async fn handle_sealed_message(ctx: &Arc, bytes: Vec) -> Result<()> { + let payload = SealedSender::decrypt(&bytes, ctx.as_ref()).await?; + + let message = payload + .message + .ok_or_else(|| TwonlyError::Generic("sealed message contains no client message".into()))?; + + handle_decoded_server_message(ctx, payload.from_user_id, message).await +} + +pub(crate) async fn handle_request_new_prekeys( + ctx: &Arc, +) -> Result { + let prekeys = match get_callbacks() { + Ok(callbacks) => (callbacks.legacy_signal.generate_prekeys)() + .await + .into_iter() + .map(|key| client_to_server::response::PreKey { + id: key.id, + prekey: key.public_key, + }) + .collect(), + Err(TwonlyError::MissingCallbackInitialization) => { + let engine = ctx.get_signal_engine().lock().await; + engine + .as_ref() + .ok_or(TwonlyError::SignalIdentityNotFound)? + .generate_prekeys(200) + .await? + .into_iter() + .map(|(id, public_key)| client_to_server::response::PreKey { + id: i64::from(id), + prekey: public_key, + }) + .collect() + } + Err(error) => return Err(error), + }; + + Ok(client_to_server::response::ok::Ok::Prekeys( + client_to_server::response::Prekeys { prekeys }, + )) +} + +pub(crate) async fn handle_request_new_pqc_prekeys( + ctx: &Arc, +) -> Result { + let engine = ctx.get_signal_engine().lock().await; + + let prekeys = engine + .as_ref() + .ok_or(TwonlyError::SignalIdentityNotFound)? + .generate_pqc_prekeys() + .await? + .into_iter() + .map(|key| client_to_server::application_data::PqcPreKey { + ecc_pre_key_id: i64::from(key.ecc_pre_key_id), + ecc_pre_key: key.ecc_pre_key, + kyber_pre_key_id: i64::from(key.kyber_pre_key_id), + kyber_pre_key: key.kyber_pre_key, + kyber_pre_key_signature: key.kyber_pre_key_signature, + }) + .collect(); + + Ok(client_to_server::response::ok::Ok::PrekeysPqc( + client_to_server::response::PqcPrekeys { prekeys }, + )) +} + +#[tracing::instrument( + skip_all, + fields( + receipt_id = tracing::field::Empty, + user = tracing::field::Empty, + kind = tracing::field::Empty + ) +)] +pub(crate) async fn handle_decoded_server_message( + ctx: &Arc, + from_user_id: i64, + message: proto::Message, +) -> Result<()> { + tracing::Span::current().record("receipt_id", &message.receipt_id); + if let Ok(user) = ctx.user_id().await { + tracing::Span::current().record("user", user); + } + tracing::info!("Started processing incoming message"); + + if message.receipt_id.is_empty() { + return Err(TwonlyError::Generic( + "client message has no receipt ID".into(), + )); + } + + let message_type = Type::try_from(message.r#type)?; + let is_encrypted_message = matches!( + message_type, + Type::Ciphertext | Type::PrekeyBundle | Type::CiphertextV2 + ); + tracing::info!(is_encrypted_message, ?message_type, "Parsed message type"); + + if is_encrypted_message { + tracing::info!("Ensuring contact exists..."); + ensure_contact_exists(ctx, from_user_id).await?; + } + + let database = ctx.get_app_database().await; + + let mut tr = database.pool.begin().await?; + + let claimed = Receipt::claim_received(&mut tr, &message.receipt_id).await?; + + if !claimed { + // Delivery receipts are terminal messages and must never themselves be + // acknowledged. For regular messages Dart retries the delivery receipt + // after ten days, atomically claiming the retry by moving created_at. + let should_resend = message_type != Type::SenderDeliveryReceipt + && Receipt::claim_received_retry(&mut tr, &message.receipt_id).await?; + if should_resend { + tracing::info!("Queueing sender delivery receipt for retry"); + queue_sender_delivery_receipt(&mut tr, from_user_id, &message.receipt_id).await?; + } + tr.commit().await?; + if should_resend { + tracing::info!("Spawning receipt delivery for retry"); + spawn_receipt_delivery(ctx, message.receipt_id); + } + tracing::info!("Returning early because receipt was already claimed"); + return Ok(()); + } + + let mut sends_error_response = false; + + match message_type { + Type::SenderDeliveryReceipt => { + tracing::info!("Received sender delivery receipt"); + handle_sender_delivery_receipt(&mut tr, from_user_id, &message.receipt_id).await?; + } + Type::Ciphertext | Type::PrekeyBundle => { + let ciphertext = message.encrypted_content.ok_or_else(|| { + TwonlyError::Generic("legacy encrypted client message has no ciphertext".into()) + })?; + tracing::info!("Decrypting legacy signal message..."); + match decrypt_legacy_signal_with_error(from_user_id, ciphertext, message.r#type).await? + { + Ok(content) => { + tracing::info!("Decrypted successfully, processing..."); + sends_error_response = process_encrypted_or_queue_error( + ctx, + &mut tr, + from_user_id, + &message.receipt_id, + content, + ) + .await? + .is_some(); + } + Err(error_type) => { + tracing::info!(error_type, "Decryption error"); + if error_type + == proto::plaintext_content::decryption_error_message::Type::PrekeyUnknown + as i32 + { + if let Ok(callbacks) = get_callbacks() { + (callbacks.api.resync_signal_session)(from_user_id).await; + } + } + queue_decryption_error(&mut tr, from_user_id, &message.receipt_id, error_type) + .await?; + sends_error_response = true; + } + } + } + Type::CiphertextV2 => { + let ciphertext = message.encrypted_content.ok_or_else(|| { + TwonlyError::Generic("V2 encrypted client message has no ciphertext".into()) + })?; + let decrypted = { + let engine = ctx.get_signal_engine().lock().await; + engine + .as_ref() + .ok_or(TwonlyError::SignalIdentityNotFound)? + .decrypt_message(from_user_id.to_string(), 1, ciphertext) + .await + }; + match decrypted { + Ok(plaintext) => { + let content = + proto::EncryptedContent::decode(plaintext.as_slice()).map_err(|error| { + TwonlyError::Generic(format!( + "invalid decrypted client content: {error}" + )) + })?; + sends_error_response = process_encrypted_or_queue_error( + ctx, + &mut tr, + from_user_id, + &message.receipt_id, + content, + ) + .await? + .is_some(); + } + Err(error) => { + tracing::warn!( + receipt_id = message.receipt_id, + "V2 decryption failed: {error}" + ); + queue_decryption_error(&mut tr, from_user_id, &message.receipt_id, 0).await?; + sends_error_response = true; + } + } + } + Type::PlaintextContent => { + tracing::info!("Handling plaintext content"); + let plaintext = message.plaintext_content.ok_or_else(|| { + TwonlyError::Generic("plaintext client message has no content".into()) + })?; + handle_plaintext_content(ctx, &mut tr, from_user_id, &message.receipt_id, plaintext) + .await?; + } + Type::TestNotification => {} + } + + if is_encrypted_message && !sends_error_response { + queue_sender_delivery_receipt(&mut tr, from_user_id, &message.receipt_id).await?; + } + + tr.commit().await?; + + database.notify_committed([ + "received_receipts", + "receipts", + "messages", + "groups", + "contacts", + ]); + + let ctx = ctx.clone(); + tokio::spawn(async move { + if let Err(error) = retransmit_queued_receipts(&ctx).await { + tracing::warn!("failed to flush messages queued during inbound handling: {error}"); + } + }); + + Ok(()) +} diff --git a/rust/src/api/messages/mod.rs b/rust/src/api/messages/mod.rs new file mode 100644 index 00000000..51a001d1 --- /dev/null +++ b/rust/src/api/messages/mod.rs @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +pub(crate) mod incoming; +#[doc(hidden)] +pub mod outgoing; + +pub(crate) fn content_type_kind( + content: &crate::api::proto::client::EncryptedContent, +) -> &'static str { + if content.message_update.is_some() { + "MessageUpdate" + } else if content.media.is_some() { + "Media" + } else if content.media_update.is_some() { + "MediaUpdate" + } else if content.contact_update.is_some() { + "ContactUpdate" + } else if content.contact_request.is_some() { + "ContactRequest" + } else if content.flame_sync.is_some() { + "FlameSync" + } else if content.push_keys.is_some() { + "PushKeys" + } else if content.reaction.is_some() { + "Reaction" + } else if content.text_message.is_some() { + "TextMessage" + } else if content.group_create.is_some() { + "GroupCreate" + } else if content.group_join.is_some() { + "GroupJoin" + } else if content.group_update.is_some() { + "GroupUpdate" + } else if content.resend_group_public_key.is_some() { + "ResendGroupPublicKey" + } else if content.error_messages.is_some() { + "ErrorMessages" + } else if content.additional_data_message.is_some() { + "AdditionalDataMessage" + } else if content.typing_indicator.is_some() { + "TypingIndicator" + } else if content.user_discovery_request.is_some() { + "UserDiscoveryRequest" + } else if content.user_discovery_update.is_some() { + "UserDiscoveryUpdate" + } else if content.key_verification_proof.is_some() { + "KeyVerificationProof" + } else if content.passwordless_recovery.is_some() { + "PasswordLessRecovery" + } else if content.passwordless_recovery_heartbeat.is_some() { + "PasswordLessRecoveryHeartbeat" + } else { + "Unknown" + } +} diff --git a/rust/src/api/messages/outgoing.rs b/rust/src/api/messages/outgoing.rs new file mode 100644 index 00000000..5e261f9b --- /dev/null +++ b/rust/src/api/messages/outgoing.rs @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::messages::content_type_kind; +use crate::api::messages::incoming::client2client::messages; +use crate::api::proto::client as proto; +use crate::context::Context; +use crate::error::Result; +use crate::user_config::UserConfig; +use prost::Message as _; +use std::sync::Arc; + +async fn decorate_content( + ctx: &Context, + contact_id: i64, + content: &mut proto::EncryptedContent, + is_persisted_message: bool, +) -> Result<()> { + let Some(config) = UserConfig::load_from(ctx)? else { + return Ok(()); + }; + + content.sender_profile_counter = Some(config.avatar_counter); + + if config.ask_for_friend_promotions { + let database = ctx.get_app_database().await; + let accepted = sqlx::query_scalar!("SELECT COUNT(*) FROM contacts WHERE accepted = 1") + .fetch_one(&database.pool) + .await?; + if accepted <= 5 { + content.ask_for_friend_promotions = Some(true); + } + } + + if config.is_user_discovery_enabled && is_persisted_message { + let database = ctx.get_app_database().await; + let allowed = sqlx::query_scalar!( + r#"SELECT EXISTS(SELECT 1 FROM contacts WHERE user_id = ? AND accepted = 1 + AND blocked = 0 AND media_send_counter >= ? AND user_discovery_excluded = 0 + AND (? = 0 OR user_discovery_manual_approved = 1))"#, + contact_id, + config.required_send_images, + config.user_discovery_requires_manual_approval, + ) + .fetch_one(&database.pool) + .await?; + if allowed != 0 { + content.sender_user_discovery_version = Some( + ctx.get_user_discovery() + .get() + .await + .get_current_version() + .await?, + ); + } + } + Ok(()) +} + +#[bon::builder] +pub async fn send_c2c_message_to_contact( + ctx: &Arc, + contact_id: i64, + encrypted_content: Vec, + message_id: Option, + #[builder(default)] only_send_if_no_receipts_are_open: bool, + #[builder(default)] only_return_encrypted_data: bool, + #[builder(default)] blocking: bool, +) -> Result, Option>)>> { + let mut content = proto::EncryptedContent::decode(encrypted_content.as_slice())?; + + decorate_content(ctx, contact_id, &mut content, message_id.is_some()).await?; + + let db_app = ctx.get_app_database().await; + let mut t = db_app.pool.begin().await?; + + if only_send_if_no_receipts_are_open { + let count = sqlx::query_scalar!( + "SELECT COUNT(*) FROM receipts WHERE contact_id = ?", + contact_id + ) + .fetch_one(&mut *t) + .await?; + if count > 10 { + return Ok(None); + } + } + + let mut retry_count = 0_i64; + let mut last_retry = None; + if let Some(message_id) = &message_id { + let previous = sqlx::query!( + r#"SELECT COUNT(*) AS "count!: i64", MAX(last_retry) AS last_retry + FROM receipts WHERE contact_id = ? AND message_id = ?"#, + contact_id, + message_id, + ) + .fetch_one(&mut *t) + .await?; + retry_count = previous.count; + last_retry = previous.last_retry; + sqlx::query!( + "DELETE FROM receipts WHERE contact_id = ? AND message_id = ?", + contact_id, + message_id + ) + .execute(&mut *t) + .await?; + } + + let type_kind = content_type_kind(&content); + let receipt_id = messages::queue_encrypted_content(&mut t, contact_id, content, true).await?; + + tracing::info!( + contact_id, + ?message_id, + receipt_id, + type_kind, + "queued c2c message to contact" + ); + + sqlx::query!( + r#"UPDATE receipts SET message_id = ?, will_be_retried_by_media_upload = ?, + retry_count = ?, last_retry = ? WHERE receipt_id = ?"#, + message_id, + only_return_encrypted_data, + retry_count, + last_retry, + receipt_id + ) + .execute(&mut *t) + .await?; + + t.commit().await?; + db_app.notify_committed(["receipts"]); + + if only_return_encrypted_data { + return messages::prepare_queued_receipt(ctx, &receipt_id).await; + } + + if blocking { + tracing::info!(receipt_id, "sending c2c message synchronously"); + messages::send_queued_receipt(ctx, &receipt_id).await?; + } else { + tracing::info!(receipt_id, "sending c2c message asynchronously"); + let queued_receipt_id = receipt_id.clone(); + let ctx = ctx.clone(); + tokio::spawn(async move { + if let Err(error) = messages::send_queued_receipt(&ctx, &queued_receipt_id).await { + tracing::warn!( + receipt_id = queued_receipt_id, + "queued message send failed: {error}" + ); + } + }); + } + Ok(None) +} diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs new file mode 100644 index 00000000..c77a95a7 --- /dev/null +++ b/rust/src/api/mod.rs @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +pub(crate) mod groups; +pub mod messages; +pub mod proto; +pub(super) mod runtime; +pub(super) mod server; + +#[doc(hidden)] +pub use runtime::ApiRuntime; +pub use server::{prekeys::PqcPreKeyInput, Server}; diff --git a/rust/src/api/proto/api/http/http_requests.proto b/rust/src/api/proto/api/http/http_requests.proto new file mode 100644 index 00000000..5b786280 --- /dev/null +++ b/rust/src/api/proto/api/http/http_requests.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; +package http_requests; + +message UpdateGroupState { + message UpdateTBS { + uint64 version_id = 1; + bytes encrypted_group_state = 3; + bytes public_key = 4; + optional bytes remove_admin = 5; + optional bytes add_admin = 6; + bytes nonce = 7; + } + UpdateTBS update = 1; + bytes signature = 2; +} + +message NewGroupState { + string group_id = 1; + uint64 version_id = 2; + bytes encrypted_group_state = 3; + bytes public_key = 4; +} + +message AppendGroupState { + message AppendTBS { + bytes encrypted_group_state_append = 1; + bytes public_key = 2; + string group_id = 3; + bytes nonce = 4; + } + bytes signature = 1; + AppendTBS append_tbs = 2; + uint64 version_id = 3; +} + +message GroupState { + uint64 version_id = 1; + bytes encrypted_group_state = 2; + repeated AppendGroupState appended_group_states = 3; +} diff --git a/rust/src/api/proto/api/websocket/client_to_server.proto b/rust/src/api/proto/api/websocket/client_to_server.proto new file mode 100644 index 00000000..ebdb8e69 --- /dev/null +++ b/rust/src/api/proto/api/websocket/client_to_server.proto @@ -0,0 +1,296 @@ +syntax = "proto3"; +package client_to_server; +import "api/websocket/error.proto"; + +message ClientToServer { + oneof v { + V0 V0 = 1; + } +} + +message V0 { + uint64 seq = 1; + oneof Kind { + Handshake handshake = 2; + ApplicationData applicationdata = 3; + Response response = 4; + } +} + +message Handshake { + + message RequestPOW {} + + message Register { + string username = 1; + optional string invite_code = 2; + bytes public_identity_key = 3; + bytes signed_prekey = 4; + bytes signed_prekey_signature = 5; + int64 signed_prekey_id = 6; + int64 registration_id = 7; + bool is_ios = 8; + string lang_code = 9; + int64 proof_of_work = 10; + optional bytes login_token = 11; + } + + message GetAuthChallenge {} + + message GetUserIdByUsername { + string username = 1; + } + + message GetAuthToken { + int64 user_id = 1; + bytes response = 2; + } + + message Authenticate { + int64 user_id = 1; + bytes auth_token = 2; + optional string app_version = 3; + optional int64 device_id = 4; + optional bool in_background = 5; + } + + message AuthenticateWithLoginToken { + int64 user_id = 1; + bytes secret_login_token = 2; + string app_version = 3; + int64 device_id = 4; + bool in_background = 5; + } + + message GetServerKeyForPasswordLessRecovery { + int64 user_id = 1; + bytes server_key_protection = 2; + optional bytes pin_unlock_token = 3; + optional bytes pin_protection_key = 4; + optional string email = 5; + } + + message RegisterPasswordlessNotification { + string notification_id = 1; + bytes download_auth_token = 2; + string lang_code = 3; + optional string google_fcm = 4; + } + + message CheckForPasswordlessNotification { + string notification_id = 1; + bytes download_auth_token = 2; + repeated int64 already_received_message_ids = 3; + } + + oneof Handshake { + Register register = 1; + GetAuthChallenge getAuthChallenge = 2; + GetAuthToken getAuthToken = 3; + Authenticate authenticate = 4; + RequestPOW requestPOW = 5; + AuthenticateWithLoginToken authenticate_with_login_token = 6; + GetUserIdByUsername get_userid_by_username = 7; + GetServerKeyForPasswordLessRecovery get_server_key_for_passwordless_recovery = 8; + RegisterPasswordlessNotification register_passwordless_notification = 9; + CheckForPasswordlessNotification check_for_passwordless_notification = 10; + } +} + +message ApplicationData { + reserved 9, 11, 13 to 17, 19; + + message TextMessage { + int64 user_id = 1; + bytes body = 3; + optional bytes push_data = 4; + } + + message GetUserByUsername { + string username = 1; + } + + message ChangeUsername { + string username = 1; + } + + message UpdateGoogleFcmToken { + string google_fcm = 1; + } + + message GetUserById { + int64 user_id = 1; + } + + message GetAvailablePlans {} + message GetCurrentPlanInfos {} + + message RemoveAdditionalUser { + int64 user_id = 1; + } + + message GetPrekeysByUserId { + int64 user_id = 1; + } + + message GetSignedPreKeyByUserId { + int64 user_id = 1; + } + + message UpdateSignedPreKey { + int64 signed_prekey_id = 1; + bytes signed_prekey = 2; + bytes signed_prekey_signature = 3; + } + + message PqcPreKey { + int64 ecc_pre_key_id = 1; + bytes ecc_pre_key = 2; + int64 kyber_pre_key_id = 3; + bytes kyber_pre_key = 4; + bytes kyber_pre_key_signature = 5; + } + + message UploadPqcPreKeys { + int64 ecc_signed_prekey_id = 1; + bytes ecc_signed_prekey = 2; + bytes ecc_signed_prekey_signature = 3; + int64 kyber_signed_prekey_id = 4; + bytes kyber_signed_prekey = 5; + bytes kyber_signed_prekey_signature = 6; + repeated PqcPreKey prekeys = 7; + } + + message DownloadDone { + bytes download_token = 1; + } + + message ReportUser { + int64 reported_user_id = 1; + string reason = 2; + } + + message IPAPurchase { + string product_id = 1; + string source = 2; + string verification_data = 3; + } + + message IPAForceCheck {} + + message DeleteAccount {} + + message AddAdditionalUser { + int64 user_id = 1; + } + + message SetLoginToken { + bytes login_token = 1; + } + + message RegisterPasswordLessRecovery { + bytes encryptedServerKey = 1; + optional bytes pinUnlockToken = 2; + } + + message PasswordlessNotification { + string notification_id = 1; + bytes encrypted_message = 2; + } + + message RequestMemoriesUpload { + int64 size = 1; + int64 original_date = 2; + string media_id = 3; + } + + message ConfirmMemoriesUpload { + string media_id = 1; + } + + message GetMemoriesList { + int64 offset_date = 1; + int64 limit = 2; + } + + message GetMemoriesUrl { + string media_id = 1; + bool thumbnail = 2; + } + + message GetMemoriesUsage {} + + message DeleteMemory { + string media_id = 1; + } + + message DisableMemoriesBackup {} + + message GetPrivacyPassParameters {} + + message IssuePrivacyPassTokens { + repeated bytes token_requests = 1; + } + + oneof ApplicationData { + TextMessage textMessage = 1; + GetUserByUsername getUserByUsername = 2; + GetPrekeysByUserId getPrekeysByUserId = 3; + GetUserById getUserById = 6; + UpdateGoogleFcmToken updateGoogleFcmToken = 8; + GetCurrentPlanInfos getCurrentPlanInfos = 10; + GetAvailablePlans getAvailablePlans = 12; + DownloadDone downloadDone = 20; + GetSignedPreKeyByUserId getSignedPrekeyByUserid = 22; + UpdateSignedPreKey updateSignedPrekey = 23; + DeleteAccount deleteAccount = 24; + ReportUser reportUser = 25; + ChangeUsername changeUsername = 26; + IPAPurchase ipaPurchase = 27; + IPAForceCheck ipaForceCheck = 28; + RemoveAdditionalUser removeAdditionalUser = 18; + AddAdditionalUser addAdditionalUser = 29; + SetLoginToken set_login_token = 30; + RegisterPasswordLessRecovery register_passwordless_recovery = 31; + PasswordlessNotification passwordless_notification = 32; + RequestMemoriesUpload request_memories_upload = 33; + ConfirmMemoriesUpload confirm_memories_upload = 34; + GetMemoriesList get_memories_list = 35; + GetMemoriesUrl get_memories_url = 36; + GetMemoriesUsage get_memories_usage = 37; + DeleteMemory delete_memory = 38; + DisableMemoriesBackup disable_memories_backup = 39; + UploadPqcPreKeys upload_pqc_prekeys = 40; + GetPrivacyPassParameters get_privacy_pass_parameters = 41; + IssuePrivacyPassTokens issue_privacy_pass_tokens = 42; + } +} + +message Response { + + message PreKey { + int64 id = 1; + bytes prekey = 2; + } + + message Prekeys { + repeated PreKey prekeys = 1; + } + + message PqcPrekeys { + repeated ApplicationData.PqcPreKey prekeys = 1; + } + + message Ok { + oneof Ok { + bool None = 1; + Prekeys prekeys = 2; + PqcPrekeys prekeys_pqc = 3; + } + } + + oneof Response { + Ok ok = 1; + error.ErrorCode error = 2; + } +} diff --git a/rust/src/api/proto/api/websocket/error.proto b/rust/src/api/proto/api/websocket/error.proto new file mode 100644 index 00000000..edc2256d --- /dev/null +++ b/rust/src/api/proto/api/websocket/error.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package error; + +enum ErrorCode { + Unknown = 0; + BadRequest = 400; + TooManyRequests = 429; + InternalError = 500; + + InvalidInvitationCode = 1002; + UsernameAlreadyTaken = 1003; + SignatureNotValid = 1004; + UsernameNotFound = 1005; + UsernameNotValid = 1006; + InvalidPublicKey = 1007; + SessionAlreadyAuthenticated = 1008; + SessionNotAuthenticated = 1009; + OnlyOneSessionAllowed = 1010; + UploadLimitReached = 1011; + InvalidUpdateToken = 1012; + InvalidOffset = 1013; + InvalidGoogleFcmToken = 1014; + UploadTokenIsBlocked = 1015; + UploadChecksumInvalid = 1016; + InvalidDownloadToken = 1017; + ApiEndpointNotFound = 1018; + AuthTokenNotValid = 1019; + InvalidPreKeys = 1020; + VoucherInValid = 1021; + PlanNotAllowed = 1022; + PlanLimitReached = 1023; + NotEnoughCredit = 1024; + PlanDowngrade = 1025; + PlanUpgradeNotYearly = 1026; + InvalidSignedPreKey = 1027; + UserIdNotFound= 1028; + UserIdAlreadyTaken = 1029; + AppVersionOutdated = 1030; + NewDeviceRegistered = 1031; + InvalidProofOfWork = 1032; + RegistrationDisabled = 1033; + IPAPaymentExpired = 1034; + UserIsNotInFreePlan = 1035; + ForegroundSessionConnected = 1036; + NoRecoveryData = 1037; + InvalidRecoveryData = 1038; +} diff --git a/rust/src/api/proto/api/websocket/server_to_client.proto b/rust/src/api/proto/api/websocket/server_to_client.proto new file mode 100644 index 00000000..985b97b6 --- /dev/null +++ b/rust/src/api/proto/api/websocket/server_to_client.proto @@ -0,0 +1,232 @@ + +syntax = "proto3"; +package server_to_client; +import "api/websocket/error.proto"; + +message ServerToClient { + oneof v { + V0 V0 = 1; + } +} + +message V0 { + uint64 seq = 1; + oneof Kind { + Response response = 2; + NewMessage newMessage = 3; + NewMessages newMessages = 7; + bool RequestNewPreKeys = 4; + bool RequestNewPqcPreKeys = 8; + error.ErrorCode error = 6; + SealedSenderMessage sealedSenderMessage = 9; + SealedSenderMessages sealedSenderMessages = 10; + } +} + +message NewMessage { + int64 from_user_id = 2; + bytes body = 1; +} + +message NewMessages { + repeated NewMessage newMessages = 1; +} + +message SealedSenderMessage { + string message_id = 1; + bytes body = 2; +} + +message SealedSenderMessages { + repeated SealedSenderMessage messages = 1; +} + +message Response { + + message Authenticated { + string plan = 1; + } + + message Plan { + string plan_id = 1; + int64 upload_size_limit = 2; + int64 daily_media_upload_limit = 3; + int64 maximal_upload_size_of_single_media_size = 4; + int64 additional_plus_accounts = 5; + int64 monthly_costs_cent = 7; + int64 yearly_costs_cent = 8; + bool allowed_to_send_text_messages = 9; + bool is_additional_account = 10; + int64 memories_size_limit = 11; + } + + message Plans { + repeated Plan plans = 1; + } + + message AddAccountsInvite { + string plan_id = 1; + string invite_code = 2; + } + + message AddAccountsInvites { + repeated AddAccountsInvite invites = 1; + } + + message AdditionalAccount { + int64 user_id = 1; + string plan_id = 3; + } + + message Transaction {} + + message PlanBallance { + int64 used_daily_media_upload_limit = 1; + int64 used_upload_media_size_limit = 2; + optional int64 payment_period_days = 3; + optional int64 last_payment_done_unix_timestamp = 4; + repeated Transaction transactions = 5; + repeated AdditionalAccount additional_accounts = 6; + optional bool auto_renewal = 7; + optional int64 additional_account_owner_id = 8; + } + message PreKey { + int64 id = 1; + bytes prekey = 2; + } + + message SignedPreKey { + int64 signed_prekey_id = 1; + bytes signed_prekey = 2; + bytes signed_prekey_signature = 3; + } + + message PqcPreKey { + int64 ecc_pre_key_id = 1; + bytes ecc_pre_key = 2; + int64 kyber_pre_key_id = 3; + bytes kyber_pre_key = 4; + bytes kyber_pre_key_signature = 5; + } + + message PqcBundle { + int64 ecc_signed_prekey_id = 1; + bytes ecc_signed_prekey = 2; + bytes ecc_signed_prekey_signature = 3; + int64 kyber_signed_prekey_id = 4; + bytes kyber_signed_prekey = 5; + bytes kyber_signed_prekey_signature = 6; + optional PqcPreKey prekey = 7; + } + + message UserData { + int64 user_id = 1; + repeated PreKey prekeys = 2; + optional bytes username = 7; + optional bytes public_identity_key = 3; + optional bytes signed_prekey = 4; + optional bytes signed_prekey_signature = 5; + optional int64 signed_prekey_id = 6; + optional int64 registration_id = 8; + optional PqcBundle pqc_bundle = 9; + } + + message UploadToken { + bytes upload_token = 1; + repeated bytes download_tokens = 2; + } + + message DownloadTokens { + repeated bytes download_tokens = 1; + } + + message ProofOfWork { + string prefix = 1; + int64 difficulty = 2; + } + + message PasswordlessNotificationMessage { + int64 id = 1; + bytes encrypted_message = 2; + } + + message PasswordlessNotificationMessages { + repeated PasswordlessNotificationMessage messages = 1; + } + + message PresignedPost { + string url = 1; + map fields = 2; + } + + message MemoriesUploadUrls { + string media_id = 1; + PresignedPost thumbnail_upload = 2; + PresignedPost full_upload = 3; + } + + message MediaItem { + string media_id = 1; + int64 original_date = 2; + string thumbnail_download_url = 3; + } + + message MemoriesList { + repeated MediaItem items = 1; + } + + message MemoriesUrl { + string full_download_url = 1; + } + + message MemoriesUsage { + int64 max_bytes = 1; + int64 current_bytes = 2; + int64 count = 3; + } + + message PrivacyPassParameters { + bytes token_challenge = 1; + bytes public_key = 2; + uint32 max_batch_size = 3; + uint32 max_age_seconds = 4; + uint32 daily_token_limit = 5; + uint32 issuance_cooldown_seconds = 6; + } + + message PrivacyPassTokenResponses { + repeated bytes token_responses = 1; + } + + message Ok { + reserved 7, 11; + oneof Ok { + bool None = 1; + int64 userid = 2; + bytes authchallenge = 3; + UploadToken uploadtoken = 4; + UserData userdata = 5; + bytes authtoken = 6; + Authenticated authenticated = 8; + Plans plans = 9; + PlanBallance planballance = 10; + AddAccountsInvites addaccountsinvites = 12; + DownloadTokens downloadtokens = 13; + SignedPreKey signedprekey = 14; + ProofOfWork proofOfWork = 15; + bytes passwordless_recovery_server_key = 16; + PasswordlessNotificationMessages passwordless_notification_messages = 17; + MemoriesUploadUrls memories_upload_urls = 18; + MemoriesList memories_list = 19; + MemoriesUrl memories_url = 20; + MemoriesUsage memories_usage = 21; + PrivacyPassParameters privacy_pass_parameters = 22; + PrivacyPassTokenResponses privacy_pass_token_responses = 23; + } + } + + oneof Response { + Ok ok = 1; + error.ErrorCode error = 2; + } +} diff --git a/rust/src/api/proto/mod.rs b/rust/src/api/proto/mod.rs new file mode 100644 index 00000000..7c28d54a --- /dev/null +++ b/rust/src/api/proto/mod.rs @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +#![allow(clippy::derive_partial_eq_without_eq)] + +include!(concat!(env!("OUT_DIR"), "/websocket_protocol.rs")); + +pub mod client { + include!(concat!(env!("OUT_DIR"), "/client_messages.rs")); +} + +pub mod http_requests { + include!(concat!(env!("OUT_DIR"), "/http_requests.rs")); +} diff --git a/rust/src/api/runtime.rs b/rust/src/api/runtime.rs new file mode 100644 index 00000000..ae2b51df --- /dev/null +++ b/rust/src/api/runtime.rs @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::bridge::api::{ApiConfig, ApiConnectionState, ApiEvent, ApiEventKind}; +use crate::context::Context; +use crate::error::{Result, TwonlyError}; +use crate::frb_generated::StreamSink; +use std::sync::Arc; +use tokio::sync::broadcast; + +pub(crate) mod auth; +pub(crate) mod client; +pub(crate) mod helpers; +pub(crate) mod request; + +pub(crate) use client::{ApiClient, API_EVENTS}; +use helpers::*; + +#[doc(hidden)] +pub struct ApiRuntime {} + +impl ApiRuntime { + pub(crate) async fn initialize(ctx: &Arc) -> Result<()> { + let config = ApiConfig::from_rust_state(ctx).await?; + ctx.get_api_client() + .set(tokio::sync::RwLock::new(ApiClient::new(ctx, config))) + .map_err(|_| TwonlyError::Initialization) + } + + pub async fn reload_configuration(ctx: &Arc) -> Result<()> { + let replacement = ApiClient::new(ctx, ApiConfig::from_rust_state(ctx).await?); + let current = Self::client(ctx).await?; + let slot = ctx + .get_api_client() + .get() + .ok_or(TwonlyError::Initialization)?; + *slot.write().await = replacement; + current.close().await; + Ok(()) + } + + pub async fn connect(ctx: &Arc) -> Result<()> { + Self::client(ctx).await?.connect().await + } + + pub async fn close(ctx: &Arc) -> Result<()> { + Self::client(ctx).await?.close().await; + Ok(()) + } + + pub async fn connection_state(ctx: &Arc) -> Result { + Ok(*Self::client(ctx).await?.state.read().await) + } + + pub async fn set_background(ctx: &Arc, in_background: bool) -> Result<()> { + Self::client(ctx).await?.set_background(in_background).await + } + + pub async fn set_network_available(ctx: &Arc, available: bool) -> Result<()> { + Self::client(ctx) + .await? + .set_network_available(available) + .await + } + + pub async fn send_binary(ctx: &Arc, bytes: Vec) -> Result<()> { + Self::client(ctx).await?.send(bytes).await + } + + pub async fn request_binary(ctx: &Arc, bytes: Vec) -> Result> { + let client = Self::client(ctx).await?; + if client.ws_client.lock().await.is_none() { + client.connect().await?; + } + client.request(bytes).await + } + + pub async fn request_authenticated( + ctx: &Arc, + bytes: Vec, + contact_id: Option, + ) -> Result> { + let client = Self::client(ctx).await?; + if client.ws_client.lock().await.is_none() { + client.connect().await?; + } + let response = client.request(bytes.clone()).await?; + match response_error_code(&response)? { + None => { + client.handle_success_metadata(&response).await?; + Ok(response) + } + Some(code) + if code == crate::api::proto::error::ErrorCode::SessionNotAuthenticated as i32 => + { + client.set_state(ApiConnectionState::Connected).await; + + let retried = client.request(bytes).await?; + if let Some(code) = response_error_code(&retried)? { + client.handle_api_error(code, contact_id).await?; + return Ok(retried); + } + client.handle_success_metadata(&retried).await?; + Ok(retried) + } + Some(code) => { + client.handle_api_error(code, contact_id).await?; + Ok(response) + } + } + } + + pub async fn request_durable_authenticated( + ctx: &Arc, + bytes: Vec, + operation_kind: &str, + ) -> Result> { + let client = Self::client(ctx).await?; + if client.ws_client.lock().await.is_none() { + client.connect().await?; + } + let response = client.request_durable(bytes, operation_kind).await?; + if let Some(code) = response_error_code(&response)? { + client.handle_api_error(code, None).await?; + } + Ok(response) + } + + pub(crate) async fn replay_outbox(ctx: &Arc) -> Result<()> { + let database = ctx.get_app_database().await; + let rows = sqlx::query!("SELECT sequence_id, payload FROM api_outbox ORDER BY created_at") + .fetch_all(&database.pool) + .await?; + for row in rows { + match Self::request_authenticated(ctx, row.payload, None).await { + Ok(_) => { + sqlx::query!( + "DELETE FROM api_outbox WHERE sequence_id = ?", + row.sequence_id + ) + .execute(&database.pool) + .await?; + } + Err(error) => { + tracing::warn!(sequence = row.sequence_id, "outbox replay failed: {error}") + } + } + } + database.notify_committed(["api_outbox"]); + Ok(()) + } + + pub(crate) async fn replay_legacy_raw_outbox(ctx: &Arc) -> Result<()> { + use base64::Engine as _; + let path = std::path::Path::new(&ctx.get_config()?.data_dir) + .join("keyvalue") + .join("rawbytes-to-retransmit.json"); + if !path.exists() { + return Ok(()); + } + let value: serde_json::Value = serde_json::from_slice(&std::fs::read(&path)?) + .map_err(|error| TwonlyError::Generic(format!("invalid legacy outbox: {error}")))?; + let Some(messages) = value.as_object() else { + return Err(TwonlyError::Generic( + "legacy outbox is not an object".into(), + )); + }; + for encoded in messages.values() { + let encoded = encoded.as_str().ok_or_else(|| { + TwonlyError::Generic("legacy outbox payload is not a string".into()) + })?; + let bytes = base64::engine::general_purpose::STANDARD + .decode(encoded) + .map_err(|error| { + TwonlyError::Generic(format!("invalid legacy outbox payload: {error}")) + })?; + Self::request_authenticated(ctx, bytes, None).await?; + } + std::fs::remove_file(path)?; + Ok(()) + } + + pub async fn allocate_sequence(ctx: &Arc) -> Result { + Ok(Self::client(ctx).await?.next_sequence().await) + } + + pub async fn events(ctx: &Arc, sink: StreamSink) -> Result<()> { + let mut receiver = API_EVENTS.subscribe(); + + // The Dart stream is created synchronously, but this async FFI call may + // not have installed its broadcast receiver before Dart starts the API + // connection. Send a snapshot after subscribing so Dart can use it as + // a readiness handshake and no connection events are lost at startup. + let state = Self::connection_state(ctx).await?; + if sink + .add(ApiEvent { + kind: ApiEventKind::ConnectionStateChanged, + state: Some(state), + message: None, + }) + .is_err() + { + return Ok(()); + } + + tokio::spawn(async move { + loop { + match receiver.recv().await { + Ok(event) => { + if sink.add(event).is_err() { + break; + } + } + Err(broadcast::error::RecvError::Lagged(skipped)) => { + let _ = sink.add(ApiEvent { + kind: ApiEventKind::TransportError, + state: None, + message: Some(format!("API event consumer lagged by {skipped} events")), + }); + } + Err(broadcast::error::RecvError::Closed) => break, + } + } + }); + Ok(()) + } + + pub(crate) async fn client(ctx: &Arc) -> Result> { + let client = ctx + .get_api_client() + .get() + .ok_or(TwonlyError::Initialization)?; + Ok(client.read().await.clone()) + } +} diff --git a/rust/src/api/runtime/auth.rs b/rust/src/api/runtime/auth.rs new file mode 100644 index 00000000..5c4a5984 --- /dev/null +++ b/rust/src/api/runtime/auth.rs @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::client::ApiClient; +use super::helpers::{decode_ok, schedule_post_authentication}; +use crate::api::proto::{client_to_server, server_to_client}; +use crate::bridge::api::{ApiConnectionState, ApiEvent, ApiEventKind, ServerResult}; +use crate::context::Context; +use crate::error::{Result, TwonlyError}; +use crate::user_config::UserConfig; +use async_trait::async_trait; +use prost::Message; +use rand::SeedableRng; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Weak}; +use stream_tungstenite::context::ConnectionContext; +use stream_tungstenite::error::HandshakeError; +use stream_tungstenite::handshake::{HandshakeReceiver, HandshakeSender, Handshaker}; +use stream_tungstenite::tokio_tungstenite::tungstenite; +use tokio::sync::broadcast; + +pub(crate) struct ApiAuthHandshaker { + pub context: Arc, + pub api_client: Weak, + pub is_authenticated: Arc, + pub can_use_login_token_for_auth: bool, + pub legacy_user_app_version: i64, + pub in_background: bool, + pub events: broadcast::Sender, +} + +impl ApiAuthHandshaker { + async fn get_identity(&self) -> Result<(Option, i64, String)> { + let user = UserConfig::load_from(&self.context)? + .ok_or_else(|| TwonlyError::Generic("User configuration not found".into()))?; + let device_id = user.device_id; + let app_version = user.app_version.to_string(); + Ok((user.user_id, device_id, app_version)) + } + + async fn request_handshake( + &self, + sender: &mut dyn HandshakeSender, + receiver: &mut dyn HandshakeReceiver, + handshake: client_to_server::handshake::Handshake, + ) -> std::result::Result { + let request = client_to_server::ClientToServer { + v: Some(client_to_server::client_to_server::V::V0( + client_to_server::V0 { + seq: 0, + kind: Some(client_to_server::v0::Kind::Handshake( + client_to_server::Handshake { + handshake: Some(handshake), + }, + )), + }, + )), + }; + + sender + .send_msg(tungstenite::Message::Binary(request.encode_to_vec().into())) + .await?; + + let msg = receiver + .recv_msg() + .await? + .ok_or_else(|| HandshakeError::Protocol("Connection closed during handshake".into()))?; + + let bytes = match msg { + tungstenite::Message::Binary(b) => b, + _ => return Err(HandshakeError::Protocol("Expected binary message".into())), + }; + + let ok_res = + decode_ok(bytes.to_vec()).map_err(|e| HandshakeError::Protocol(e.to_string()))?; + let ok = match ok_res { + ServerResult::Ok(val) => val, + ServerResult::ErrorCode(code) => { + return Err(HandshakeError::Protocol(format!( + "Server returned error code: {}", + code + ))); + } + }; + + if let server_to_client::response::ok::Ok::Authenticated(authenticated) = &ok { + let _ = self.events.send(ApiEvent { + kind: ApiEventKind::PlanUpdated, + state: None, + message: Some(authenticated.plan.clone()), + }); + } + Ok(ok) + } + + async fn authenticate_with_login_token( + &self, + sender: &mut dyn HandshakeSender, + receiver: &mut dyn HandshakeReceiver, + user_id: i64, + ) -> Result<()> { + let login_token = self + .context + .get_key_manager() + .await? + .main_key + .get_login_token() + .to_vec(); + let (_, device_id, app_version) = self.get_identity().await?; + let handshake = client_to_server::handshake::Handshake::AuthenticateWithLoginToken( + client_to_server::handshake::AuthenticateWithLoginToken { + user_id, + secret_login_token: login_token, + app_version, + device_id, + in_background: self.in_background, + }, + ); + self.request_handshake(sender, receiver, handshake) + .await + .map(|_| ()) + .map_err(|e| TwonlyError::Generic(e.to_string())) + } + + async fn authenticate_with_legacy_token( + &self, + sender: &mut dyn HandshakeSender, + receiver: &mut dyn HandshakeReceiver, + user_id: i64, + ) -> Result { + use base64::Engine as _; + let Some(encoded) = self.context.get_secure_storage().read("api_auth_token")? else { + return Ok(false); + }; + let auth_token = base64::engine::general_purpose::STANDARD + .decode(encoded) + .map_err(|error| TwonlyError::Generic(format!("invalid stored auth token: {error}")))?; + let (_, device_id, app_version) = self.get_identity().await?; + let handshake = client_to_server::handshake::Handshake::Authenticate( + client_to_server::handshake::Authenticate { + user_id, + auth_token, + app_version: Some(app_version), + device_id: Some(device_id), + in_background: Some(self.in_background), + }, + ); + match self.request_handshake(sender, receiver, handshake).await { + Ok(_) => Ok(true), + Err(error) => { + if error.to_string().contains("AuthTokenNotValid") { + // simple approximation + Ok(false) + } else { + Err(TwonlyError::Generic(error.to_string())) + } + } + } + } + + async fn obtain_legacy_auth_token( + &self, + sender: &mut dyn HandshakeSender, + receiver: &mut dyn HandshakeReceiver, + user_id: i64, + ) -> Result<()> { + use base64::Engine as _; + use libsignal_protocol::IdentityKeyPair; + + let challenge = self + .request_handshake( + sender, + receiver, + client_to_server::handshake::Handshake::GetAuthChallenge( + client_to_server::handshake::GetAuthChallenge {}, + ), + ) + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?; + let server_to_client::response::ok::Ok::Authchallenge(challenge) = challenge else { + return Err(TwonlyError::Generic( + "auth challenge response has unexpected payload".into(), + )); + }; + let serialized_identity = self + .context + .get_key_manager() + .await? + .signal_identity + .as_ref() + .ok_or(TwonlyError::SignalIdentityNotFound)? + .identity_key_pair_structure + .clone(); + let identity = IdentityKeyPair::try_from(serialized_identity.as_slice()) + .map_err(|error| TwonlyError::Signal(error.to_string()))?; + let mut rng = rand::rngs::StdRng::from_os_rng(); + let signature = identity + .private_key() + .calculate_signature_for_multipart_message(&[challenge.as_slice()], &mut rng) + .map_err(|error| TwonlyError::Signal(error.to_string()))?; + let token = self + .request_handshake( + sender, + receiver, + client_to_server::handshake::Handshake::GetAuthToken( + client_to_server::handshake::GetAuthToken { + user_id, + response: signature.to_vec(), + }, + ), + ) + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?; + let server_to_client::response::ok::Ok::Authtoken(token) = token else { + return Err(TwonlyError::Generic( + "auth-token response has unexpected payload".into(), + )); + }; + self.context.get_secure_storage().write( + "api_auth_token", + &base64::engine::general_purpose::STANDARD.encode(token), + )?; + Ok(()) + } + + async fn migrate_to_login_token( + &self, + sender: &mut dyn HandshakeSender, + receiver: &mut dyn HandshakeReceiver, + ) -> Result<()> { + let token = self + .context + .get_key_manager() + .await? + .main_key + .get_login_token() + .to_vec(); + let application = client_to_server::application_data::ApplicationData::SetLoginToken( + client_to_server::application_data::SetLoginToken { login_token: token }, + ); + let request = client_to_server::ClientToServer { + v: Some(client_to_server::client_to_server::V::V0( + client_to_server::V0 { + seq: 0, + kind: Some(client_to_server::v0::Kind::Applicationdata( + client_to_server::ApplicationData { + application_data: Some(application), + }, + )), + }, + )), + }; + sender + .send_msg(tungstenite::Message::Binary(request.encode_to_vec().into())) + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?; + let msg = receiver + .recv_msg() + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))? + .unwrap(); + let bytes = match msg { + tungstenite::Message::Binary(b) => b, + _ => return Err(TwonlyError::Generic("Expected binary message".into())), + }; + match decode_ok(bytes.to_vec())? { + ServerResult::Ok(_) => {} + ServerResult::ErrorCode(code) => return Err(TwonlyError::Api(code)), + } + self.context.get_secure_storage().delete("api_auth_token")?; + let _ = self.events.send(ApiEvent { + kind: ApiEventKind::LoginTokenMigrated, + state: None, + message: None, + }); + + Ok(()) + } + + fn authentication_succeeded(&self) { + self.is_authenticated.store(true, Ordering::Release); + + if let Some(client) = self.api_client.upgrade() { + tokio::spawn(async move { + client.set_state(ApiConnectionState::Authenticated).await; + }); + } + + let _ = self.events.send(ApiEvent { + kind: ApiEventKind::Authenticated, + state: Some(ApiConnectionState::Authenticated), + message: None, + }); + schedule_post_authentication(&self.context, self.in_background); + } +} + +#[async_trait] +impl Handshaker for ApiAuthHandshaker { + async fn handshake( + &self, + sender: &mut dyn HandshakeSender, + receiver: &mut dyn HandshakeReceiver, + _context: &ConnectionContext, + ) -> std::result::Result<(), HandshakeError> { + let user_id = match self.context.user_id().await { + Ok(id) => id, + Err(e) => { + tracing::info!("ApiAuthHandshaker skipped: user_id missing ({})", e); + return Ok(()); + } + }; + + let _ = self.events.send(ApiEvent { + kind: ApiEventKind::ConnectionStateChanged, + state: Some(ApiConnectionState::Authenticating), + message: None, + }); + + if self.can_use_login_token_for_auth { + match self + .authenticate_with_login_token(sender, receiver, user_id) + .await + { + Ok(()) => { + tracing::info!("ApiAuthHandshaker: authenticated with login token"); + self.authentication_succeeded(); + return Ok(()); + } + Err(error) => { + tracing::error!("ApiAuthHandshaker: login token auth failed ({})", error); + return Err(HandshakeError::Protocol(error.to_string())); + } + } + } else if self.legacy_user_app_version >= 62 + && self + .authenticate_with_legacy_token(sender, receiver, user_id) + .await + .unwrap_or(false) + { + tracing::info!("ApiAuthHandshaker: authenticated with legacy token"); + self.authentication_succeeded(); + return Ok(()); + } + + tracing::error!( + "ApiAuthHandshaker: no auth method succeeded. legacy_app_version={}", + self.legacy_user_app_version + ); + + if self.legacy_user_app_version < 62 { + return Err(HandshakeError::Protocol( + "legacy user version is too old for API authentication".into(), + )); + } + + if let Err(e) = self + .obtain_legacy_auth_token(sender, receiver, user_id) + .await + { + return Err(HandshakeError::Protocol(e.to_string())); + } + + let authenticated = self + .authenticate_with_legacy_token(sender, receiver, user_id) + .await + .unwrap_or(false); + + if !authenticated { + return Err(HandshakeError::Protocol("API authentication failed".into())); + } + + if !self.in_background { + if let Err(e) = self.migrate_to_login_token(sender, receiver).await { + return Err(HandshakeError::Protocol(e.to_string())); + } + } + + self.authentication_succeeded(); + Ok(()) + } + + fn name(&self) -> &'static str { + "ApiAuthHandshaker" + } +} + +impl ApiClient { + // publish_plan method logic moved inside Handshaker temporarily, but ApiClient should still have it if needed. + pub(crate) async fn publish_plan(&self, plan: String) -> Result<()> { + let _ = self.events.send(ApiEvent { + kind: ApiEventKind::PlanUpdated, + state: None, + message: Some(plan), + }); + Ok(()) + } +} diff --git a/rust/src/api/runtime/client.rs b/rust/src/api/runtime/client.rs new file mode 100644 index 00000000..b3c4e26b --- /dev/null +++ b/rust/src/api/runtime/client.rs @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::bridge::api::{ApiConfig, ApiConnectionState, ApiEvent, ApiEventKind}; +use crate::context::Context; +use crate::error::Result; +use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, LazyLock, Weak}; +use stream_tungstenite::WebSocketClient; +use tokio::sync::{broadcast, oneshot, Mutex, RwLock}; + +use super::auth::ApiAuthHandshaker; + +pub(super) type PendingRequests = Arc>>>>; + +pub(crate) static API_EVENTS: LazyLock> = + LazyLock::new(|| broadcast::channel(256).0); + +pub(crate) struct ApiClient { + pub(crate) context: Weak, + pub(crate) config: ApiConfig, + pub(crate) state: RwLock, + pub(crate) ws_client: Mutex>>, + pub(crate) pending: PendingRequests, + pub(crate) next_sequence: Mutex, + pub(crate) events: broadcast::Sender, + pub(crate) deliberately_closed: AtomicBool, + pub(crate) in_background: AtomicBool, + pub(crate) network_available: AtomicBool, + pub(crate) is_authenticated: Arc, +} + +impl ApiClient { + pub(crate) fn new(context: &Arc, config: ApiConfig) -> Arc { + Arc::new(Self { + context: Arc::downgrade(context), + config, + state: RwLock::const_new(ApiConnectionState::Stopped), + ws_client: Mutex::const_new(None), + pending: Arc::new(Mutex::const_new(HashMap::new())), + next_sequence: Mutex::const_new(1), + events: API_EVENTS.clone(), + deliberately_closed: AtomicBool::new(false), + in_background: AtomicBool::new(false), + network_available: AtomicBool::new(true), + is_authenticated: Arc::new(AtomicBool::new(false)), + }) + } + + pub(crate) async fn set_state(&self, state: ApiConnectionState) { + let mut guard = self.state.write().await; + if *guard != state { + *guard = state; + drop(guard); + let _ = self.events.send(ApiEvent { + kind: ApiEventKind::ConnectionStateChanged, + state: Some(state), + message: None, + }); + } + } + + pub async fn connect(self: &Arc) -> Result<()> { + self.deliberately_closed.store(false, Ordering::Release); + + let mut client_guard = self.ws_client.lock().await; + if client_guard.is_some() { + return Ok(()); + } + + self.set_state(ApiConnectionState::Connecting).await; + + let host = &self.config.websocket_url; + + let context = self + .context + .upgrade() + .ok_or(crate::error::TwonlyError::Initialization)?; + let handshaker = ApiAuthHandshaker { + context, + api_client: Arc::downgrade(self), + is_authenticated: self.is_authenticated.clone(), + can_use_login_token_for_auth: self.config.can_use_login_token_for_auth, + legacy_user_app_version: self.config.legacy_user_app_version, + in_background: self.in_background.load(Ordering::Acquire), + events: self.events.clone(), + }; + + let client = WebSocketClient::builder(host) + .handshaker(handshaker) + .build(); + + let mut receiver = client.subscribe(); + let mut events = client.subscribe_events(); + + let ws_arc = Arc::new(client); + *client_guard = Some(ws_arc.clone()); + drop(client_guard); + + // Spawn client runner + tokio::spawn({ + let c = ws_arc.clone(); + async move { + let _ = c.run().await; + } + }); + + let self_clone = self.clone(); + tokio::spawn(async move { + loop { + tokio::select! { + msg = receiver.recv() => { + match msg { + Ok(msg) => { + // Tungstenite message + if let stream_tungstenite::tokio_tungstenite::tungstenite::Message::Binary(bytes) = &*msg { + self_clone.handle_incoming(bytes).await; + } + } + Err(_) => break, + } + } + ev = events.recv() => { + use stream_tungstenite::ConnectionEvent; + match ev { + Ok(ConnectionEvent::Connected { .. }) => { + let is_auth = self_clone.is_authenticated.load(Ordering::Acquire); + tracing::info!("ConnectionEvent::Connected received. is_authenticated={}", is_auth); + if is_auth { + self_clone.set_state(ApiConnectionState::Authenticated).await; + } else { + self_clone.set_state(ApiConnectionState::Connected).await; + } + } + Ok(ConnectionEvent::Disconnected { .. }) => { + self_clone.is_authenticated.store(false, Ordering::Release); + self_clone.set_state(ApiConnectionState::Stopped).await; + } + Ok(ConnectionEvent::Connecting { .. }) => { + self_clone.set_state(ApiConnectionState::Connecting).await; + } + Ok(_) => {} + Err(_) => break, + } + } + } + } + }); + + Ok(()) + } + + pub async fn close(&self) { + self.deliberately_closed.store(true, Ordering::Release); + if let Some(_) = self.ws_client.lock().await.take() { + // Drop client to disconnect + } + self.fail_pending().await; + self.set_state(ApiConnectionState::Stopped).await; + } + + pub(crate) async fn fail_pending(&self) { + self.pending.lock().await.clear(); + } + + // pub async fn connection_state(&self) -> ApiConnectionState { + // *self.state.read().await + // } + + pub(crate) async fn set_background(self: &Arc, in_background: bool) -> Result<()> { + self.in_background.store(in_background, Ordering::Release); + if in_background { + self.set_state(ApiConnectionState::Suspended).await; + } else if self.ws_client.lock().await.is_some() { + self.set_state(ApiConnectionState::Authenticated).await; + } else if self.network_available.load(Ordering::Acquire) { + self.connect().await?; + } + Ok(()) + } + + pub(crate) async fn set_network_available(self: &Arc, available: bool) -> Result<()> { + self.network_available.store(available, Ordering::Release); + if available + && !self.in_background.load(Ordering::Acquire) + && self.ws_client.lock().await.is_none() + { + self.connect().await?; + } + Ok(()) + } +} diff --git a/rust/src/api/runtime/helpers.rs b/rust/src/api/runtime/helpers.rs new file mode 100644 index 00000000..75d504b5 --- /dev/null +++ b/rust/src/api/runtime/helpers.rs @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::messages::incoming::client2client::messages; +use crate::api::proto::server_to_client; +use crate::api::runtime::ApiRuntime; + +use crate::bridge::api::ServerResult; +use crate::context::Context; +use crate::error::{Result, TwonlyError}; +use prost::Message as ProstMessage; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::time::Duration; + +pub(crate) fn response_error_code(bytes: &[u8]) -> Result> { + let response = server_to_client::ServerToClient::decode(bytes) + .map_err(|error| TwonlyError::Generic(format!("invalid API response: {error}")))?; + let Some(server_to_client::server_to_client::V::V0(v0)) = response.v else { + return Err(TwonlyError::Generic( + "API response has no V0 envelope".into(), + )); + }; + let Some(server_to_client::v0::Kind::Response(response)) = v0.kind else { + return Err(TwonlyError::Generic( + "API response has no response payload".into(), + )); + }; + Ok(match response.response { + Some(server_to_client::response::Response::Error(code)) => Some(code), + Some(server_to_client::response::Response::Ok(_)) => None, + None => return Err(TwonlyError::Generic("empty API response".into())), + }) +} + +pub(crate) fn schedule_post_authentication(ctx: &Arc, in_background: bool) { + if in_background { + return; + } + let ctx = ctx.clone(); + tokio::spawn(async move { + // Wait a bit to let other initial state settle + tokio::time::sleep(Duration::from_millis(100)).await; + + let replay: Pin> + Send>> = + Box::pin(ApiRuntime::replay_outbox(&ctx)); + if let Err(error) = replay.await { + tracing::warn!("failed to replay API outbox: {error}"); + } + if let Err(error) = ApiRuntime::replay_legacy_raw_outbox(&ctx).await { + tracing::warn!("failed to replay legacy raw-byte outbox: {error}"); + } + if let Err(error) = messages::retransmit_queued_receipts(&ctx).await { + tracing::warn!("failed to retransmit queued receipts: {error}"); + } + }); +} + +pub(crate) fn decode_ok( + bytes: Vec, +) -> Result> { + let response = server_to_client::ServerToClient::decode(bytes.as_slice()) + .map_err(|error| TwonlyError::Generic(format!("invalid API response: {error}")))?; + let Some(server_to_client::server_to_client::V::V0(v0)) = response.v else { + return Err(TwonlyError::Generic( + "API response has no V0 envelope".into(), + )); + }; + let Some(server_to_client::v0::Kind::Response(response)) = v0.kind else { + return Err(TwonlyError::Generic( + "API response has no response payload".into(), + )); + }; + match response.response { + Some(server_to_client::response::Response::Ok(ok)) => { + let ok_val = ok.ok.ok_or_else(|| { + TwonlyError::Generic("successful API response has no value".into()) + })?; + Ok(ServerResult::Ok(ok_val)) + } + Some(server_to_client::response::Response::Error(code)) => { + Ok(ServerResult::ErrorCode(code)) + } + None => Err(TwonlyError::Generic("empty API response".into())), + } +} + +pub(crate) fn decode_ok_value( + bytes: Vec, + extract: impl FnOnce(server_to_client::response::ok::Ok) -> Option, +) -> Result> { + match decode_ok(bytes)? { + ServerResult::Ok(ok) => { + let extracted = extract(ok).ok_or_else(|| { + TwonlyError::Generic("successful API response has an unexpected value".into()) + })?; + Ok(ServerResult::Ok(extracted)) + } + ServerResult::ErrorCode(code) => Ok(ServerResult::ErrorCode(code)), + } +} diff --git a/rust/src/api/runtime/request.rs b/rust/src/api/runtime/request.rs new file mode 100644 index 00000000..775249b2 --- /dev/null +++ b/rust/src/api/runtime/request.rs @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::client::ApiClient; +use crate::api::messages::incoming::handle_server_message; +use crate::api::proto::{client_to_server, server_to_client}; +use crate::bridge::api::{ApiConnectionState, ApiEvent, ApiEventKind}; +use crate::context::Context; +use crate::error::{Result, TwonlyError}; +use core::result::Result as StdResult; +use prost::Message; +use std::future::Future; +use std::pin::Pin; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::oneshot; + +fn call_handle_server_message( + ctx: Arc, + kind: server_to_client::v0::Kind, +) -> Pin> + Send>> { + Box::pin(async move { + handle_server_message(&ctx, kind) + .await + .map_err(|error| error.to_string()) + }) +} + +impl ApiClient { + pub(crate) async fn handle_incoming(self: &Arc, bytes: &[u8]) { + let Ok(message) = server_to_client::ServerToClient::decode(bytes) else { + return; + }; + + if let Some(server_to_client::server_to_client::V::V0(v0)) = message.v { + if let Some(kind) = v0.kind { + if matches!(kind, server_to_client::v0::Kind::Response(_)) { + let mut pending = self.pending.lock().await; + if let Some(sender) = pending.remove(&v0.seq) { + let _ = sender.send(bytes.to_vec()); + } + } else { + let client = self.clone(); + let Some(ctx) = self.context.upgrade() else { + tracing::warn!("context was dropped before handling a server message"); + return; + }; + tokio::spawn(async move { + match call_handle_server_message(ctx, kind).await { + Ok(response) => { + let acknowledgement = client_to_server::ClientToServer { + v: Some(client_to_server::client_to_server::V::V0( + client_to_server::V0 { + seq: v0.seq, + kind: Some(client_to_server::v0::Kind::Response( + response, + )), + }, + )), + }; + if let Err(error) = + client.send(acknowledgement.encode_to_vec()).await + { + tracing::warn!("failed to acknowledge server message: {error}"); + } + } + Err(error) => { + tracing::warn!("failed to process server message: {error}"); + } + } + }); + } + } + } + } + + pub(crate) async fn send(self: &Arc, bytes: Vec) -> Result<()> { + let ws_client = self.ws_client.lock().await.clone(); + if let Some(client) = ws_client { + client + .send_async( + stream_tungstenite::tokio_tungstenite::tungstenite::Message::Binary( + bytes.into(), + ), + ) + .await + .map_err(|e| TwonlyError::Generic(format!("send error: {:?}", e)))?; + Ok(()) + } else { + Err(TwonlyError::Generic("Not connected".into())) + } + } + + pub(crate) async fn request(self: &Arc, bytes: Vec) -> Result> { + self.request_internal(bytes, Duration::from_secs(30)).await + } + + pub(crate) async fn request_durable( + &self, + bytes: Vec, + operation_kind: &str, + ) -> Result> { + let sequence = self.next_sequence().await; + let context = self.context.upgrade().ok_or(TwonlyError::Initialization)?; + let database = context.get_app_database().await; + + sqlx::query!( + "INSERT INTO api_outbox(sequence_id, operation_kind, payload) VALUES(?, ?, ?)", + sequence as i64, + operation_kind, + bytes + ) + .execute(&database.pool) + .await?; + + let response = self.request_internal(bytes, Duration::from_secs(60)).await; + + if response.is_ok() { + sqlx::query!( + "DELETE FROM api_outbox WHERE sequence_id = ?", + sequence as i64 + ) + .execute(&database.pool) + .await?; + database.notify_committed(["api_outbox"]); + } + response + } + + pub(crate) async fn request_internal( + &self, + bytes: Vec, + timeout: Duration, + ) -> Result> { + let mut request = client_to_server::ClientToServer::decode(bytes.as_slice()) + .map_err(|error| TwonlyError::Generic(format!("invalid request payload: {error}")))?; + let sequence = self.next_sequence().await; + if let Some(client_to_server::client_to_server::V::V0(v0)) = &mut request.v { + v0.seq = sequence; + } + + let (sender, receiver) = oneshot::channel(); + self.pending.lock().await.insert(sequence, sender); + + let ws_client = self.ws_client.lock().await.clone(); + if let Some(client) = ws_client { + client + .send_async( + stream_tungstenite::tokio_tungstenite::tungstenite::Message::Binary( + request.encode_to_vec().into(), + ), + ) + .await + .map_err(|e| TwonlyError::Generic(format!("send error: {:?}", e)))?; + } else { + self.pending.lock().await.remove(&sequence); + return Err(TwonlyError::Generic("Not connected".into())); + } + + match tokio::time::timeout(timeout, receiver).await { + Ok(Ok(response)) => Ok(response), + Ok(Err(_)) => Err(TwonlyError::Generic("response channel closed".into())), + Err(_) => { + self.pending.lock().await.remove(&sequence); + Err(TwonlyError::Generic("API request timed out".into())) + } + } + } + + pub(crate) async fn next_sequence(&self) -> u64 { + let mut guard = self.next_sequence.lock().await; + let value = *guard; + *guard += 1; + value + } + + pub(crate) async fn handle_success_metadata(&self, bytes: &[u8]) -> Result<()> { + let response = server_to_client::ServerToClient::decode(bytes) + .map_err(|error| TwonlyError::Generic(format!("invalid API response: {error}")))?; + let Some(server_to_client::server_to_client::V::V0(v0)) = response.v else { + return Ok(()); + }; + let Some(server_to_client::v0::Kind::Response(response)) = v0.kind else { + return Ok(()); + }; + let Some(server_to_client::response::Response::Ok(ok)) = response.response else { + return Ok(()); + }; + if let Some(server_to_client::response::ok::Ok::Authenticated(authenticated)) = ok.ok { + // Note: publish_plan needs to be reachable, we'll keep it in auth or request. + self.publish_plan(authenticated.plan).await?; + } + Ok(()) + } + + pub(crate) async fn handle_api_error(&self, code: i32, contact_id: Option) -> Result<()> { + use crate::api::proto::error::ErrorCode; + if code == ErrorCode::AppVersionOutdated as i32 + || code == ErrorCode::NewDeviceRegistered as i32 + { + self.set_state(ApiConnectionState::PermanentlyRejected) + .await; + self.deliberately_closed.store(true, Ordering::Release); + let kind = if code == ErrorCode::AppVersionOutdated as i32 { + ApiEventKind::AppOutdated + } else { + ApiEventKind::NewDeviceRegistered + }; + let _ = self.events.send(ApiEvent { + kind, + state: Some(ApiConnectionState::PermanentlyRejected), + message: None, + }); + if let Some(_) = self.ws_client.lock().await.take() { + // Drop client to disconnect + } + } + if code == ErrorCode::UserIdNotFound as i32 { + if let Some(contact_id) = contact_id { + let context = self.context.upgrade().ok_or(TwonlyError::Initialization)?; + let database = context.get_app_database().await; + let mut transaction = database.pool.begin().await?; + sqlx::query!( + "UPDATE contacts SET account_deleted = 1 WHERE user_id = ?", + contact_id + ) + .execute(&mut *transaction) + .await?; + sqlx::query!("DELETE FROM receipts WHERE contact_id = ?", contact_id) + .execute(&mut *transaction) + .await?; + transaction.commit().await?; + database.notify_committed(["contacts", "receipts"]); + } + } + Ok(()) + } +} diff --git a/rust/src/api/server.rs b/rust/src/api/server.rs new file mode 100644 index 00000000..2d18b1b0 --- /dev/null +++ b/rust/src/api/server.rs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +mod account; +mod contacts; +mod memories; +mod passwordless; +pub mod prekeys; +mod purchases; +mod transport; + +pub struct Server; + +macro_rules! server_ok { + ($body:expr) => { + decode_ok_value($body, |value| match value { + ResponseOk::None(_) => Some(()), + _ => None, + }) + }; +} + +pub(crate) use server_ok; diff --git a/rust/src/api/server/account.rs b/rust/src/api/server/account.rs new file mode 100644 index 00000000..4bce3ab2 --- /dev/null +++ b/rust/src/api/server/account.rs @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::{server_ok, Server}; +use crate::api::proto::client_to_server; +use crate::api::proto::server_to_client::response::{ok::Ok as ResponseOk, ProofOfWork}; +use crate::api::runtime::helpers::decode_ok_value; +use crate::bridge::api::ServerResult; +use crate::context::Context; +use crate::error::{Result, TwonlyError}; +use libsignal_protocol::{GenericSignedPreKey, IdentityKeyPair, SignedPreKeyRecord}; +use std::sync::Arc; + +impl Server { + pub async fn register( + ctx: &Arc, + username: String, + proof_of_work: i64, + lang_code: String, + is_ios: bool, + ) -> Result> { + let key_manager = ctx.get_key_manager().await?; + let identity = key_manager + .signal_identity + .as_ref() + .ok_or(TwonlyError::SignalIdentityNotFound)?; + let identity_pair = + IdentityKeyPair::try_from(identity.identity_key_pair_structure.as_slice()) + .map_err(|error| TwonlyError::Signal(error.to_string()))?; + let (&signed_prekey_id, record) = identity + .pre_key_store + .iter() + .min_by_key(|(id, _)| *id) + .ok_or_else(|| { + TwonlyError::Generic("Signal signed-prekey store is empty".into()) + })?; + let signed_prekey = SignedPreKeyRecord::deserialize(record) + .map_err(|error| TwonlyError::Signal(error.to_string()))?; + let login_token = key_manager.main_key.get_login_token().to_vec(); + let register = client_to_server::handshake::Register { + username, + invite_code: None, + public_identity_key: identity_pair.identity_key().serialize().to_vec(), + signed_prekey: signed_prekey + .public_key() + .map_err(|error| TwonlyError::Signal(error.to_string()))? + .serialize() + .to_vec(), + signed_prekey_signature: signed_prekey + .signature() + .map_err(|error| TwonlyError::Signal(error.to_string()))?, + signed_prekey_id, + registration_id: identity.registration_id, + is_ios, + lang_code, + proof_of_work, + login_token: Some(login_token), + }; + drop(key_manager); + + let bytes = Self::handshake( + ctx, + client_to_server::handshake::Handshake::Register(register), + ) + .await?; + + decode_ok_value(bytes, |value| match value { + ResponseOk::Userid(id) => Some(id), + _ => None, + }) + } + + pub async fn get_proof_of_work(ctx: &Arc) -> Result> { + let bytes = Self::handshake( + ctx, + client_to_server::handshake::Handshake::RequestPow( + client_to_server::handshake::RequestPow {}, + ), + ) + .await?; + + decode_ok_value(bytes, |value| match value { + ResponseOk::ProofOfWork(p) => Some(p), + _ => None, + }) + } + + pub async fn download_done( + ctx: &Arc, + download_token: Vec, + ) -> Result> { + server_ok!( + Self::durable_application( + ctx, + client_to_server::application_data::ApplicationData::DownloadDone( + client_to_server::application_data::DownloadDone { download_token }, + ), + "download_done", + ) + .await? + ) + } + + pub async fn set_login_token( + ctx: &Arc, + login_token: Vec, + ) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::SetLoginToken( + client_to_server::application_data::SetLoginToken { login_token }, + ), + ) + .await? + ) + } + + pub async fn delete_account(ctx: &Arc) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::DeleteAccount( + client_to_server::application_data::DeleteAccount {}, + ), + ) + .await? + ) + } + + pub async fn update_fcm_token( + ctx: &Arc, + google_fcm: String, + ) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::UpdateGoogleFcmToken( + client_to_server::application_data::UpdateGoogleFcmToken { google_fcm }, + ), + ) + .await? + ) + } + + pub async fn change_username(ctx: &Arc, username: String) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::ChangeUsername( + client_to_server::application_data::ChangeUsername { username }, + ), + ) + .await? + ) + } +} diff --git a/rust/src/api/server/contacts.rs b/rust/src/api/server/contacts.rs new file mode 100644 index 00000000..8b7dd4e5 --- /dev/null +++ b/rust/src/api/server/contacts.rs @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::Server; +use crate::api::proto::server_to_client::response::ok::Ok as ResponseOk; +use crate::api::runtime::helpers::decode_ok_value; +use crate::api::server::server_ok; +use crate::api::{proto, proto::client_to_server}; +use crate::bridge::api::ServerResult; +use crate::context::Context; +use crate::error::Result; +use std::sync::Arc; + +impl Server { + pub async fn get_user_by_id_response(ctx: &Arc, user_id: i64) -> Result> { + Self::application( + ctx, + client_to_server::application_data::ApplicationData::GetUserById( + client_to_server::application_data::GetUserById { user_id }, + ), + ) + .await + } + + pub async fn get_user_by_id( + ctx: &Arc, + user_id: i64, + ) -> Result> { + let bytes = Self::get_user_by_id_response(ctx, user_id).await?; + decode_ok_value(bytes, |value| match value { + proto::server_to_client::response::ok::Ok::Userdata(user) => Some(user), + _ => None, + }) + } + + pub async fn check_for_deleted_usernames(ctx: &Arc) -> Result<()> { + let database = ctx.get_app_database().await; + let contacts = sqlx::query_scalar!( + "SELECT user_id FROM contacts WHERE username IN ('[deleted]', '[Unknown]')" + ) + .fetch_all(&database.pool) + .await?; + for user_id in contacts { + let user = match Self::get_user_by_id(ctx, user_id).await? { + ServerResult::Ok(u) => u, + ServerResult::ErrorCode(_) => continue, + }; + if let Some(username) = user.username { + let username = String::from_utf8(username)?; + sqlx::query!( + "UPDATE contacts SET username = ? WHERE user_id = ?", + username, + user_id + ) + .execute(&database.pool) + .await?; + } + } + database.notify_committed(["contacts"]); + Ok(()) + } + + pub async fn get_user_id_from_username( + ctx: &Arc, + username: String, + ) -> Result> { + let bytes = Self::handshake( + ctx, + client_to_server::handshake::Handshake::GetUseridByUsername( + client_to_server::handshake::GetUserIdByUsername { username }, + ), + ) + .await?; + decode_ok_value(bytes, |value| match value { + ResponseOk::Userid(id) => Some(id), + _ => None, + }) + } + + pub async fn get_user_by_username( + ctx: &Arc, + username: String, + ) -> Result> { + let bytes = Self::application( + ctx, + client_to_server::application_data::ApplicationData::GetUserByUsername( + client_to_server::application_data::GetUserByUsername { username }, + ), + ) + .await?; + + decode_ok_value(bytes, |value| match value { + ResponseOk::Userdata(user) => Some(user), + _ => None, + }) + } + + pub async fn remove_additional_user( + ctx: &Arc, + user_id: i64, + ) -> Result> { + server_ok!( + Self::application_for_contact( + ctx, + client_to_server::application_data::ApplicationData::RemoveAdditionalUser( + client_to_server::application_data::RemoveAdditionalUser { user_id }, + ), + user_id, + ) + .await? + ) + } + + pub async fn report_user( + ctx: &Arc, + reported_user_id: i64, + reason: String, + ) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::ReportUser( + client_to_server::application_data::ReportUser { + reported_user_id, + reason, + }, + ), + ) + .await? + ) + } + + pub async fn send_text_message( + ctx: &Arc, + user_id: i64, + body: Vec, + push_data: Option>, + ) -> Result> { + server_ok!( + Self::application_for_contact( + ctx, + client_to_server::application_data::ApplicationData::TextMessage( + client_to_server::application_data::TextMessage { + user_id, + body, + push_data, + }, + ), + user_id, + ) + .await? + ) + } +} diff --git a/rust/src/api/server/memories.rs b/rust/src/api/server/memories.rs new file mode 100644 index 00000000..9201071f --- /dev/null +++ b/rust/src/api/server/memories.rs @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::Server; +use crate::api::proto::server_to_client::response::ok::Ok as ResponseOk; +use crate::api::proto::{self, client_to_server}; +use crate::api::runtime::helpers::decode_ok_value; +use crate::api::server::server_ok; +use crate::bridge::api::ServerResult; +use crate::context::Context; +use crate::error::Result; +use std::sync::Arc; + +impl Server { + pub async fn request_memories_upload( + ctx: &Arc, + size: i64, + original_date: i64, + media_id: String, + ) -> Result> { + let bytes = Self::application( + ctx, + client_to_server::application_data::ApplicationData::RequestMemoriesUpload( + client_to_server::application_data::RequestMemoriesUpload { + size, + original_date, + media_id, + }, + ), + ) + .await?; + + decode_ok_value(bytes, |value| match value { + ResponseOk::MemoriesUploadUrls(urls) => Some(urls), + _ => None, + }) + } + + pub async fn get_memories_usage( + ctx: &Arc, + ) -> Result> { + let bytes = Self::application( + ctx, + client_to_server::application_data::ApplicationData::GetMemoriesUsage( + client_to_server::application_data::GetMemoriesUsage {}, + ), + ) + .await?; + + decode_ok_value(bytes, |value| match value { + ResponseOk::MemoriesUsage(usage) => Some(usage), + _ => None, + }) + } + + pub async fn get_memories_url( + ctx: &Arc, + media_id: String, + thumbnail: bool, + ) -> Result> { + let bytes = Self::application( + ctx, + client_to_server::application_data::ApplicationData::GetMemoriesUrl( + client_to_server::application_data::GetMemoriesUrl { + media_id, + thumbnail, + }, + ), + ) + .await?; + + decode_ok_value(bytes, |value| match value { + ResponseOk::MemoriesUrl(url) => Some(url), + _ => None, + }) + } + + pub async fn confirm_memories_upload( + ctx: &Arc, + media_id: String, + ) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::ConfirmMemoriesUpload( + client_to_server::application_data::ConfirmMemoriesUpload { media_id }, + ), + ) + .await? + ) + } + + pub async fn delete_memory(ctx: &Arc, media_id: String) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::DeleteMemory( + client_to_server::application_data::DeleteMemory { media_id }, + ), + ) + .await? + ) + } + + pub async fn disable_memories_backup(ctx: &Arc) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::DisableMemoriesBackup( + client_to_server::application_data::DisableMemoriesBackup {}, + ), + ) + .await? + ) + } +} diff --git a/rust/src/api/server/passwordless.rs b/rust/src/api/server/passwordless.rs new file mode 100644 index 00000000..290b87ed --- /dev/null +++ b/rust/src/api/server/passwordless.rs @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::Server; +use crate::api::proto::server_to_client::response::ok::Ok as ResponseOk; +use crate::api::proto::{self, client_to_server}; +use crate::api::runtime::helpers::decode_ok_value; +use crate::api::server::server_ok; +use crate::bridge::api::ServerResult; +use crate::context::Context; +use crate::error::Result; +use std::sync::Arc; + +impl Server { + pub async fn register_passwordless_recovery( + ctx: &Arc, + encrypted_server_key: Vec, + pin_unlock_token: Option>, + ) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::RegisterPasswordlessRecovery( + client_to_server::application_data::RegisterPasswordLessRecovery { + encrypted_server_key, + pin_unlock_token, + }, + ), + ) + .await? + ) + } + + pub async fn get_server_key_for_passwordless_recovery( + ctx: &Arc, + user_id: i64, + server_key_protection: Vec, + pin_unlock_token: Option>, + pin_protection_key: Option>, + email: Option, + ) -> Result>> { + let bytes = Self::handshake( + ctx, + client_to_server::handshake::Handshake::GetServerKeyForPasswordlessRecovery( + client_to_server::handshake::GetServerKeyForPasswordLessRecovery { + user_id, + server_key_protection, + pin_unlock_token, + pin_protection_key, + email, + }, + ), + ) + .await?; + + decode_ok_value(bytes, |value| match value { + ResponseOk::PasswordlessRecoveryServerKey(key) => Some(key), + _ => None, + }) + } + + pub async fn submit_recovery_share( + ctx: &Arc, + notification_id: String, + encrypted_message: Vec, + ) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::PasswordlessNotification( + client_to_server::application_data::PasswordlessNotification { + notification_id, + encrypted_message, + }, + ), + ) + .await? + ) + } + + pub async fn register_passwordless_notification( + ctx: &Arc, + notification_id: String, + download_auth_token: Vec, + lang_code: String, + google_fcm: Option, + ) -> Result> { + server_ok!( + Self::handshake( + ctx, + client_to_server::handshake::Handshake::RegisterPasswordlessNotification( + client_to_server::handshake::RegisterPasswordlessNotification { + notification_id, + download_auth_token, + lang_code, + google_fcm, + }, + ), + ) + .await? + ) + } + + pub async fn check_for_passwordless_notification( + ctx: &Arc, + notification_id: String, + download_auth_token: Vec, + already_received_message_ids: Vec, + ) -> Result> + { + let bytes = Self::handshake( + ctx, + client_to_server::handshake::Handshake::CheckForPasswordlessNotification( + client_to_server::handshake::CheckForPasswordlessNotification { + notification_id, + download_auth_token, + already_received_message_ids, + }, + ), + ) + .await?; + + decode_ok_value(bytes, |value| match value { + ResponseOk::PasswordlessNotificationMessages(msgs) => Some(msgs), + _ => None, + }) + } +} diff --git a/rust/src/api/server/prekeys.rs b/rust/src/api/server/prekeys.rs new file mode 100644 index 00000000..6d29fe65 --- /dev/null +++ b/rust/src/api/server/prekeys.rs @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::Server; +use crate::api::proto::client_to_server; +use crate::context::Context; +use crate::error::Result; +use std::sync::Arc; + +pub struct PqcPreKeyInput { + pub ecc_pre_key_id: i64, + pub ecc_pre_key: Vec, + pub kyber_pre_key_id: i64, + pub kyber_pre_key: Vec, + pub kyber_pre_key_signature: Vec, +} + +impl Server { + #[doc(hidden)] + pub async fn generate_and_upload_pqc_pre_keys(ctx: &Arc) -> Result> { + let engine = ctx.get_signal_engine().lock().await; + let bundle = engine + .as_ref() + .ok_or(crate::error::TwonlyError::SignalIdentityNotFound)? + .generate_bundle() + .await?; + drop(engine); + + Self::upload_pqc_pre_keys( + ctx, + i64::from(bundle.signed_pre_key_id), + bundle.signed_pre_key_public, + bundle.signed_pre_key_signature, + i64::from(bundle.kyber_pre_key_id), + bundle.kyber_pre_key_public, + bundle.kyber_pre_key_signature, + Vec::new(), + ) + .await + } + + pub async fn update_signed_pre_key( + ctx: &Arc, + id: i64, + key: Vec, + signature: Vec, + ) -> Result> { + Self::application( + ctx, + client_to_server::application_data::ApplicationData::UpdateSignedPrekey( + client_to_server::application_data::UpdateSignedPreKey { + signed_prekey_id: id, + signed_prekey: key, + signed_prekey_signature: signature, + }, + ), + ) + .await + } + + #[allow(clippy::too_many_arguments)] + pub async fn upload_pqc_pre_keys( + ctx: &Arc, + ecc_signed_prekey_id: i64, + ecc_signed_prekey: Vec, + ecc_signed_prekey_signature: Vec, + kyber_signed_prekey_id: i64, + kyber_signed_prekey: Vec, + kyber_signed_prekey_signature: Vec, + prekeys: Vec, + ) -> Result> { + let prekeys = prekeys + .into_iter() + .map(|key| client_to_server::application_data::PqcPreKey { + ecc_pre_key_id: key.ecc_pre_key_id, + ecc_pre_key: key.ecc_pre_key, + kyber_pre_key_id: key.kyber_pre_key_id, + kyber_pre_key: key.kyber_pre_key, + kyber_pre_key_signature: key.kyber_pre_key_signature, + }) + .collect(); + Self::application( + ctx, + client_to_server::application_data::ApplicationData::UploadPqcPrekeys( + client_to_server::application_data::UploadPqcPreKeys { + ecc_signed_prekey_id, + ecc_signed_prekey, + ecc_signed_prekey_signature, + kyber_signed_prekey_id, + kyber_signed_prekey, + kyber_signed_prekey_signature, + prekeys, + }, + ), + ) + .await + } +} diff --git a/rust/src/api/server/purchases.rs b/rust/src/api/server/purchases.rs new file mode 100644 index 00000000..ed8a03dc --- /dev/null +++ b/rust/src/api/server/purchases.rs @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::Server; +use crate::api::proto::client_to_server; +use crate::api::proto::server_to_client::response::ok::Ok as ResponseOk; +use crate::api::runtime::helpers::decode_ok_value; +use crate::api::server::server_ok; +use crate::bridge::api::ServerResult; +use crate::context::Context; +use crate::error::Result; +use std::sync::Arc; + +impl Server { + pub async fn get_plan_balance(ctx: &Arc) -> Result> { + Self::application( + ctx, + client_to_server::application_data::ApplicationData::GetCurrentPlanInfos( + client_to_server::application_data::GetCurrentPlanInfos {}, + ), + ) + .await + } + + pub async fn load_plan_balance(ctx: &Arc, use_cache: bool) -> Result> { + let database = ctx.get_app_database().await; + match Self::get_plan_balance(ctx).await { + Ok(response) => { + sqlx::query!( + r#" + INSERT INTO api_state(key, value) VALUES('plan_balance', ?) + ON CONFLICT(key) DO UPDATE SET value = excluded.value, + updated_at = CAST(strftime('%s', 'now') AS INTEGER) + "#, + response, + ) + .execute(&database.pool) + .await?; + database.notify_committed(["api_state"]); + Ok(response) + } + Err(network_error) if use_cache => { + sqlx::query_scalar!("SELECT value FROM api_state WHERE key = 'plan_balance'") + .fetch_optional(&database.pool) + .await? + .ok_or(network_error) + } + Err(error) => Err(error), + } + } + + pub async fn ipa_purchase( + ctx: &Arc, + product_id: String, + source: String, + verification_data: String, + ) -> Result> { + Self::application( + ctx, + client_to_server::application_data::ApplicationData::IpaPurchase( + client_to_server::application_data::IpaPurchase { + product_id, + source, + verification_data, + }, + ), + ) + .await + } + + pub async fn add_additional_user(ctx: &Arc, user_id: i64) -> Result> { + server_ok!( + Self::application_for_contact( + ctx, + client_to_server::application_data::ApplicationData::AddAdditionalUser( + client_to_server::application_data::AddAdditionalUser { user_id }, + ), + user_id, + ) + .await? + ) + } + + pub async fn force_ipa_check(ctx: &Arc) -> Result> { + server_ok!( + Self::application( + ctx, + client_to_server::application_data::ApplicationData::IpaForceCheck( + client_to_server::application_data::IpaForceCheck {}, + ), + ) + .await? + ) + } +} diff --git a/rust/src/api/server/transport.rs b/rust/src/api/server/transport.rs new file mode 100644 index 00000000..280a88c5 --- /dev/null +++ b/rust/src/api/server/transport.rs @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use super::Server; +use crate::api::{proto::client_to_server, ApiRuntime}; +use crate::context::Context; +use crate::error::Result; +use prost::Message as _; +use std::sync::Arc; + +impl Server { + fn application_request( + application: client_to_server::application_data::ApplicationData, + ) -> Vec { + client_to_server::ClientToServer { + v: Some(client_to_server::client_to_server::V::V0( + client_to_server::V0 { + seq: 0, + kind: Some(client_to_server::v0::Kind::Applicationdata( + client_to_server::ApplicationData { + application_data: Some(application), + }, + )), + }, + )), + } + .encode_to_vec() + } + + fn handshake_request(handshake: client_to_server::handshake::Handshake) -> Vec { + client_to_server::ClientToServer { + v: Some(client_to_server::client_to_server::V::V0( + client_to_server::V0 { + seq: 0, + kind: Some(client_to_server::v0::Kind::Handshake( + client_to_server::Handshake { + handshake: Some(handshake), + }, + )), + }, + )), + } + .encode_to_vec() + } + + pub(super) async fn application( + ctx: &Arc, + value: client_to_server::application_data::ApplicationData, + ) -> Result> { + ApiRuntime::request_authenticated(ctx, Self::application_request(value), None).await + } + + pub(super) async fn application_for_contact( + ctx: &Arc, + value: client_to_server::application_data::ApplicationData, + contact_id: i64, + ) -> Result> { + ApiRuntime::request_authenticated(ctx, Self::application_request(value), Some(contact_id)) + .await + } + + pub(super) async fn durable_application( + ctx: &Arc, + value: client_to_server::application_data::ApplicationData, + kind: &'static str, + ) -> Result> { + ApiRuntime::request_durable_authenticated(ctx, Self::application_request(value), kind).await + } + + pub(super) async fn handshake( + ctx: &Arc, + value: client_to_server::handshake::Handshake, + ) -> Result> { + ApiRuntime::request_binary(ctx, Self::handshake_request(value)).await + } +} diff --git a/rust/src/backup/backup_archive.rs b/rust/src/backup/backup_archive.rs index 58e869b7..98a0a89e 100644 --- a/rust/src/backup/backup_archive.rs +++ b/rust/src/backup/backup_archive.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::context::Context; use crate::database::app::APP_DATABASE_FILE; use crate::database::signal::Database; @@ -227,9 +232,14 @@ impl BackupArchive { true, ) .await?; - let integrity: String = sqlx::query_scalar("PRAGMA integrity_check") + let integrity = sqlx::query_scalar!(r#"PRAGMA integrity_check"#) .fetch_one(&staged_app_database.pool) .await?; + let integrity = integrity.ok_or_else(|| { + crate::error::TwonlyError::Generic( + "Staged app database integrity check returned no result".to_owned(), + ) + })?; staged_app_database.pool.close().await; if integrity.to_lowercase() != "ok" { return Err(crate::error::TwonlyError::Generic(format!( @@ -252,7 +262,7 @@ impl BackupArchive { // unlinked pre-restore database. let current_app_database = ctx.get_app_database().await; current_app_database.pool.close().await; - let current_rust_database = ctx.get_rust_database().await; + let current_rust_database = ctx.get_rust_db().await; current_rust_database.pool.close().await; for (file_name, target_dir, is_db, _) in Self::get_backup_files(ctx, &key_manager)? { @@ -398,10 +408,15 @@ mod tests { }; { let app_db = ctx.get_app_database().await; - sqlx::query("INSERT INTO contacts(user_id, username) VALUES(1, 'original contact')") - .execute(&app_db.pool) - .await - .unwrap(); + sqlx::query!( + r#" + INSERT INTO contacts(user_id, username) + VALUES(1, 'original contact') + "# + ) + .execute(&app_db.pool) + .await + .unwrap(); } // 2. Create backup @@ -416,10 +431,16 @@ mod tests { std::fs::write(config_file, "new config").unwrap(); let app_db = ctx.get_app_database().await; - sqlx::query("UPDATE contacts SET username = 'changed contact' WHERE user_id = 1") - .execute(&app_db.pool) - .await - .unwrap(); + sqlx::query!( + r#" + UPDATE contacts + SET username = 'changed contact' + WHERE user_id = 1 + "# + ) + .execute(&app_db.pool) + .await + .unwrap(); } // 4. Restore backup @@ -439,11 +460,16 @@ mod tests { assert_eq!(key_manager.main_key.get_login_token(), original_login_token); let app_db = ctx.get_app_database().await; - let username: String = - sqlx::query_scalar("SELECT username FROM contacts WHERE user_id = 1") - .fetch_one(&app_db.pool) - .await - .unwrap(); + let username = sqlx::query_scalar!( + r#" + SELECT username + FROM contacts + WHERE user_id = 1 + "# + ) + .fetch_one(&app_db.pool) + .await + .unwrap(); assert_eq!(username, "original contact"); } } @@ -465,35 +491,53 @@ mod tests { .await .unwrap(); legacy.run_migrations().await.unwrap(); - sqlx::query("PRAGMA user_version = 25") - .execute(&legacy.pool) - .await - .unwrap(); - sqlx::query("INSERT INTO contacts(user_id, username) VALUES(99, 'from old backup')") + sqlx::query!(r#"PRAGMA user_version = 25"#) .execute(&legacy.pool) .await .unwrap(); + sqlx::query!( + r#" + INSERT INTO contacts(user_id, username) + VALUES(99, 'from old backup') + "# + ) + .execute(&legacy.pool) + .await + .unwrap(); legacy.pool.close().await; let archive_path = BackupArchive::create_backup(&ctx).await.unwrap(); remove_file_from_encrypted_archive(&ctx, &archive_path, APP_DATABASE_FILE).await; let app_db = ctx.get_app_database().await; - sqlx::query("INSERT INTO contacts(user_id, username) VALUES(1, 'current data')") - .execute(&app_db.pool) - .await - .unwrap(); + sqlx::query!( + r#" + INSERT INTO contacts(user_id, username) + VALUES(1, 'current data') + "# + ) + .execute(&app_db.pool) + .await + .unwrap(); BackupArchive::restore_from_backup(&ctx, &archive_path) .await .unwrap(); let restored = ctx.get_app_database().await; - let contacts: Vec<(i64, String)> = - sqlx::query_as("SELECT user_id, username FROM contacts ORDER BY user_id") - .fetch_all(&restored.pool) - .await - .unwrap(); + let contacts = sqlx::query!( + r#" + SELECT user_id, username + FROM contacts + ORDER BY user_id + "# + ) + .fetch_all(&restored.pool) + .await + .unwrap() + .into_iter() + .map(|row| (row.user_id, row.username)) + .collect::>(); assert_eq!(contacts, vec![(99, "from old backup".to_owned())]); } diff --git a/rust/src/backup/backup_identity.rs b/rust/src/backup/backup_identity.rs index a81c1e4f..d73ab0c5 100644 --- a/rust/src/backup/backup_identity.rs +++ b/rust/src/backup/backup_identity.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::error::{Result, TwonlyError}; use crate::keys::{BackupPasswordKeys, KeyManager}; use crate::secure_storage::SecureStorage; diff --git a/rust/src/backup/mod.rs b/rust/src/backup/mod.rs index 7d3cd538..87fdbd08 100644 --- a/rust/src/backup/mod.rs +++ b/rust/src/backup/mod.rs @@ -1,2 +1,7 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + pub(crate) mod backup_archive; pub(crate) mod backup_identity; diff --git a/rust/src/bridge/api.rs b/rust/src/bridge/api.rs new file mode 100644 index 00000000..20b1e7cf --- /dev/null +++ b/rust/src/bridge/api.rs @@ -0,0 +1,634 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::messages::incoming::client2client::messages; +use crate::api::ApiRuntime; +pub use crate::api::PqcPreKeyInput; +use crate::api::Server; +use crate::context::Context; +use crate::error::Result; +use crate::frb_generated::StreamSink; +use crate::user_config::UserConfig; +use flutter_rust_bridge::frb; +use prost::Message; + +#[frb(ignore)] +pub enum ServerResult { + Ok(T), + ErrorCode(i32), +} + +#[frb] +pub enum ServerResultEmpty { + Ok, + ErrorCode(i32), +} + +#[frb] +pub enum ServerResultVecU8 { + Ok(Vec), + ErrorCode(i32), +} + +#[frb] +pub enum ServerResultI64 { + Ok(i64), + ErrorCode(i32), +} + +impl ServerResult<()> { + pub fn into_bridge(self) -> ServerResultEmpty { + match self { + ServerResult::Ok(()) => ServerResultEmpty::Ok, + ServerResult::ErrorCode(c) => ServerResultEmpty::ErrorCode(c), + } + } +} + +impl ServerResult> { + pub fn into_bridge(self) -> ServerResultVecU8 { + match self { + ServerResult::Ok(v) => ServerResultVecU8::Ok(v), + ServerResult::ErrorCode(c) => ServerResultVecU8::ErrorCode(c), + } + } +} + +impl ServerResult { + pub fn into_bridge(self) -> ServerResultI64 { + match self { + ServerResult::Ok(v) => ServerResultI64::Ok(v), + ServerResult::ErrorCode(c) => ServerResultI64::ErrorCode(c), + } + } +} + +#[derive(Clone, Debug)] +pub struct ApiConfig { + /// Full WebSocket URL, for example `wss://api.twonly.eu/api/client`. + pub websocket_url: String, + /// Authentication identity. `None` keeps the transport usable for + /// unauthenticated registration/recovery requests. + /// The persisted integer schema/app version used by the legacy-token + /// migration guard in the Dart implementation. + pub legacy_user_app_version: i64, + pub in_background: bool, + pub can_use_login_token_for_auth: bool, +} + +impl ApiConfig { + pub(crate) async fn from_rust_state(context: &Context) -> Result { + let user = UserConfig::load_from(context)?; + Ok(Self { + websocket_url: format!("{}client", RustApi::api_base_url("wss".to_owned())), + legacy_user_app_version: user.as_ref().map_or(0, |value| value.app_version), + in_background: false, + can_use_login_token_for_auth: user + .as_ref() + .is_some_and(|value| value.can_use_login_token_for_auth), + }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ApiConnectionState { + Stopped, + Connecting, + Connected, + Authenticating, + Authenticated, + Reconnecting, + Suspended, + PermanentlyRejected, +} + +#[derive(Clone, Debug)] +pub struct ApiEvent { + pub kind: ApiEventKind, + pub state: Option, + pub message: Option, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ApiEventKind { + ConnectionStateChanged, + TransportError, + Authenticated, + PlanUpdated, + AppOutdated, + NewDeviceRegistered, + LoginTokenMigrated, +} + +/// Flutter-facing facade for the Rust-owned API runtime. +pub struct RustApi {} + +pub struct PreparedOutgoingMessage { + pub message: Vec, + pub push_data: Option>, +} + +impl RustApi { + pub async fn request_contact_by_username(username: String) -> Result<()> { + let ctx = Context::get_static()?; + crate::services::contacts::ContactService::new(ctx) + .request_by_username(username, false) + .await + } + + #[frb(sync)] + pub fn api_base_url(protocol: String) -> String { + if cfg!(debug_assertions) { + format!("{}://dev-api.twonly.eu/api/", protocol) + } else { + format!("{}://api.twonly.eu/api/", protocol) + } + } + + pub async fn connect() -> Result<()> { + let ctx = Context::get_static()?; + ApiRuntime::connect(ctx).await + } + + /// Reloads identity and authentication settings from Rust-owned storage. + /// No configuration values are accepted from the caller. + pub async fn reload_configuration() -> Result<()> { + let ctx = Context::get_static()?; + ApiRuntime::reload_configuration(ctx).await + } + + pub async fn close() -> Result<()> { + let ctx = Context::get_static()?; + ApiRuntime::close(ctx).await + } + + pub async fn connection_state() -> Result { + let ctx = Context::get_static()?; + ApiRuntime::connection_state(ctx).await + } + + pub async fn set_background(in_background: bool) -> Result<()> { + let ctx = Context::get_static()?; + ApiRuntime::set_background(ctx, in_background).await + } + + pub async fn set_network_available(available: bool) -> Result<()> { + let ctx = Context::get_static()?; + ApiRuntime::set_network_available(ctx, available).await + } + + pub async fn send_binary(bytes: Vec) -> Result<()> { + let ctx = Context::get_static()?; + ApiRuntime::send_binary(ctx, bytes).await + } + + /// Sends a protobuf ClientToServer envelope. Rust assigns the sequence ID + /// and resolves this future with the matching ServerToClient response. + pub async fn request_binary(bytes: Vec) -> Result> { + let ctx = Context::get_static()?; + ApiRuntime::request_binary(ctx, bytes).await + } + + pub async fn allocate_sequence() -> Result { + let ctx = Context::get_static()?; + ApiRuntime::allocate_sequence(ctx).await + } + + pub async fn events(sink: StreamSink) -> Result<()> { + let ctx = Context::get_static()?; + ApiRuntime::events(ctx, sink).await + } + + pub async fn register( + username: String, + proof_of_work: i64, + lang_code: String, + is_ios: bool, + ) -> Result { + let ctx = Context::get_static()?; + Server::register(ctx, username, proof_of_work, lang_code, is_ios) + .await + .map(|r| r.into_bridge()) + } + + pub async fn get_user_by_id(user_id: i64) -> Result { + let ctx = Context::get_static()?; + Server::get_user_by_id(ctx, user_id) + .await + .map(|r| r.into_bridge()) + } + pub async fn check_for_deleted_usernames() -> Result<()> { + let ctx = Context::get_static()?; + Server::check_for_deleted_usernames(ctx).await + } + pub async fn get_user_id_from_username(username: String) -> Result { + let ctx = Context::get_static()?; + Server::get_user_id_from_username(ctx, username) + .await + .map(|r| r.into_bridge()) + } + pub async fn get_user_data(username: String) -> Result { + let ctx = Context::get_static()?; + Server::get_user_by_username(ctx, username) + .await + .map(|r| r.into_bridge()) + } + pub async fn get_proof_of_work() -> Result { + let ctx = Context::get_static()?; + Server::get_proof_of_work(ctx).await.map(|r| match r { + ServerResult::Ok(data) => ServerResultVecU8::Ok(data.encode_to_vec()), + ServerResult::ErrorCode(c) => ServerResultVecU8::ErrorCode(c), + }) + } + pub async fn download_done(token: Vec) -> Result { + let ctx = Context::get_static()?; + Server::download_done(ctx, token) + .await + .map(|r| r.into_bridge()) + } + pub async fn set_login_token(token: Vec) -> Result { + let ctx = Context::get_static()?; + Server::set_login_token(ctx, token) + .await + .map(|r| r.into_bridge()) + } + pub async fn request_memories_upload( + size: i64, + original_date: i64, + media_id: String, + ) -> Result { + let ctx = Context::get_static()?; + Server::request_memories_upload(ctx, size, original_date, media_id) + .await + .map(|r| r.into_bridge()) + } + pub async fn get_memories_usage() -> Result { + let ctx = Context::get_static()?; + Server::get_memories_usage(ctx) + .await + .map(|r| r.into_bridge()) + } + pub async fn get_memories_url(media_id: String, thumbnail: bool) -> Result { + let ctx = Context::get_static()?; + Server::get_memories_url(ctx, media_id, thumbnail) + .await + .map(|r| r.into_bridge()) + } + pub async fn confirm_memories_upload(media_id: String) -> Result { + let ctx = Context::get_static()?; + Server::confirm_memories_upload(ctx, media_id) + .await + .map(|r| r.into_bridge()) + } + pub async fn delete_memory(media_id: String) -> Result { + let ctx = Context::get_static()?; + Server::delete_memory(ctx, media_id) + .await + .map(|r| r.into_bridge()) + } + pub async fn disable_memories_backup() -> Result { + let ctx = Context::get_static()?; + Server::disable_memories_backup(ctx) + .await + .map(|r| r.into_bridge()) + } + pub async fn get_plan_balance() -> Result> { + let ctx = Context::get_static()?; + Server::get_plan_balance(ctx).await + } + pub async fn load_plan_balance(use_cache: bool) -> Result> { + let ctx = Context::get_static()?; + Server::load_plan_balance(ctx, use_cache).await + } + pub async fn remove_additional_user(user_id: i64) -> Result { + let ctx = Context::get_static()?; + Server::remove_additional_user(ctx, user_id) + .await + .map(|r| r.into_bridge()) + } + pub async fn add_additional_user(user_id: i64) -> Result { + let ctx = Context::get_static()?; + Server::add_additional_user(ctx, user_id) + .await + .map(|r| r.into_bridge()) + } + pub async fn register_passwordless_recovery( + encrypted_server_key: Vec, + pin_unlock_token: Option>, + ) -> Result { + let ctx = Context::get_static()?; + Server::register_passwordless_recovery(ctx, encrypted_server_key, pin_unlock_token) + .await + .map(|r| r.into_bridge()) + } + pub async fn get_server_key_for_passwordless_recovery( + user_id: i64, + server_key_protection: Vec, + pin_unlock_token: Option>, + pin_protection_key: Option>, + email: Option, + ) -> Result { + let ctx = Context::get_static()?; + Server::get_server_key_for_passwordless_recovery( + ctx, + user_id, + server_key_protection, + pin_unlock_token, + pin_protection_key, + email, + ) + .await + .map(|r| r.into_bridge()) + } + pub async fn submit_recovery_share( + notification_id: String, + encrypted_message: Vec, + ) -> Result { + let ctx = Context::get_static()?; + Server::submit_recovery_share(ctx, notification_id, encrypted_message) + .await + .map(|r| r.into_bridge()) + } + pub async fn register_passwordless_notification( + notification_id: String, + download_auth_token: Vec, + lang_code: String, + google_fcm: Option, + ) -> Result { + let ctx = Context::get_static()?; + Server::register_passwordless_notification( + ctx, + notification_id, + download_auth_token, + lang_code, + google_fcm, + ) + .await + .map(|r| r.into_bridge()) + } + pub async fn check_for_passwordless_notification( + notification_id: String, + download_auth_token: Vec, + already_received_message_ids: Vec, + ) -> Result { + let ctx = Context::get_static()?; + Server::check_for_passwordless_notification( + ctx, + notification_id, + download_auth_token, + already_received_message_ids, + ) + .await + .map(|r| r.into_bridge()) + } + pub async fn report_user(user_id: i64, reason: String) -> Result { + let ctx = Context::get_static()?; + Server::report_user(ctx, user_id, reason) + .await + .map(|r| r.into_bridge()) + } + pub async fn delete_account() -> Result { + let ctx = Context::get_static()?; + Server::delete_account(ctx).await.map(|r| r.into_bridge()) + } + pub async fn update_fcm_token(token: String) -> Result { + let ctx = Context::get_static()?; + Server::update_fcm_token(ctx, token) + .await + .map(|r| r.into_bridge()) + } + pub async fn ipa_purchase( + product_id: String, + source: String, + verification_data: String, + ) -> Result> { + let ctx = Context::get_static()?; + Server::ipa_purchase(ctx, product_id, source, verification_data).await + } + pub async fn change_username(username: String) -> Result { + let ctx = Context::get_static()?; + Server::change_username(ctx, username) + .await + .map(|r| r.into_bridge()) + } + pub async fn force_ipa_check() -> Result { + let ctx = Context::get_static()?; + Server::force_ipa_check(ctx).await.map(|r| r.into_bridge()) + } + pub async fn update_signed_pre_key( + id: i64, + key: Vec, + signature: Vec, + ) -> Result> { + let ctx = Context::get_static()?; + Server::update_signed_pre_key(ctx, id, key, signature).await + } + #[allow(clippy::too_many_arguments)] + pub async fn upload_pqc_pre_keys( + ecc_signed_prekey_id: i64, + ecc_signed_prekey: Vec, + ecc_signed_prekey_signature: Vec, + kyber_signed_prekey_id: i64, + kyber_signed_prekey: Vec, + kyber_signed_prekey_signature: Vec, + prekeys: Vec, + ) -> Result> { + let ctx = Context::get_static()?; + Server::upload_pqc_pre_keys( + ctx, + ecc_signed_prekey_id, + ecc_signed_prekey, + ecc_signed_prekey_signature, + kyber_signed_prekey_id, + kyber_signed_prekey, + kyber_signed_prekey_signature, + prekeys, + ) + .await + } + pub async fn send_text_message( + user_id: i64, + body: Vec, + push_data: Option>, + ) -> Result { + let ctx = Context::get_static()?; + Server::send_text_message(ctx, user_id, body, push_data) + .await + .map(|r| r.into_bridge()) + } + + pub async fn send_encrypted_content( + contact_id: i64, + content: Vec, + message_id: Option, + only_send_if_no_receipts_are_open: bool, + only_return_encrypted_data: bool, + blocking: bool, + ) -> Result> { + let ctx = Context::get_static()?; + Ok( + crate::api::messages::outgoing::send_c2c_message_to_contact() + .ctx(ctx) + .contact_id(contact_id) + .encrypted_content(content) + .maybe_message_id(message_id) + .only_send_if_no_receipts_are_open(only_send_if_no_receipts_are_open) + .only_return_encrypted_data(only_return_encrypted_data) + .blocking(blocking) + .call() + .await? + .map(|(message, push_data)| PreparedOutgoingMessage { message, push_data }), + ) + } + + pub async fn send_encrypted_content_to_group( + group_id: String, + content: Vec, + message_id: Option, + only_send_if_no_receipts_are_open: bool, + ) -> Result<()> { + let ctx = Context::get_static()?; + crate::services::messages::MessageService::new(ctx) + .send_to_group( + group_id, + content, + message_id, + only_send_if_no_receipts_are_open, + ) + .await + } + + pub async fn insert_and_send_text( + group_id: String, + text: String, + quote_message_id: Option, + ) -> Result { + let ctx = Context::get_static()?; + crate::services::messages::MessageService::new(ctx) + .insert_and_send_text(group_id, text, quote_message_id) + .await + } + + pub async fn insert_and_send_additional_data( + group_id: String, + message_type: String, + additional_data: Vec, + ) -> Result { + let ctx = Context::get_static()?; + crate::services::messages::MessageService::new(ctx) + .insert_and_send_additional_data(group_id, message_type, additional_data) + .await + } + + pub async fn insert_and_send_contact_share( + group_id: String, + contact_ids: Vec, + ) -> Result { + let ctx = Context::get_static()?; + crate::services::messages::MessageService::new(ctx) + .insert_and_send_contact_share(group_id, contact_ids) + .await + } + + pub async fn insert_and_send_ask_about_user( + contact_id: i64, + ask_about_user_id: i64, + ) -> Result { + let ctx = Context::get_static()?; + crate::services::messages::MessageService::new(ctx) + .insert_and_send_ask_about_user(contact_id, ask_about_user_id) + .await + } + + pub async fn send_typing(group_id: String, is_typing: bool) -> Result<()> { + let ctx = Context::get_static()?; + crate::services::messages::MessageService::new(ctx) + .send_typing(group_id, is_typing) + .await + } + + pub async fn retransmit_all_messages() -> Result<()> { + let ctx = Context::get_static()?; + crate::services::messages::MessageService::new(ctx) + .retransmit_all() + .await + } + + pub async fn send_queued_message(receipt_id: String) -> Result<()> { + let ctx = Context::get_static()?; + crate::services::messages::MessageService::new(ctx) + .send_receipt(receipt_id) + .await + } + + pub async fn prepare_queued_message( + receipt_id: String, + ) -> Result> { + Ok( + messages::prepare_queued_receipt(Context::get_static()?, &receipt_id) + .await? + .map(|(message, push_data)| PreparedOutgoingMessage { message, push_data }), + ) + } + + pub async fn notify_messages_opened(contact_id: i64, message_ids: Vec) -> Result<()> { + let ctx = Context::get_static()?; + crate::services::messages::MessageService::new(ctx) + .notify_opened(contact_id, message_ids) + .await + } + + pub async fn send_contact_profile(contact_id: i64) -> Result<()> { + let ctx = Context::get_static()?; + crate::services::contacts::ContactService::new(ctx) + .send_profile(contact_id) + .await + } +} + +impl ServerResult { + pub fn into_bridge(self) -> ServerResultVecU8 { + match self { + ServerResult::Ok(v) => ServerResultVecU8::Ok(prost::Message::encode_to_vec(&v)), + ServerResult::ErrorCode(c) => ServerResultVecU8::ErrorCode(c), + } + } +} + +impl ServerResult { + pub fn into_bridge(self) -> ServerResultVecU8 { + match self { + ServerResult::Ok(v) => ServerResultVecU8::Ok(prost::Message::encode_to_vec(&v)), + ServerResult::ErrorCode(c) => ServerResultVecU8::ErrorCode(c), + } + } +} + +impl ServerResult { + pub fn into_bridge(self) -> ServerResultVecU8 { + match self { + ServerResult::Ok(v) => ServerResultVecU8::Ok(prost::Message::encode_to_vec(&v)), + ServerResult::ErrorCode(c) => ServerResultVecU8::ErrorCode(c), + } + } +} + +impl ServerResult { + pub fn into_bridge(self) -> ServerResultVecU8 { + match self { + ServerResult::Ok(v) => ServerResultVecU8::Ok(prost::Message::encode_to_vec(&v)), + ServerResult::ErrorCode(c) => ServerResultVecU8::ErrorCode(c), + } + } +} + +impl ServerResult { + pub fn into_bridge(self) -> ServerResultVecU8 { + match self { + ServerResult::Ok(v) => ServerResultVecU8::Ok(prost::Message::encode_to_vec(&v)), + ServerResult::ErrorCode(c) => ServerResultVecU8::ErrorCode(c), + } + } +} diff --git a/rust/src/bridge/callbacks.rs b/rust/src/bridge/callbacks.rs index b1029284..7a381d72 100644 --- a/rust/src/bridge/callbacks.rs +++ b/rust/src/bridge/callbacks.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + pub(crate) mod log; mod macros; use flutter_rust_bridge::DartFnFuture; @@ -15,11 +20,49 @@ tokio::task_local! { pub(crate) static FLUTTER_CALLBACKS: std::sync::RwLock>> = std::sync::RwLock::new(None); +#[derive(Clone, Debug)] +pub struct LegacySignalDecryptResult { + /// Serialized `EncryptedContent` when legacy Signal decryption succeeded. + pub plaintext: Option>, + /// Serialized protobuf enum value for `DecryptionErrorMessage.Type`. + pub decryption_error_type: Option, +} + +#[derive(Clone, Debug)] +pub struct LegacySignalEncryptResult { + pub ciphertext: Vec, + /// `Message.Type.CIPHERTEXT` or `Message.Type.PREKEY_BUNDLE`. + pub message_type: i32, +} + +#[derive(Clone, Debug)] +pub struct LegacySignalPreKey { + pub id: i64, + pub public_key: Vec, +} + // This will also generate the function init_flutter_callbacks which MUST be called from Flutter to initialize the callbacks callback_generator! { FlutterCallbacks { Logging logging { get_stream_sink: () => StreamSink + }, + LegacySignal legacy_signal { + decrypt: (i64, Vec, i32) => LegacySignalDecryptResult, + encrypt: (i64, Vec) => Option, + generate_prekeys: () => Vec + }, + Api api { + resync_signal_session: (i64) => (), + push_key_requested: (i64) => (), + group_membership_error: (i64, String, String) => (), + media_action: (String, String, i64, String) => (), + verification_proof: (i64, Vec) => (), + create_push_data: (i64, Option, Vec, i32) => Option>, + create_push_avatars: (i64) => (), + recovery_changed: () => (), + media_received: (String, i64) => (), + group_state_refresh: (String, bool) => () } } } diff --git a/rust/src/bridge/callbacks/log.rs b/rust/src/bridge/callbacks/log.rs index e1525ed2..b5eaa62c 100644 --- a/rust/src/bridge/callbacks/log.rs +++ b/rust/src/bridge/callbacks/log.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::frb_generated::StreamSink; use tracing_subscriber::fmt::MakeWriter; diff --git a/rust/src/bridge/callbacks/macros.rs b/rust/src/bridge/callbacks/macros.rs index af53a4ee..ba65d634 100644 --- a/rust/src/bridge/callbacks/macros.rs +++ b/rust/src/bridge/callbacks/macros.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + #[macro_export] macro_rules! callback_generator { ( diff --git a/rust/src/bridge/groups.rs b/rust/src/bridge/groups.rs new file mode 100644 index 00000000..71cc6e97 --- /dev/null +++ b/rust/src/bridge/groups.rs @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::{error::Result, services::groups::GroupService}; + +pub async fn create_new_group(group_name: String, member_ids: Vec) -> Result { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx) + .create_group(group_name, member_ids) + .await +} + +pub async fn fetch_group_state(group_id: String) -> Result { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx).fetch_group_state(group_id).await +} + +pub async fn add_hidden_contact(contact_id: i64) -> Result { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx).add_hidden_contact(contact_id).await +} + +pub async fn fetch_group_states_for_unjoined_groups() -> Result<()> { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx) + .fetch_group_states_for_unjoined_groups() + .await +} + +pub async fn fetch_missing_group_public_keys() -> Result<()> { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx) + .fetch_missing_group_public_keys() + .await +} + +pub async fn manage_admin_state( + group_id: String, + _group_public_key: Vec, + contact_id: i64, + remove: bool, +) -> Result { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx) + .manage_admin(group_id, contact_id, remove) + .await +} + +pub async fn update_group_name(group_id: String, group_name: String) -> Result { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx) + .update_group_name(group_id, group_name) + .await +} + +pub async fn update_chat_deletion_time( + group_id: String, + delete_messages_after_milliseconds: i64, +) -> Result { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx) + .update_chat_deletion_time(group_id, delete_messages_after_milliseconds) + .await +} + +pub async fn add_new_group_members(group_id: String, member_ids: Vec) -> Result { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx) + .add_members(group_id, member_ids) + .await +} + +pub async fn remove_member_from_group( + group_id: String, + group_public_key: Vec, + contact_id: i64, +) -> Result { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx) + .remove_member(group_id, group_public_key, contact_id) + .await +} + +pub async fn leave_group(group_id: String) -> Result { + let ctx = crate::context::Context::get_static()?; + GroupService::new(ctx).leave_group(group_id).await +} diff --git a/rust/src/bridge/mod.rs b/rust/src/bridge/mod.rs index 49114e23..9f243550 100644 --- a/rust/src/bridge/mod.rs +++ b/rust/src/bridge/mod.rs @@ -1,9 +1,17 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + #![allow(unexpected_cfgs)] +pub mod api; pub mod callbacks; +pub mod groups; pub mod wrapper; use std::sync::Arc; +use crate::api::runtime::ApiClient; use crate::context::Context; use crate::database::app::AppDatabase; use crate::database::signal::Database; @@ -19,7 +27,7 @@ use flutter_rust_bridge::frb; pub use crate::user_discovery::traits::AnnouncedUser; pub use crate::user_discovery::traits::OtherPromotion; -use tokio::sync::{Mutex, RwLock}; +use tokio::sync::{Mutex, OnceCell, RwLock}; pub struct InitConfig { pub database_dir: String, @@ -54,11 +62,12 @@ pub(crate) struct TwonlyFlutter { pub(crate) secure_storage: SecureStorage, pub(crate) key_manager: Arc>, pub(crate) signal_engine: Arc>>, + pub(crate) api_client: OnceCell>>, } pub(super) fn get_twonly_flutter() -> Result<&'static TwonlyFlutter> { let ctx = Context::get_static()?; - if let Context::Flutter(twonly) = ctx { + if let Context::Flutter(twonly) = &**ctx { Ok(twonly) } else { Err(TwonlyError::Initialization) @@ -69,3 +78,9 @@ pub async fn initialize_twonly_flutter(config: InitConfig) -> Result<()> { Context::init_flutter(config).await?; Ok(()) } + +/// Initializes the complete Rust runtime without Flutter or callback setup. +/// Background executables should call this before using `RustApi`. +pub async fn initialize_twonly_standalone(config: InitConfig) -> Result<()> { + Context::init_standalone(config).await +} diff --git a/rust/src/bridge/wrapper/app_database.rs b/rust/src/bridge/wrapper/app_database.rs index 75589d5e..05f22e6f 100644 --- a/rust/src/bridge/wrapper/app_database.rs +++ b/rust/src/bridge/wrapper/app_database.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use std::path::PathBuf; use crate::bridge::get_twonly_flutter; diff --git a/rust/src/bridge/wrapper/backup.rs b/rust/src/bridge/wrapper/backup.rs index d5f81309..0f516fc0 100644 --- a/rust/src/bridge/wrapper/backup.rs +++ b/rust/src/bridge/wrapper/backup.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use std::path::PathBuf; use crate::backup::backup_archive::BackupArchive; diff --git a/rust/src/bridge/wrapper/key_manager.rs b/rust/src/bridge/wrapper/key_manager.rs index 3899e4f8..dea6cb6f 100644 --- a/rust/src/bridge/wrapper/key_manager.rs +++ b/rust/src/bridge/wrapper/key_manager.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use std::collections::HashMap; use crate::bridge::get_twonly_flutter; diff --git a/rust/src/bridge/wrapper/mod.rs b/rust/src/bridge/wrapper/mod.rs index 886715d8..b000006f 100644 --- a/rust/src/bridge/wrapper/mod.rs +++ b/rust/src/bridge/wrapper/mod.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + pub mod app_database; pub mod backup; pub mod key_manager; diff --git a/rust/src/bridge/wrapper/signal.rs b/rust/src/bridge/wrapper/signal.rs index eb8501b3..d7ebf178 100644 --- a/rust/src/bridge/wrapper/signal.rs +++ b/rust/src/bridge/wrapper/signal.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::bridge::get_twonly_flutter; use crate::error::{Result, TwonlyError}; use crate::signal::engine::{FrbPqcPreKey, FrbPreKeyBundle}; @@ -30,7 +35,7 @@ impl RustSignal { pub async fn encrypt(name: String, device_id: u32, plaintext: Vec) -> Result> { let guard = get_twonly_flutter()?.signal_engine.lock().await; let engine = guard.as_ref().ok_or(TwonlyError::Initialization)?; - Ok(engine.encrypt_message(name, device_id, plaintext).await?) + engine.encrypt_message(name, device_id, plaintext).await } pub async fn decrypt(name: String, device_id: u32, ciphertext: Vec) -> Result> { diff --git a/rust/src/bridge/wrapper/user_discovery.rs b/rust/src/bridge/wrapper/user_discovery.rs index 53de44d2..1bb48190 100644 --- a/rust/src/bridge/wrapper/user_discovery.rs +++ b/rust/src/bridge/wrapper/user_discovery.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::bridge::callbacks::CURRENT_CALLBACK_ID; use crate::bridge::get_twonly_flutter; use crate::error::Result; diff --git a/rust/src/context.rs b/rust/src/context.rs index d7e7e365..9d8d4916 100644 --- a/rust/src/context.rs +++ b/rust/src/context.rs @@ -1,23 +1,33 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::runtime::{ApiClient, ApiRuntime}; +use crate::bridge::InitConfig; use crate::database::app::{AppDatabase, APP_DATABASE_FILE}; +use crate::database::signal::Database; +use crate::error::Result; +use crate::error::TwonlyError; +use crate::keys::DatabaseKey; +use crate::keys::KeyManager; +#[cfg(not(test))] +use crate::log::init_tracing; use crate::signal::engine::RustSignalEngine; use crate::user_discovery::stores::{NativeUserDiscoveryStore, NativeUserDiscoveryUtils}; use crate::user_discovery::UserDiscovery; -use crate::{ - bridge::InitConfig, - database::signal::Database, - error::{Result, TwonlyError}, - keys::{DatabaseKey, KeyManager}, - log::init_tracing, - utils::Shared, -}; +use crate::utils::Shared; +use crate::{bridge::TwonlyFlutter, secure_storage::SecureStorage}; use libsignal_protocol::IdentityKey; use std::{path::PathBuf, sync::Arc}; use tokio::sync::{Mutex, OnceCell, RwLock}; +#[cfg(not(test))] use zeroize::Zeroize; -use crate::{bridge::TwonlyFlutter, secure_storage::SecureStorage}; +#[cfg(not(test))] +static GLOBAL_CONTEXT: OnceCell> = OnceCell::const_new(); -pub(crate) struct TwonlyStandalone { +pub struct TwonlyStandalone { #[allow(dead_code)] pub(crate) config: InitConfig, #[allow(dead_code)] @@ -26,23 +36,53 @@ pub(crate) struct TwonlyStandalone { #[allow(dead_code)] pub(crate) secure_storage: SecureStorage, pub(crate) key_manager: Arc>, + pub(crate) user_discovery: + Shared>, + pub(crate) signal_engine: Arc>>, + pub(crate) api_client: OnceCell>>, } -pub(crate) enum Context { +#[allow(private_interfaces)] // for the test +pub enum Context { Flutter(TwonlyFlutter), Standalone(TwonlyStandalone), } impl Context { - #[cfg(test)] - pub(crate) fn from_standalone(standalone: TwonlyStandalone) -> Self { + pub(crate) fn get_api_client(&self) -> &OnceCell>> { + match self { + Self::Flutter(value) => &value.api_client, + Self::Standalone(value) => &value.api_client, + } + } + + pub(crate) fn data_dir(&self) -> &str { + match self { + Self::Flutter(value) => &value.config.data_dir, + Self::Standalone(value) => &value.config.data_dir, + } + } + + pub(crate) fn get_user_discovery( + &self, + ) -> &Shared> { + match self { + Self::Flutter(value) => &value.user_discovery, + Self::Standalone(value) => &value.user_discovery, + } + } + + pub(crate) fn get_signal_engine(&self) -> &Arc>> { + match self { + Self::Flutter(value) => &value.signal_engine, + Self::Standalone(value) => &value.signal_engine, + } + } + + pub fn from_standalone(standalone: TwonlyStandalone) -> Self { Self::Standalone(standalone) } -} -static GLOBAL_CONTEXT: OnceCell = OnceCell::const_new(); - -impl Context { pub(crate) async fn init_flutter(config: InitConfig) -> Result<()> { Self::init_common(config, true).await } @@ -52,13 +92,10 @@ impl Context { Self::init_common(config, false).await } - #[cfg(test)] - pub(crate) async fn init_for_testing( + pub async fn init_for_testing( database_dir: PathBuf, data_dir: PathBuf, - ) -> Result { - use tokio::sync::Mutex; - + ) -> Result> { std::fs::create_dir_all(&database_dir)?; std::fs::create_dir_all(&data_dir)?; @@ -93,16 +130,72 @@ impl Context { .await?; app_db.run_migrations().await?; let app_db = Arc::new(RwLock::new(Arc::new(app_db))); + let rust_db = Arc::new(RwLock::new(rust_db)); + let key_manager = Arc::new(Mutex::new(key_manager)); + let user_discovery = Shared::new(UserDiscovery::new( + NativeUserDiscoveryStore::new(app_db.clone(), data_dir.to_str().unwrap()), + NativeUserDiscoveryUtils::new(key_manager.clone(), rust_db.clone()), + )?); - Ok(Context::from_standalone(TwonlyStandalone { + let ctx = Arc::new(Context::from_standalone(TwonlyStandalone { config, - rust_db: Arc::new(RwLock::new(rust_db)), + rust_db, app_db, secure_storage, - key_manager: Arc::new(Mutex::new(key_manager)), - })) + key_manager, + user_discovery, + signal_engine: Arc::new(Mutex::new(None)), + api_client: OnceCell::const_new(), + })); + ApiRuntime::initialize(&ctx).await?; + ApiRuntime::connect(&ctx).await?; + Ok(ctx) } + #[doc(hidden)] + #[cfg(any(test, debug_assertions))] + pub async fn inject_test_signal_identity( + &self, + identity_key_pair_structure: Vec, + registration_id: i64, + pre_key_store: std::collections::HashMap>, + ) -> Result<()> { + let mut key_manager = self.get_key_manager().await?; + key_manager.signal_identity = Some(crate::keys::SignalIdentityKey { + identity_key_pair_structure, + registration_id, + pre_key_store, + }); + Ok(()) + } + + #[doc(hidden)] + #[cfg(any(test, debug_assertions))] + pub async fn inject_test_user_id(&self, user_id: i64) -> Result<()> { + let mut key_manager = self.get_key_manager().await?; + key_manager.user_id = Some(user_id); + key_manager.store_to_keychain(self.get_secure_storage())?; + let signal_identity = key_manager.signal_identity.as_ref().map(|identity| { + ( + identity.identity_key_pair_structure.clone(), + identity.registration_id, + ) + }); + drop(key_manager); + + if let Some((identity_key_pair_structure, registration_id)) = signal_identity { + let database = self.get_rust_db().await; + *self.get_signal_engine().lock().await = Some(RustSignalEngine::new_with_pool( + database.pool.clone(), + identity_key_pair_structure, + registration_id as u32, + user_id.to_string(), + )?); + } + Ok(()) + } + + #[cfg(not(test))] async fn init_common(config: InitConfig, is_flutter: bool) -> Result<()> { if GLOBAL_CONTEXT.initialized() { tracing::info!("twonly already initialized. Ensuring storage directories exist."); @@ -125,7 +218,7 @@ impl Context { let app_db_path = database_dir.join(APP_DATABASE_FILE); tracing::info!("Initialized twonly workspace."); - let res: Result<&'static Context> = GLOBAL_CONTEXT + let res: Result<&'static Arc> = GLOBAL_CONTEXT .get_or_try_init(|| async { let key_manager = match KeyManager::try_from_keychain(&secure_storage) { Ok(key) => key, @@ -191,7 +284,7 @@ impl Context { NativeUserDiscoveryStore::new(app_db.clone(), &config.data_dir), NativeUserDiscoveryUtils::new(key_manager.clone(), rust_db_handle.clone()), )?); - Ok(Context::Flutter(TwonlyFlutter { + let ctx = Arc::new(Context::Flutter(TwonlyFlutter { config, secure_storage, rust_db: rust_db_handle, @@ -199,32 +292,70 @@ impl Context { key_manager, user_discovery, signal_engine, - })) + api_client: OnceCell::const_new(), + })); + ApiRuntime::initialize(&ctx).await?; + Ok(ctx) } else { - Ok(Context::Standalone(TwonlyStandalone { + let key_manager = Arc::new(Mutex::new(key_manager)); + let signal_engine = { + let key_manager_guard = key_manager.lock().await; + let engine = match (key_manager_guard.user_id, &key_manager_guard.signal_identity) { + (Some(user_id), Some(identity)) => Some(RustSignalEngine::new_with_pool( + rust_db_handle.read().await.pool.clone(), + identity.identity_key_pair_structure.clone(), + identity.registration_id as u32, + user_id.to_string(), + )?), + _ => None, + }; + Arc::new(Mutex::new(engine)) + }; + let user_discovery = Shared::new(UserDiscovery::new( + NativeUserDiscoveryStore::new(app_db.clone(), &config.data_dir), + NativeUserDiscoveryUtils::new(key_manager.clone(), rust_db_handle.clone()), + )?); + let ctx = Arc::new(Context::Standalone(TwonlyStandalone { config, rust_db: rust_db_handle, app_db, - key_manager: Arc::new(Mutex::new(key_manager)), + key_manager, secure_storage, - })) + user_discovery, + signal_engine, + api_client: OnceCell::const_new(), + })); + ApiRuntime::initialize(&ctx).await?; + Ok(ctx) } }) .await; - res?; + let ctx = res?; + ApiRuntime::connect(ctx).await?; Ok(()) } - pub(super) fn get_static() -> Result<&'static Context> { + #[cfg(test)] + async fn init_common(_config: InitConfig, _is_flutter: bool) -> Result<()> { + Err(TwonlyError::Initialization) + } + + #[cfg(not(test))] + pub(super) fn get_static() -> Result<&'static Arc> { GLOBAL_CONTEXT.get().ok_or(TwonlyError::Initialization) } - // pub(crate) fn get_secure_storage(&self) -> Result<&SecureStorage> { - // match self { - // Self::Flutter(twonly) => Ok(&twonly.secure_storage), - // Self::Standalone(twonly) => Ok(&twonly.secure_storage), - // } - // } + #[cfg(test)] + pub(super) fn get_static() -> Result<&'static Arc> { + Err(TwonlyError::Initialization) + } + + pub(crate) fn get_secure_storage(&self) -> &SecureStorage { + match self { + Self::Flutter(twonly) => &twonly.secure_storage, + Self::Standalone(twonly) => &twonly.secure_storage, + } + } pub(crate) fn get_config(&self) -> Result<&InitConfig> { match self { @@ -240,14 +371,21 @@ impl Context { } } - pub(crate) async fn get_app_database(&self) -> Arc { + pub(crate) async fn user_id(&self) -> Result { + self.get_key_manager() + .await? + .user_id + .ok_or_else(|| TwonlyError::Generic("local user ID is missing".into())) + } + + pub async fn get_app_database(&self) -> Arc { match self { Self::Flutter(twonly) => twonly.app_db.read().await.clone(), Self::Standalone(twonly) => twonly.app_db.read().await.clone(), } } - pub(crate) async fn get_rust_database(&self) -> Arc { + pub(crate) async fn get_rust_db(&self) -> Arc { match self { Self::Flutter(twonly) => twonly.rust_db.read().await.clone(), Self::Standalone(twonly) => twonly.rust_db.read().await.clone(), @@ -255,12 +393,14 @@ impl Context { } pub(crate) async fn get_identity(&self, user_id: i64) -> Result> { - let database = self.get_rust_database().await; - let identity_key: Option> = - sqlx::query_scalar("SELECT identity_key FROM signal_identities WHERE name = ?") - .bind(user_id.to_string()) - .fetch_optional(&database.pool) - .await?; + let database = self.get_rust_db().await; + let user_id = user_id.to_string(); + let identity_key = sqlx::query_scalar!( + r#"SELECT identity_key FROM signal_identities WHERE name = ?"#, + user_id, + ) + .fetch_optional(&database.pool) + .await?; identity_key .map(|bytes| { diff --git a/rust/src/database/app/legacy_import.rs b/rust/src/database/app/legacy_import.rs index 5cba0a46..5a4f6b74 100644 --- a/rust/src/database/app/legacy_import.rs +++ b/rust/src/database/app/legacy_import.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use super::{AppDatabase, MigrationReport, TableMigrationCount, APPLICATION_TABLES}; use crate::error::{Result, TwonlyError}; use sqlx::{Acquire, AssertSqlSafe, Row}; @@ -6,8 +11,12 @@ use std::path::Path; impl AppDatabase { pub async fn is_legacy_import_complete(&self) -> Result { - let value = sqlx::query_scalar::<_, String>( - "SELECT value FROM app_metadata WHERE key = 'legacy_import_complete'", + let value = sqlx::query_scalar!( + r#" + SELECT value + FROM app_metadata + WHERE key = 'legacy_import_complete' + "# ) .fetch_optional(&self.pool) .await?; @@ -16,8 +25,24 @@ impl AppDatabase { pub async fn complete_empty_legacy_import(&self) -> Result { let mut transaction = self.pool.begin().await?; - sqlx::query("INSERT INTO app_metadata(key, value) VALUES('legacy_schema_version', '25') ON CONFLICT(key) DO UPDATE SET value = excluded.value").execute(&mut *transaction).await?; - sqlx::query("INSERT INTO app_metadata(key, value) VALUES('legacy_import_complete', '1') ON CONFLICT(key) DO UPDATE SET value = excluded.value").execute(&mut *transaction).await?; + sqlx::query!( + r#" + INSERT INTO app_metadata(key, value) + VALUES('legacy_schema_version', '25') + ON CONFLICT(key) DO UPDATE SET value = excluded.value + "# + ) + .execute(&mut *transaction) + .await?; + sqlx::query!( + r#" + INSERT INTO app_metadata(key, value) + VALUES('legacy_import_complete', '1') + ON CONFLICT(key) DO UPDATE SET value = excluded.value + "# + ) + .execute(&mut *transaction) + .await?; transaction.commit().await?; self.migration_report().await } @@ -30,12 +55,14 @@ impl AppDatabase { return Err(TwonlyError::DatabaseNotFound); } let mut connection = self.pool.acquire().await?; - sqlx::query("ATTACH DATABASE ? AS legacy KEY ''") + // SQLCipher's KEY clause and the attached schema are not available to + // SQLx's compile-time SQLite connection. + sqlx::query(r#"ATTACH DATABASE ? AS legacy KEY ''"#) .bind(legacy_path.to_string_lossy().as_ref()) .execute(&mut *connection) .await?; let import_result = import(&mut connection).await; - let _ = sqlx::query("DETACH DATABASE legacy") + let _ = sqlx::query(r#"DETACH DATABASE legacy"#) .execute(&mut *connection) .await; let report = import_result?; @@ -44,8 +71,12 @@ impl AppDatabase { } async fn migration_report(&self) -> Result { - let legacy_version = sqlx::query_scalar::<_, String>( - "SELECT value FROM app_metadata WHERE key = 'legacy_schema_version'", + let legacy_version = sqlx::query_scalar!( + r#" + SELECT value + FROM app_metadata + WHERE key = 'legacy_schema_version' + "# ) .fetch_optional(&self.pool) .await? @@ -54,7 +85,7 @@ impl AppDatabase { let mut tables = Vec::with_capacity(APPLICATION_TABLES.len()); for table in APPLICATION_TABLES { let rows = sqlx::query_scalar::<_, i64>(AssertSqlSafe(format!( - "SELECT COUNT(*) FROM \"{table}\"" + r#"SELECT COUNT(*) FROM "{table}""# ))) .fetch_one(&self.pool) .await?; @@ -73,14 +104,14 @@ impl AppDatabase { async fn import( connection: &mut sqlx::pool::PoolConnection, ) -> Result { - let legacy_version: i64 = sqlx::query_scalar("PRAGMA legacy.user_version") + let legacy_version: i64 = sqlx::query_scalar(r#"PRAGMA legacy.user_version"#) .fetch_one(&mut **connection) .await?; if legacy_version != 25 { return Err(TwonlyError::Generic(format!("Legacy database must be upgraded to Drift schema 25 before import; found {legacy_version}"))); } let mut tx = connection.begin().await?; - sqlx::query("PRAGMA defer_foreign_keys = ON") + sqlx::query!(r#"PRAGMA defer_foreign_keys = ON"#) .execute(&mut *tx) .await?; let mut counts = Vec::with_capacity(APPLICATION_TABLES.len()); @@ -96,14 +127,18 @@ async fn import( .map(|column| format!("\"{}\"", column.replace('"', "\"\""))) .collect::>() .join(", "); - sqlx::query(AssertSqlSafe(format!("INSERT OR REPLACE INTO main.\"{table}\" ({quoted}) SELECT {quoted} FROM legacy.\"{table}\""))).execute(&mut *tx).await?; + sqlx::query(AssertSqlSafe(format!( + r#"INSERT OR REPLACE INTO main."{table}" ({quoted}) SELECT {quoted} FROM legacy."{table}""# + ))) + .execute(&mut *tx) + .await?; let source_count: i64 = sqlx::query_scalar(AssertSqlSafe(format!( - "SELECT COUNT(*) FROM legacy.\"{table}\"" + r#"SELECT COUNT(*) FROM legacy."{table}""# ))) .fetch_one(&mut *tx) .await?; let target_count: i64 = sqlx::query_scalar(AssertSqlSafe(format!( - "SELECT COUNT(*) FROM main.\"{table}\"" + r#"SELECT COUNT(*) FROM main."{table}""# ))) .fetch_one(&mut *tx) .await?; @@ -112,8 +147,16 @@ async fn import( "Row count mismatch for {table}: source={source_count}, target={target_count}" ))); } - let mismatch: Option = sqlx::query_scalar(AssertSqlSafe(format!("SELECT 1 FROM (SELECT {quoted} FROM main.\"{table}\" EXCEPT SELECT {quoted} FROM legacy.\"{table}\") LIMIT 1"))).fetch_optional(&mut *tx).await?; - let reverse_mismatch: Option = sqlx::query_scalar(AssertSqlSafe(format!("SELECT 1 FROM (SELECT {quoted} FROM legacy.\"{table}\" EXCEPT SELECT {quoted} FROM main.\"{table}\") LIMIT 1"))).fetch_optional(&mut *tx).await?; + let mismatch: Option = sqlx::query_scalar(AssertSqlSafe(format!( + r#"SELECT 1 FROM (SELECT {quoted} FROM main."{table}" EXCEPT SELECT {quoted} FROM legacy."{table}") LIMIT 1"# + ))) + .fetch_optional(&mut *tx) + .await?; + let reverse_mismatch: Option = sqlx::query_scalar(AssertSqlSafe(format!( + r#"SELECT 1 FROM (SELECT {quoted} FROM legacy."{table}" EXCEPT SELECT {quoted} FROM main."{table}") LIMIT 1"# + ))) + .fetch_optional(&mut *tx) + .await?; if mismatch.is_some() || reverse_mismatch.is_some() { return Err(TwonlyError::Generic(format!( "Data mismatch while importing {table}" @@ -125,8 +168,8 @@ async fn import( }); } copy_sequences(&mut tx).await?; - let foreign_key_errors: i64 = - sqlx::query_scalar("SELECT COUNT(*) FROM pragma_foreign_key_check") + let foreign_key_errors = + sqlx::query_scalar!(r#"SELECT COUNT(*) FROM pragma_foreign_key_check"#) .fetch_one(&mut *tx) .await?; if foreign_key_errors != 0 { @@ -134,8 +177,25 @@ async fn import( "Imported database has {foreign_key_errors} foreign-key violations" ))); } - sqlx::query("INSERT INTO app_metadata(key, value) VALUES('legacy_schema_version', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").bind(legacy_version.to_string()).execute(&mut *tx).await?; - sqlx::query("INSERT INTO app_metadata(key, value) VALUES('legacy_import_complete', '1') ON CONFLICT(key) DO UPDATE SET value = '1'").execute(&mut *tx).await?; + sqlx::query!( + r#" + INSERT INTO app_metadata(key, value) + VALUES('legacy_schema_version', ?) + ON CONFLICT(key) DO UPDATE SET value = excluded.value + "#, + legacy_version.to_string(), + ) + .execute(&mut *tx) + .await?; + sqlx::query!( + r#" + INSERT INTO app_metadata(key, value) + VALUES('legacy_import_complete', '1') + ON CONFLICT(key) DO UPDATE SET value = '1' + "# + ) + .execute(&mut *tx) + .await?; tx.commit().await?; Ok(MigrationReport { legacy_version, @@ -148,7 +208,7 @@ async fn common_columns( table: &str, ) -> Result> { let source_rows = sqlx::query(AssertSqlSafe(format!( - "PRAGMA legacy.table_info(\"{table}\")" + r#"PRAGMA legacy.table_info("{table}")"# ))) .fetch_all(&mut **connection) .await?; @@ -162,7 +222,7 @@ async fn common_columns( .map(|row| row.get::("name")) .collect(); let target_rows = sqlx::query(AssertSqlSafe(format!( - "PRAGMA main.table_info(\"{table}\")" + r#"PRAGMA main.table_info("{table}")"# ))) .fetch_all(&mut **connection) .await?; @@ -184,20 +244,21 @@ async fn copy_sequences(connection: &mut sqlx::Transaction<'_, sqlx::Sqlite>) -> "labels", ] { let source_sequence: Option = - sqlx::query_scalar("SELECT seq FROM legacy.sqlite_sequence WHERE name = ?") + sqlx::query_scalar(r#"SELECT seq FROM legacy.sqlite_sequence WHERE name = ?"#) .bind(table) .fetch_optional(&mut **connection) .await?; if let Some(sequence) = source_sequence { - sqlx::query("DELETE FROM main.sqlite_sequence WHERE name = ?") - .bind(table) - .execute(&mut **connection) - .await?; - sqlx::query("INSERT INTO main.sqlite_sequence(name, seq) VALUES(?, ?)") - .bind(table) - .bind(sequence) + sqlx::query!(r#"DELETE FROM main.sqlite_sequence WHERE name = ?"#, table) .execute(&mut **connection) .await?; + sqlx::query!( + r#"INSERT INTO main.sqlite_sequence(name, seq) VALUES(?, ?)"#, + table, + sequence, + ) + .execute(&mut **connection) + .await?; } } Ok(()) @@ -220,7 +281,7 @@ mod tests { .await .unwrap(); legacy.run_migrations().await.unwrap(); - sqlx::query("PRAGMA user_version = 25") + sqlx::query!(r#"PRAGMA user_version = 25"#) .execute(&legacy.pool) .await .unwrap(); @@ -268,11 +329,16 @@ mod tests { .raw_execute("ROLLBACK".to_owned(), vec![]) .await .unwrap(); - let username: String = - sqlx::query_scalar("SELECT username FROM contacts WHERE user_id = 7") - .fetch_one(&target.pool) - .await - .unwrap(); + let username = sqlx::query_scalar!( + r#" + SELECT username + FROM contacts + WHERE user_id = 7 + "# + ) + .fetch_one(&target.pool) + .await + .unwrap(); assert_eq!(username, "alice"); let legacy_check = AppDatabase::new(legacy_path.to_str().unwrap(), None, true) @@ -295,7 +361,7 @@ mod tests { ]; for (table, column, expected) in signal_checks { let actual: Vec = sqlx::query_scalar(AssertSqlSafe(format!( - "SELECT {column} FROM {table} LIMIT 1" + r#"SELECT {column} FROM {table} LIMIT 1"# ))) .fetch_one(&legacy_check.pool) .await diff --git a/rust/src/database/app/migrations/0002_api_state.sql b/rust/src/database/app/migrations/0002_api_state.sql new file mode 100644 index 00000000..6644bb8b --- /dev/null +++ b/rust/src/database/app/migrations/0002_api_state.sql @@ -0,0 +1,25 @@ +-- State required by the standalone Rust API and client-to-client processor. +CREATE TABLE api_state ( + key TEXT NOT NULL PRIMARY KEY, + value BLOB NOT NULL, + updated_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)) +); + +CREATE TABLE api_outbox ( + sequence_id INTEGER NOT NULL PRIMARY KEY, + payload BLOB NOT NULL, + operation_kind TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)), + attempt_count INTEGER NOT NULL DEFAULT 0, + next_attempt_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)), + expires_at INTEGER +); +CREATE INDEX idx_api_outbox_next_attempt ON api_outbox(next_attempt_at); + +CREATE TABLE contact_push_keys ( + contact_id INTEGER NOT NULL REFERENCES contacts(user_id) ON DELETE CASCADE, + key_id INTEGER NOT NULL, + key BLOB NOT NULL, + created_at INTEGER NOT NULL, + PRIMARY KEY (contact_id, key_id) +); diff --git a/rust/src/database/app/mod.rs b/rust/src/database/app/mod.rs index f5d8aac1..5c6ec5c8 100644 --- a/rust/src/database/app/mod.rs +++ b/rust/src/database/app/mod.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::error::{Result, TwonlyError}; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use sqlx::{AssertSqlSafe, Column, ConnectOptions, Row, SqlitePool, TypeInfo, ValueRef}; @@ -7,9 +12,10 @@ use std::time::Duration; use tokio::sync::broadcast; mod legacy_import; +pub mod tables; pub const APP_DATABASE_FILE: &str = "app_db.sqlite"; -pub const APP_SCHEMA_VERSION: i64 = 1; +pub const APP_SCHEMA_VERSION: i64 = 2; pub const APPLICATION_TABLES: &[&str] = &[ "contacts", @@ -80,10 +86,16 @@ impl AppDatabase { .map_err(|error| { TwonlyError::Generic(format!("App database migration failed: {error}")) })?; - sqlx::query("INSERT INTO app_metadata(key, value) VALUES('schema_version', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value") - .bind(APP_SCHEMA_VERSION.to_string()) - .execute(&self.pool) - .await?; + sqlx::query!( + r#" + INSERT INTO app_metadata(key, value) + VALUES('schema_version', ?) + ON CONFLICT(key) DO UPDATE SET value = excluded.value + "#, + APP_SCHEMA_VERSION.to_string() + ) + .execute(&self.pool) + .await?; Ok(()) } @@ -152,16 +164,18 @@ impl AppDatabase { pub async fn create_backup(&self, output_path: &str, encryption_key: &str) -> Result<()> { let mut connection = self.pool.acquire().await?; - sqlx::query("ATTACH DATABASE ? AS backup KEY ?") + // SQLCipher-only syntax cannot be described by the SQLite database + // used by SQLx during compile-time query preparation. + sqlx::query(r#"ATTACH DATABASE ? AS backup KEY ?"#) .bind(output_path) .bind(encryption_key) .execute(&mut *connection) .await .map_err(|error| TwonlyError::Generic(format!("Attach app backup failed: {error}")))?; - let export = sqlx::query("SELECT sqlcipher_export('backup')") + let export = sqlx::query(r#"SELECT sqlcipher_export('backup')"#) .execute(&mut *connection) .await; - let detach = sqlx::query("DETACH DATABASE backup") + let detach = sqlx::query(r#"DETACH DATABASE backup"#) .execute(&mut *connection) .await; export.map_err(|error| TwonlyError::Generic(format!("App export failed: {error}")))?; diff --git a/rust/src/database/app/tables/contact.rs b/rust/src/database/app/tables/contact.rs new file mode 100644 index 00000000..77796ed4 --- /dev/null +++ b/rust/src/database/app/tables/contact.rs @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::{context::Context, error::Result}; +use sqlx::{Sqlite, Transaction}; + +#[derive(sqlx::FromRow, Debug, Clone)] +pub struct Contact { + pub user_id: i64, + pub username: String, + pub display_name: Option, + pub nick_name: Option, + pub avatar_svg_compressed: Option>, + pub sender_profile_counter: i64, + pub accepted: i64, + pub deleted_by_user: i64, + pub requested: i64, + pub blocked: i64, + pub verified: i64, + pub account_deleted: i64, + pub created_at: i64, + pub signal_version: String, + pub user_discovery_version: Option>, + pub user_discovery_excluded: i64, + pub user_discovery_manual_approved: Option, + pub recovery_is_trusted_friend: i64, + pub recovery_last_heartbeat: Option, + pub recovery_secret_share: Option>, + pub recovery_contacts_secret_share: Option>, + pub recovery_contacts_last_heartbeat: Option, + pub recovery_contacts_threshold: Option, + pub ask_for_friend_promotions: Option, + pub media_send_counter: i64, + pub media_received_counter: i64, +} + +#[derive(bon::Builder)] +pub struct UpdateContact { + user_id: i64, + username: Option, + display_name: Option>, + avatar_svg_compressed: Option>>, + sender_profile_counter: Option, + signal_version: Option, + #[builder(with = |value: bool| value as i64)] + accepted: Option, + #[builder(with = |value: bool| value as i64)] + requested: Option, + #[builder(with = |value: bool| value as i64)] + deleted_by_user: Option, + #[builder(with = |value: bool| value as i64)] + blocked: Option, + #[builder(default)] + only_if_not_requested: bool, +} + +impl UpdateContact { + pub async fn update(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + Contact::update(tr, self).await + } + + pub async fn insert_on_conflict_update(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + Contact::insert_on_conflict_update(tr, self).await + } +} + +impl Contact { + pub fn get_group_name(&self) -> String { + self.display_name + .clone() + .unwrap_or_else(|| self.username.clone()) + } + + pub async fn update(tr: &mut Transaction<'_, Sqlite>, contact: UpdateContact) -> Result<()> { + let update_display_name = contact.display_name.is_some(); + let display_name = contact.display_name.flatten(); + let update_avatar = contact.avatar_svg_compressed.is_some(); + let avatar_svg_compressed = contact.avatar_svg_compressed.flatten(); + sqlx::query!( + r#" + UPDATE contacts SET + username = COALESCE(?, username), + display_name = CASE WHEN ? THEN ? ELSE display_name END, + avatar_svg_compressed = CASE WHEN ? THEN ? ELSE avatar_svg_compressed END, + sender_profile_counter = COALESCE(?, sender_profile_counter), + signal_version = COALESCE(?, signal_version), + accepted = COALESCE(?, accepted), + requested = COALESCE(?, requested), + deleted_by_user = COALESCE(?, deleted_by_user), + blocked = COALESCE(?, blocked) + WHERE user_id = ? AND (? = 0 OR requested = 0) + "#, + contact.username, + update_display_name, + display_name, + update_avatar, + avatar_svg_compressed, + contact.sender_profile_counter, + contact.signal_version, + contact.accepted, + contact.requested, + contact.deleted_by_user, + contact.blocked, + contact.user_id, + contact.only_if_not_requested, + ) + .execute(&mut **tr) + .await?; + + Ok(()) + } + + pub async fn insert_on_conflict_update( + tr: &mut Transaction<'_, Sqlite>, + contact: UpdateContact, + ) -> Result<()> { + sqlx::query!( + r#" + INSERT INTO contacts(user_id, username, signal_version, accepted, requested, deleted_by_user, blocked) + VALUES (?, COALESCE(?, '[Unknown]'), COALESCE(?, 'v1'), COALESCE(?, 0), COALESCE(?, 0), COALESCE(?, 0), COALESCE(?, 0)) + ON CONFLICT(user_id) DO UPDATE SET + username = COALESCE(?, contacts.username), + signal_version = COALESCE(?, contacts.signal_version), + accepted = COALESCE(?, contacts.accepted), + requested = COALESCE(?, contacts.requested), + deleted_by_user = COALESCE(?, contacts.deleted_by_user), + blocked = COALESCE(?, contacts.blocked) + WHERE ? = 0 OR contacts.requested = 0 + "#, + contact.user_id, + contact.username, + contact.signal_version, + contact.accepted, + contact.requested, + contact.deleted_by_user, + contact.blocked, + contact.username, + contact.signal_version, + contact.accepted, + contact.requested, + contact.deleted_by_user, + contact.blocked, + contact.only_if_not_requested, + ) + .execute(&mut **tr) + .await?; + + Ok(()) + } + + pub async fn get_contact_by_id( + transaction: &mut Transaction<'_, Sqlite>, + user_id: i64, + ) -> Result> { + let contact = sqlx::query_as!(Self, "SELECT * FROM contacts WHERE user_id = ?", user_id) + .fetch_optional(&mut **transaction) + .await?; + + Ok(contact) + } + + pub async fn update_ask_for_friend_promotions( + transaction: &mut Transaction<'_, Sqlite>, + user_id: i64, + ) -> Result<()> { + sqlx::query!( + r#" + UPDATE contacts + SET ask_for_friend_promotions = COALESCE(ask_for_friend_promotions, 1) + WHERE user_id = ? + "#, + user_id, + ) + .execute(&mut **transaction) + .await?; + Ok(()) + } + + pub async fn is_user_discovery_allowed( + context: &Context, + t: &mut Transaction<'_, Sqlite>, + contact_id: i64, + ) -> Result { + let config = crate::user_config::UserConfig::load_required_from(context)?; + let contact = sqlx::query!( + r#"SELECT accepted, blocked, media_send_counter, user_discovery_excluded, + user_discovery_manual_approved + FROM contacts WHERE user_id = ?"#, + contact_id, + ) + .fetch_optional(&mut **t) + .await?; + Ok(contact.is_some_and(|contact| { + contact.accepted != 0 + && contact.blocked == 0 + && contact.media_send_counter >= config.required_send_images + && contact.user_discovery_excluded == 0 + && (!config.user_discovery_requires_manual_approval + || contact.user_discovery_manual_approved.unwrap_or(0) != 0) + })) + } + + pub async fn exists(transaction: &mut Transaction<'_, Sqlite>, user_id: i64) -> Result { + let exists = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM contacts WHERE user_id = ?)", + user_id + ) + .fetch_one(&mut **transaction) + .await? + != 0; + Ok(exists) + } +} diff --git a/rust/src/database/app/tables/group_history.rs b/rust/src/database/app/tables/group_history.rs new file mode 100644 index 00000000..e74f0f3d --- /dev/null +++ b/rust/src/database/app/tables/group_history.rs @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use sqlx::{Sqlite, Transaction}; + +use crate::{error::Result, utils::new_uuid_v4}; + +#[derive(Clone, Copy)] +pub enum GroupHistoryType { + UpdatedContactUsername, + UpdatedContactDisplayName, +} + +impl GroupHistoryType { + fn as_str(self) -> &'static str { + match self { + Self::UpdatedContactUsername => "updatedContactUsername", + Self::UpdatedContactDisplayName => "updatedContactDisplayName", + } + } +} + +pub struct InsertGroupHistories<'a> { + contact_id: i64, + old_group_name: &'a str, + new_group_name: &'a str, + history_type: GroupHistoryType, +} + +impl<'a> InsertGroupHistories<'a> { + pub fn new( + contact_id: i64, + old_group_name: &'a str, + new_group_name: &'a str, + history_type: GroupHistoryType, + ) -> Self { + Self { + contact_id, + old_group_name, + new_group_name, + history_type, + } + } + + pub async fn insert(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + let group_ids = sqlx::query_scalar!( + "SELECT group_id FROM group_members WHERE contact_id = ?", + self.contact_id, + ) + .fetch_all(&mut **tr) + .await?; + let history_type = self.history_type.as_str(); + + for group_id in group_ids { + let group_history_id = new_uuid_v4(); + sqlx::query!( + r#" + INSERT INTO group_histories( + group_history_id, + group_id, + contact_id, + old_group_name, + new_group_name, + type + ) VALUES (?, ?, ?, ?, ?, ?) + "#, + group_history_id, + group_id, + self.contact_id, + self.old_group_name, + self.new_group_name, + history_type, + ) + .execute(&mut **tr) + .await?; + } + + Ok(()) + } +} diff --git a/rust/src/database/app/tables/groups.rs b/rust/src/database/app/tables/groups.rs new file mode 100644 index 00000000..f058c545 --- /dev/null +++ b/rust/src/database/app/tables/groups.rs @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use sqlx::{Sqlite, Transaction}; + +use crate::context::Context; +use crate::error::{Result, TwonlyError}; + +use super::Contact; + +const MAX_FUTURE_TIMESTAMP_SKEW_SECONDS: i64 = 10 * 60; + +pub struct Group; + +impl Group { + pub async fn create_direct_chat( + ctx: &Context, + tr: &mut Transaction<'_, Sqlite>, + contact: Contact, + ) -> Result<()> { + let local_user_id = ctx.user_id().await?; + let contact_id = contact.user_id; + let group_id = Self::direct_chat_id(local_user_id, contact_id); + let group_name = contact.get_group_name(); + + sqlx::query!( + r#" + INSERT INTO groups(group_id, group_name, is_direct_chat, is_group_admin, joined_group) + VALUES (?, ?, 1, 1, 1) + ON CONFLICT(group_id) DO UPDATE SET joined_group = 1 + "#, + group_id, + group_name, + ) + .execute(&mut **tr) + .await?; + + sqlx::query!( + "INSERT OR IGNORE INTO group_members(group_id, contact_id) VALUES (?, ?)", + group_id, + contact_id, + ) + .execute(&mut **tr) + .await?; + + Ok(()) + } + + pub fn direct_chat_id(a: i64, b: i64) -> String { + let (high, low) = if a >= b { + (a as u64, b as u64) + } else { + (b as u64, a as u64) + }; + let hex = format!("{high:016x}{low:016x}"); + format!( + "{}-{}-{}-{}-{}", + &hex[0..8], + &hex[8..12], + &hex[12..16], + &hex[16..20], + &hex[20..32] + ) + } + + pub async fn increase_last_message_exchange_to_now( + tr: &mut Transaction<'_, Sqlite>, + group_id: &str, + ) -> Result<()> { + let now = current_unix_timestamp()?; + Self::increase_last_message_exchange(tr, group_id, now).await + } + + pub async fn increase_last_message_exchange( + tr: &mut Transaction<'_, Sqlite>, + group_id: &str, + timestamp: i64, + ) -> Result<()> { + let now = current_unix_timestamp()?; + let timestamp = timestamp.min(now.saturating_add(MAX_FUTURE_TIMESTAMP_SKEW_SECONDS)); + + sqlx::query!( + r#" + UPDATE groups + SET last_message_exchange = MAX(last_message_exchange, ?) + WHERE group_id = ? + "#, + timestamp, + group_id, + ) + .execute(&mut **tr) + .await?; + + Ok(()) + } +} + +#[derive(bon::Builder)] +pub struct InsertGroup { + group_id: String, + group_name: String, + state_encryption_key: Vec, + my_group_private_key: Vec, +} + +impl InsertGroup { + pub async fn execute(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + sqlx::query!( + r#" + INSERT INTO groups( + group_id, + group_name, + is_group_admin, + state_encryption_key, + state_version_id, + my_group_private_key, + joined_group + ) VALUES (?, ?, 1, ?, 1, ?, 1) + "#, + self.group_id, + self.group_name, + self.state_encryption_key, + self.my_group_private_key + ) + .execute(&mut **tr) + .await?; + Ok(()) + } +} + +#[derive(bon::Builder)] +pub struct UpdateGroup { + group_id: String, + group_name: Option, + delete_messages_after_milliseconds: Option>, + is_group_admin: Option, + joined_group: Option, + left_group: Option, + state_version_id: Option, +} + +impl UpdateGroup { + pub async fn execute(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + let is_group_admin = self.is_group_admin.map(|v| v as i64); + let joined_group = self.joined_group.map(|v| v as i64); + let left_group = self.left_group.map(|v| v as i64); + let state_version_id = self.state_version_id.map(|v| v as i64); + // Note: For partial updates, it's easier to coalesce in SQL, but sqlx query! requires static queries. + // If we want to dynamically update, we might need a specific query for apply_state and others. + // For apply_state: + if let ( + Some(group_name), + Some(delete_ms), + Some(is_admin), + Some(joined), + Some(left), + Some(version), + ) = ( + self.group_name, + self.delete_messages_after_milliseconds, + is_group_admin, + joined_group, + left_group, + state_version_id, + ) { + sqlx::query!( + r#" + UPDATE groups + SET group_name = ?, + delete_messages_after_milliseconds = COALESCE(?, delete_messages_after_milliseconds), + is_group_admin = ?, + joined_group = ?, + left_group = ?, + state_version_id = ? + WHERE group_id = ? + "#, + group_name, + delete_ms, + is_admin, + joined, + left, + version, + self.group_id + ) + .execute(&mut **tr) + .await?; + } else if let Some(left_group) = left_group { + sqlx::query!( + "UPDATE groups SET left_group = ? WHERE group_id = ?", + left_group, + self.group_id + ) + .execute(&mut **tr) + .await?; + } + Ok(()) + } +} + +#[derive(bon::Builder)] +pub struct InsertGroupMember { + group_id: String, + contact_id: i64, + member_state: String, + #[builder(default)] + on_conflict_update: bool, +} + +impl InsertGroupMember { + pub async fn execute(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + if self.on_conflict_update { + sqlx::query!( + r#" + INSERT INTO group_members(group_id, contact_id, member_state) + VALUES (?, ?, ?) + ON CONFLICT(group_id, contact_id) + DO UPDATE SET member_state = excluded.member_state + "#, + self.group_id, + self.contact_id, + self.member_state + ) + .execute(&mut **tr) + .await?; + } else { + sqlx::query!( + "INSERT INTO group_members(group_id, contact_id, member_state) VALUES (?, ?, ?)", + self.group_id, + self.contact_id, + self.member_state + ) + .execute(&mut **tr) + .await?; + } + Ok(()) + } +} + +#[derive(bon::Builder)] +pub struct DeleteGroupMembers { + group_id: String, +} + +impl DeleteGroupMembers { + pub async fn execute(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + sqlx::query!( + "DELETE FROM group_members WHERE group_id = ?", + self.group_id + ) + .execute(&mut **tr) + .await?; + Ok(()) + } +} + +#[derive(bon::Builder)] +pub struct UpdateGroupMemberState { + group_id: String, + member_state: String, +} + +impl UpdateGroupMemberState { + pub async fn execute(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + sqlx::query!( + "UPDATE group_members SET member_state = ? WHERE group_id = ?", + self.member_state, + self.group_id + ) + .execute(&mut **tr) + .await?; + Ok(()) + } +} + +#[derive(bon::Builder)] +pub struct GetGroupPublicKey { + group_id: String, + contact_id: i64, +} + +impl GetGroupPublicKey { + pub async fn fetch(self, tr: &mut Transaction<'_, Sqlite>) -> Result>> { + let key = sqlx::query_scalar!( + "SELECT group_public_key FROM group_members WHERE group_id = ? AND contact_id = ?", + self.group_id, + self.contact_id + ) + .fetch_optional(&mut **tr) + .await? + .flatten(); + Ok(key) + } + + // For pool query compatibility + pub async fn fetch_pool(self, pool: &sqlx::Pool) -> Result>> { + let key = sqlx::query_scalar!( + "SELECT group_public_key FROM group_members WHERE group_id = ? AND contact_id = ?", + self.group_id, + self.contact_id + ) + .fetch_optional(pool) + .await? + .flatten(); + Ok(key) + } +} + +#[derive(bon::Builder)] +pub struct UpdateGroupPublicKey { + group_id: String, + contact_id: i64, + group_public_key: Vec, +} + +impl UpdateGroupPublicKey { + pub async fn execute(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + sqlx::query!( + "UPDATE group_members SET group_public_key = ? WHERE group_id = ? AND contact_id = ?", + self.group_public_key, + self.group_id, + self.contact_id + ) + .execute(&mut **tr) + .await?; + Ok(()) + } +} + +#[derive(bon::Builder)] +pub struct InsertGroupHistory { + group_id: String, + affected_contact_id: Option, + new_group_name: Option, + new_delete_messages_after_milliseconds: Option, + r#type: String, +} + +impl InsertGroupHistory { + pub async fn execute(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + let history_id = crate::utils::new_uuid_v4(); + sqlx::query!( + r#" + INSERT INTO group_histories( + group_history_id, + group_id, + affected_contact_id, + new_group_name, + new_delete_messages_after_milliseconds, + type + ) VALUES (?, ?, ?, ?, ?, ?) + "#, + history_id, + self.group_id, + self.affected_contact_id, + self.new_group_name, + self.new_delete_messages_after_milliseconds, + self.r#type + ) + .execute(&mut **tr) + .await?; + Ok(()) + } +} + +#[derive(bon::Builder)] +pub struct GetUnjoinedGroups {} + +impl GetUnjoinedGroups { + pub async fn fetch_all(self, pool: &sqlx::Pool) -> Result> { + let ids = sqlx::query_scalar!( + "SELECT group_id FROM groups WHERE joined_group = 0 AND left_group = 0" + ) + .fetch_all(pool) + .await?; + Ok(ids) + } +} + +pub struct MissingGroupPublicKeyRow { + pub group_id: String, + pub contact_id: i64, +} + +#[derive(bon::Builder)] +pub struct GetMissingGroupPublicKeys {} + +impl GetMissingGroupPublicKeys { + pub async fn fetch_all( + self, + pool: &sqlx::Pool, + ) -> Result> { + let rows = sqlx::query_as!( + MissingGroupPublicKeyRow, + r#" + SELECT group_id, contact_id + FROM group_members + WHERE group_public_key IS NULL + AND last_message >= CAST(strftime('%s','now','-2 days') AS INTEGER) + "# + ) + .fetch_all(pool) + .await?; + Ok(rows) + } +} + +fn current_unix_timestamp() -> Result { + Ok(std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_err(|error| TwonlyError::Generic(error.to_string()))? + .as_secs() as i64) +} diff --git a/rust/src/database/app/tables/key_verification.rs b/rust/src/database/app/tables/key_verification.rs new file mode 100644 index 00000000..1f02f1e2 --- /dev/null +++ b/rust/src/database/app/tables/key_verification.rs @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use sqlx::{Sqlite, Transaction}; + +use crate::error::Result; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum KeyVerificationType { + ContactSharedByVerified, +} + +impl KeyVerificationType { + fn as_str(self) -> &'static str { + match self { + Self::ContactSharedByVerified => "contactSharedByVerified", + } + } +} + +pub struct NewKeyVerification { + contact_id: i64, + verification_type: KeyVerificationType, + verified_by: Option, +} + +impl NewKeyVerification { + pub fn new(contact_id: i64, verification_type: KeyVerificationType) -> Self { + Self { + contact_id, + verification_type, + verified_by: None, + } + } + + pub fn verified_by(mut self, contact_id: i64) -> Self { + self.verified_by = Some(contact_id); + self + } + + pub async fn insert(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + KeyVerification::insert(tr, self).await + } +} + +pub struct KeyVerification; + +impl KeyVerification { + pub async fn insert( + tr: &mut Transaction<'_, Sqlite>, + verification: NewKeyVerification, + ) -> Result<()> { + let verification_type = verification.verification_type.as_str(); + sqlx::query!( + r#" + INSERT INTO key_verifications(contact_id, type, verified_by) + VALUES (?, ?, ?) + "#, + verification.contact_id, + verification_type, + verification.verified_by, + ) + .execute(&mut **tr) + .await?; + + Ok(()) + } +} diff --git a/rust/src/database/app/tables/message.rs b/rust/src/database/app/tables/message.rs new file mode 100644 index 00000000..aa07cd2f --- /dev/null +++ b/rust/src/database/app/tables/message.rs @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use sqlx::{Sqlite, Transaction}; + +use crate::error::{Result, TwonlyError}; + +pub struct Message; + +pub enum MessageType<'a> { + Text, + Media, + Reaction, + Other(&'a str), +} + +#[derive(bon::Builder)] +pub struct NewMessage<'a> { + group_id: &'a str, + message_id: &'a str, + sender_id: Option, + message_type: MessageType<'a>, + content: Option<&'a str>, + media_id: Option<&'a str>, + additional_message_data: Option<&'a [u8]>, + quotes_message_id: Option<&'a str>, + created_at: i64, + ack_by_server: Option, +} + +impl<'a> NewMessage<'a> { + pub async fn insert(self, tr: &mut Transaction<'_, Sqlite>) -> Result<()> { + Message::insert(tr, self).await + } +} + +impl Message { + pub async fn insert(tr: &mut Transaction<'_, Sqlite>, message: NewMessage<'_>) -> Result<()> { + sqlx::query!( + r#" + INSERT INTO messages( + group_id, + message_id, + sender_id, + type, + content, + media_id, + additional_message_data, + quotes_message_id, + created_at, + ack_by_server + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(message_id) DO NOTHING + "#, + message.group_id, + message.message_id, + message.sender_id, + message.message_type.as_str(), + message.content, + message.media_id, + message.additional_message_data, + message.quotes_message_id, + message.created_at, + message.ack_by_server, + ) + .execute(&mut **tr) + .await?; + + Ok(()) + } + + pub async fn check_message_owner( + t: &mut Transaction<'_, Sqlite>, + message_id: &str, + from_user_id: i64, + ) -> Result<()> { + let owner = sqlx::query_scalar!( + r#" + SELECT sender_id + FROM messages + WHERE message_id = ? + "#, + message_id, + ) + .fetch_optional(&mut **t) + .await? + .flatten(); + + if owner.is_some_and(|owner| owner != from_user_id) { + return Err(TwonlyError::Generic( + "attempted cross-sender message overwrite".into(), + )); + } + + Ok(()) + } +} + +impl<'a> MessageType<'a> { + pub fn as_str(&self) -> &'a str { + match self { + Self::Text => "text", + Self::Media => "media", + Self::Reaction => "reaction", + Self::Other(s) => s, + } + } +} diff --git a/rust/src/database/app/tables/mod.rs b/rust/src/database/app/tables/mod.rs new file mode 100644 index 00000000..ae2c592a --- /dev/null +++ b/rust/src/database/app/tables/mod.rs @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +pub mod contact; +pub mod group_history; +pub mod groups; +pub mod key_verification; +pub mod message; +pub mod receipt; + +pub use contact::{Contact, UpdateContact}; +pub use group_history::{GroupHistoryType, InsertGroupHistories}; +pub use groups::{ + DeleteGroupMembers, GetGroupPublicKey, GetMissingGroupPublicKeys, GetUnjoinedGroups, Group, + InsertGroup, InsertGroupHistory, InsertGroupMember, UpdateGroup, UpdateGroupMemberState, +}; +pub use key_verification::{KeyVerification, KeyVerificationType, NewKeyVerification}; +pub use message::{Message, MessageType, NewMessage}; +pub use receipt::{NewReceipt, Receipt}; diff --git a/rust/src/database/app/tables/receipt.rs b/rust/src/database/app/tables/receipt.rs new file mode 100644 index 00000000..8e7b6441 --- /dev/null +++ b/rust/src/database/app/tables/receipt.rs @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::error::Result; +use sqlx::{Sqlite, Transaction}; + +#[derive(sqlx::FromRow, Debug, Clone)] +pub struct Receipt { + pub receipt_id: String, + pub contact_id: i64, + pub message_id: Option, + pub message: Vec, + pub contact_will_sends_receipt: i64, + pub will_be_retried_by_media_upload: i64, + pub mark_for_retry: Option, + pub mark_for_retry_after_accepted: Option, + pub ack_by_server_at: Option, + pub retry_count: i64, + pub last_retry: Option, + pub created_at: i64, +} +pub struct NewReceipt<'a> { + receipt_id: &'a str, + contact_id: i64, + message: &'a [u8], + contact_will_send_receipt: bool, +} + +impl Receipt { + pub async fn claim_received( + transaction: &mut Transaction<'_, Sqlite>, + receipt_id: &str, + ) -> Result { + let claimed = sqlx::query!( + r#" + INSERT INTO received_receipts(receipt_id) + VALUES (?) + ON CONFLICT(receipt_id) DO NOTHING + "#, + receipt_id, + ) + .execute(&mut **transaction) + .await? + .rows_affected() + != 0; + + Ok(claimed) + } + + pub async fn claim_received_retry( + transaction: &mut Transaction<'_, Sqlite>, + receipt_id: &str, + ) -> Result { + let claimed = sqlx::query!( + r#" + UPDATE received_receipts + SET created_at = CAST(strftime('%s', 'now') AS INTEGER) + WHERE receipt_id = ? + AND created_at <= CAST(strftime('%s', 'now') AS INTEGER) - 864000 + "#, + receipt_id, + ) + .execute(&mut **transaction) + .await? + .rows_affected() + != 0; + + Ok(claimed) + } + + pub async fn mark_all_for_retry( + transaction: &mut Transaction<'_, Sqlite>, + contact_id: i64, + ) -> Result<()> { + sqlx::query!( + r#" + UPDATE receipts + SET mark_for_retry = CAST(strftime('%s', 'now') AS INTEGER) + WHERE contact_id = ? AND mark_for_retry IS NULL + "#, + contact_id, + ) + .execute(&mut **transaction) + .await?; + + Ok(()) + } + + pub async fn delete<'c, E>(executor: E, receipt_id: &str) -> Result<()> + where + E: sqlx::Executor<'c, Database = Sqlite>, + { + sqlx::query!("DELETE FROM receipts WHERE receipt_id = ?", receipt_id) + .execute(executor) + .await?; + Ok(()) + } +} + +impl<'a> NewReceipt<'a> { + pub fn new(receipt_id: &'a str, contact_id: i64, message: &'a [u8]) -> Self { + Self { + receipt_id, + contact_id, + message, + contact_will_send_receipt: true, + } + } + + pub fn contact_will_send_receipt(mut self, val: bool) -> Self { + self.contact_will_send_receipt = val; + self + } + + pub async fn insert(&self, transaction: &mut Transaction<'_, Sqlite>) -> Result<()> { + sqlx::query!( + r#" + INSERT INTO receipts(receipt_id, contact_id, message, contact_will_sends_receipt) + VALUES (?, ?, ?, ?) + "#, + self.receipt_id, + self.contact_id, + self.message, + self.contact_will_send_receipt, + ) + .execute(&mut **transaction) + .await?; + + Ok(()) + } + + pub async fn insert_or_replace(&self, transaction: &mut Transaction<'_, Sqlite>) -> Result<()> { + sqlx::query!( + r#" + INSERT OR REPLACE INTO receipts(receipt_id, contact_id, message, contact_will_sends_receipt) + VALUES (?, ?, ?, ?) + "#, + self.receipt_id, + self.contact_id, + self.message, + self.contact_will_send_receipt, + ) + .execute(&mut **transaction) + .await?; + + Ok(()) + } +} diff --git a/rust/src/database/mod.rs b/rust/src/database/mod.rs index 4b70c599..24d7c7e6 100644 --- a/rust/src/database/mod.rs +++ b/rust/src/database/mod.rs @@ -1,2 +1,7 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + pub mod app; pub mod signal; diff --git a/rust/src/database/signal/mod.rs b/rust/src/database/signal/mod.rs index 7c66a802..95bb20e6 100644 --- a/rust/src/database/signal/mod.rs +++ b/rust/src/database/signal/mod.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::error::{Result, TwonlyError}; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use sqlx::{ConnectOptions, SqlitePool}; @@ -68,24 +73,26 @@ impl Database { .await .map_err(|e| TwonlyError::Generic(e.to_string()))?; - sqlx::query("ATTACH DATABASE ? AS backup KEY ?") + // SQLCipher-only syntax cannot be described by the SQLite database + // used by SQLx during compile-time query preparation. + sqlx::query(r#"ATTACH DATABASE ? AS backup KEY ?"#) .bind(output_path) .bind(key) .execute(&mut *conn) .await .map_err(|e| TwonlyError::Generic(format!("Attach failed: {}", e)))?; - sqlx::query("SELECT sqlcipher_export('backup')") + sqlx::query(r#"SELECT sqlcipher_export('backup')"#) .execute(&mut *conn) .await .map_err(|e| TwonlyError::Generic(format!("Export failed: {}", e)))?; - sqlx::query("DETACH DATABASE backup") + sqlx::query(r#"DETACH DATABASE backup"#) .execute(&mut *conn) .await .map_err(|e| TwonlyError::Generic(format!("Detach failed: {}", e)))?; } else { - sqlx::query("VACUUM INTO ?") + sqlx::query(r#"VACUUM INTO ?"#) .bind(output_path) .execute(&self.pool) .await @@ -95,17 +102,19 @@ impl Database { } pub(crate) async fn check_integrity(&self) -> Result<()> { - let row: (String,) = sqlx::query_as("PRAGMA integrity_check") + let integrity = sqlx::query_scalar!(r#"PRAGMA integrity_check"#) .fetch_one(&self.pool) .await .map_err(|e| TwonlyError::Generic(format!("Integrity check query failed: {}", e)))?; + let integrity = integrity + .ok_or_else(|| TwonlyError::Generic("Integrity check returned no result".to_owned()))?; - if row.0.to_lowercase() == "ok" { + if integrity.to_lowercase() == "ok" { Ok(()) } else { Err(TwonlyError::Generic(format!( "Database integrity check failed: {}", - row.0 + integrity ))) } } diff --git a/rust/src/error.rs b/rust/src/error.rs index 73d19019..3a3cca99 100644 --- a/rust/src/error.rs +++ b/rust/src/error.rs @@ -1,11 +1,29 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::user_discovery::error::UserDiscoveryError; use hex::FromHexError; use scrypt::errors::{InvalidOutputLen, InvalidParams}; +use std::string::FromUtf8Error; use thiserror::Error; use zip::result::ZipError; pub type Result = core::result::Result; +macro_rules! twonly_error { + ($message:expr) => { + $crate::error::TwonlyError::Located { + message: ($message).into(), + file: file!(), + line: line!(), + } + }; +} + +pub(crate) use twonly_error; + #[derive(Error, Debug)] pub enum TwonlyError { #[error("global twonly is not initialized")] @@ -46,6 +64,19 @@ pub enum TwonlyError { #[error("{0}")] Generic(String), + #[error("{message} at {file}:{line}")] + Located { + message: String, + file: &'static str, + line: u32, + }, + + #[error("API error code {0}")] + Api(i32), + + #[error("API user response is missing required field: {0}")] + ApiResponseMissingField(&'static str), + #[error("{0}")] IoError(#[from] std::io::Error), @@ -58,9 +89,21 @@ pub enum TwonlyError { #[error("{0}")] Postcard(#[from] postcard::Error), + #[error("Protobuf decoding error: {0}")] + ProtobufDecode(#[from] prost::DecodeError), + + #[error("Unknown protobuf enum value: {0}")] + UnknownProtobufEnumValue(#[from] prost::UnknownEnumValue), + + #[error(transparent)] + SealedSender(#[from] crate::sealed_sender::SealedSenderError), + #[error("{0}")] HexError(#[from] FromHexError), + #[error("invalid UTF-8 string: {0}")] + Utf8(#[from] FromUtf8Error), + #[error("{0}")] InvalidParams(#[from] InvalidParams), #[error("{0}")] diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index b3c62672..9715cc67 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1155052375; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -392823520; // Section: executor @@ -46,6 +46,232 @@ flutter_rust_bridge::frb_generated_default_handler!(); // Section: wire_funcs +fn wire__crate__bridge__groups__add_hidden_contact_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "add_hidden_contact", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_contact_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::groups::add_hidden_contact(api_contact_id).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__groups__add_new_group_members_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "add_new_group_members", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api_member_ids = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::groups::add_new_group_members( + api_group_id, + api_member_ids, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__groups__create_new_group_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "create_new_group", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_name = ::sse_decode(&mut deserializer); + let api_member_ids = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::groups::create_new_group(api_group_name, api_member_ids) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__groups__fetch_group_state_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "fetch_group_state", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::groups::fetch_group_state(api_group_id).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__groups__fetch_group_states_for_unjoined_groups_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "fetch_group_states_for_unjoined_groups", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::groups::fetch_group_states_for_unjoined_groups().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__groups__fetch_missing_group_public_keys_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "fetch_missing_group_public_keys", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::groups::fetch_missing_group_public_keys().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_current_version_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -155,41 +381,28 @@ fn wire__crate__bridge__callbacks__init_flutter_callbacks_impl( rust_vec_len_: i32, data_len_: i32, ) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( - flutter_rust_bridge::for_generated::TaskInfo { - debug_name: "init_flutter_callbacks", - port: Some(port_), - mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, - }, - move || { - let message = unsafe { - flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( - ptr_, - rust_vec_len_, - data_len_, - ) - }; - let mut deserializer = - flutter_rust_bridge::for_generated::SseDeserializer::new(message); + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "init_flutter_callbacks", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || { + let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) }; + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); let api_callback_id = ::sse_decode(&mut deserializer); - let api_logging_get_stream_sink = - decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( - ::sse_decode(&mut deserializer), - ); - deserializer.end(); - move |context| { - transform_result_sse::<_, ()>((move || { - let output_ok = Result::<_, ()>::Ok({ - crate::bridge::callbacks::init_flutter_callbacks( - api_callback_id, - api_logging_get_stream_sink, - ); - })?; - Ok(output_ok) - })()) - } - }, - ) +let api_logging_get_stream_sink = decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(::sse_decode(&mut deserializer)); +let api_legacy_signal_decrypt = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException(::sse_decode(&mut deserializer)); +let api_legacy_signal_encrypt = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException(::sse_decode(&mut deserializer)); +let api_legacy_signal_generate_prekeys = decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_resync_signal_session = decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_push_key_requested = decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_group_membership_error = decode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_media_action = decode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_verification_proof = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_create_push_data = decode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_create_push_avatars = decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_recovery_changed = decode_DartFn_Inputs__Output_unit_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_media_received = decode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException(::sse_decode(&mut deserializer)); +let api_api_group_state_refresh = decode_DartFn_Inputs_String_bool_Output_unit_AnyhowException(::sse_decode(&mut deserializer));deserializer.end(); move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_callback_id, api_logging_get_stream_sink, api_legacy_signal_decrypt, api_legacy_signal_encrypt, api_legacy_signal_generate_prekeys, api_api_resync_signal_session, api_api_push_key_requested, api_api_group_membership_error, api_api_media_action, api_api_verification_proof, api_api_create_push_data, api_api_create_push_avatars, api_api_recovery_changed, api_api_media_received, api_api_group_state_refresh); })?; Ok(output_ok) + })()) + } }) } fn wire__crate__bridge__initialize_twonly_flutter_impl( port_: flutter_rust_bridge::for_generated::MessagePort, @@ -228,6 +441,2357 @@ fn wire__crate__bridge__initialize_twonly_flutter_impl( }, ) } +fn wire__crate__bridge__initialize_twonly_standalone_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "initialize_twonly_standalone", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_config = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::initialize_twonly_standalone(api_config).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__groups__leave_group_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "leave_group", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::groups::leave_group(api_group_id).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__groups__manage_admin_state_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "manage_admin_state", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api__group_public_key = >::sse_decode(&mut deserializer); + let api_contact_id = ::sse_decode(&mut deserializer); + let api_remove = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::groups::manage_admin_state( + api_group_id, + api__group_public_key, + api_contact_id, + api_remove, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__groups__remove_member_from_group_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "remove_member_from_group", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api_group_public_key = >::sse_decode(&mut deserializer); + let api_contact_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::groups::remove_member_from_group( + api_group_id, + api_group_public_key, + api_contact_id, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_add_additional_user_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_add_additional_user", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_user_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::add_additional_user(api_user_id).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_allocate_sequence_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_allocate_sequence", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::allocate_sequence().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_api_base_url_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_api_base_url", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_protocol = ::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::bridge::api::RustApi::api_base_url(api_protocol))?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__bridge__api__rust_api_change_username_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_change_username", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_username = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::change_username(api_username).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_check_for_deleted_usernames_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_check_for_deleted_usernames", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::check_for_deleted_usernames().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_check_for_passwordless_notification_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_check_for_passwordless_notification", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_notification_id = ::sse_decode(&mut deserializer); + let api_download_auth_token = >::sse_decode(&mut deserializer); + let api_already_received_message_ids = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::check_for_passwordless_notification( + api_notification_id, + api_download_auth_token, + api_already_received_message_ids, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_close_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_close", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::close().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_confirm_memories_upload_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_confirm_memories_upload", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_media_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::confirm_memories_upload(api_media_id) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_connect_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_connect", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::connect().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_connection_state_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_connection_state", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::connection_state().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_delete_account_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_delete_account", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::delete_account().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_delete_memory_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_delete_memory", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_media_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::delete_memory(api_media_id).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_disable_memories_backup_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_disable_memories_backup", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::disable_memories_backup().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_download_done_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_download_done", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_token = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::download_done(api_token).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_events_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_events", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_sink = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::events(api_sink).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_force_ipa_check_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_force_ipa_check", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::force_ipa_check().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_get_memories_url_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_get_memories_url", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_media_id = ::sse_decode(&mut deserializer); + let api_thumbnail = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::get_memories_url( + api_media_id, + api_thumbnail, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_get_memories_usage_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_get_memories_usage", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::get_memories_usage().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_get_plan_balance_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_get_plan_balance", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::get_plan_balance().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_get_proof_of_work_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_get_proof_of_work", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::get_proof_of_work().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_get_server_key_for_passwordless_recovery_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_get_server_key_for_passwordless_recovery", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_user_id = ::sse_decode(&mut deserializer); + let api_server_key_protection = >::sse_decode(&mut deserializer); + let api_pin_unlock_token = >>::sse_decode(&mut deserializer); + let api_pin_protection_key = >>::sse_decode(&mut deserializer); + let api_email = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::get_server_key_for_passwordless_recovery( + api_user_id, + api_server_key_protection, + api_pin_unlock_token, + api_pin_protection_key, + api_email, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_get_user_by_id_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_get_user_by_id", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_user_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::get_user_by_id(api_user_id).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_get_user_data_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_get_user_data", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_username = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::get_user_data(api_username).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_get_user_id_from_username_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_get_user_id_from_username", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_username = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::get_user_id_from_username(api_username) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_insert_and_send_additional_data_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_insert_and_send_additional_data", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api_message_type = ::sse_decode(&mut deserializer); + let api_additional_data = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::insert_and_send_additional_data( + api_group_id, + api_message_type, + api_additional_data, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_insert_and_send_ask_about_user_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_insert_and_send_ask_about_user", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_contact_id = ::sse_decode(&mut deserializer); + let api_ask_about_user_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::insert_and_send_ask_about_user( + api_contact_id, + api_ask_about_user_id, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_insert_and_send_contact_share_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_insert_and_send_contact_share", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api_contact_ids = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::insert_and_send_contact_share( + api_group_id, + api_contact_ids, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_insert_and_send_text_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_insert_and_send_text", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api_text = ::sse_decode(&mut deserializer); + let api_quote_message_id = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::insert_and_send_text( + api_group_id, + api_text, + api_quote_message_id, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_ipa_purchase_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_ipa_purchase", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_product_id = ::sse_decode(&mut deserializer); + let api_source = ::sse_decode(&mut deserializer); + let api_verification_data = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::ipa_purchase( + api_product_id, + api_source, + api_verification_data, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_load_plan_balance_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_load_plan_balance", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_use_cache = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::load_plan_balance(api_use_cache).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_notify_messages_opened_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_notify_messages_opened", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_contact_id = ::sse_decode(&mut deserializer); + let api_message_ids = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::notify_messages_opened( + api_contact_id, + api_message_ids, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_prepare_queued_message_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_prepare_queued_message", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_receipt_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::prepare_queued_message(api_receipt_id) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_register_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_register", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_username = ::sse_decode(&mut deserializer); + let api_proof_of_work = ::sse_decode(&mut deserializer); + let api_lang_code = ::sse_decode(&mut deserializer); + let api_is_ios = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::register( + api_username, + api_proof_of_work, + api_lang_code, + api_is_ios, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_register_passwordless_notification_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_register_passwordless_notification", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_notification_id = ::sse_decode(&mut deserializer); + let api_download_auth_token = >::sse_decode(&mut deserializer); + let api_lang_code = ::sse_decode(&mut deserializer); + let api_google_fcm = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::register_passwordless_notification( + api_notification_id, + api_download_auth_token, + api_lang_code, + api_google_fcm, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_register_passwordless_recovery_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_register_passwordless_recovery", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_encrypted_server_key = >::sse_decode(&mut deserializer); + let api_pin_unlock_token = >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::register_passwordless_recovery( + api_encrypted_server_key, + api_pin_unlock_token, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_reload_configuration_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_reload_configuration", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::reload_configuration().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_remove_additional_user_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_remove_additional_user", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_user_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::remove_additional_user(api_user_id) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_report_user_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_report_user", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_user_id = ::sse_decode(&mut deserializer); + let api_reason = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::report_user(api_user_id, api_reason) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_request_binary_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_request_binary", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_bytes = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::request_binary(api_bytes).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_request_contact_by_username_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_request_contact_by_username", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_username = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::request_contact_by_username(api_username) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_request_memories_upload_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_request_memories_upload", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_size = ::sse_decode(&mut deserializer); + let api_original_date = ::sse_decode(&mut deserializer); + let api_media_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::request_memories_upload( + api_size, + api_original_date, + api_media_id, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_retransmit_all_messages_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_retransmit_all_messages", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::retransmit_all_messages().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_send_binary_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_send_binary", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_bytes = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::send_binary(api_bytes).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_send_contact_profile_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_send_contact_profile", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_contact_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::send_contact_profile(api_contact_id) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_send_encrypted_content_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_send_encrypted_content", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_contact_id = ::sse_decode(&mut deserializer); + let api_content = >::sse_decode(&mut deserializer); + let api_message_id = >::sse_decode(&mut deserializer); + let api_only_send_if_no_receipts_are_open = ::sse_decode(&mut deserializer); + let api_only_return_encrypted_data = ::sse_decode(&mut deserializer); + let api_blocking = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::send_encrypted_content( + api_contact_id, + api_content, + api_message_id, + api_only_send_if_no_receipts_are_open, + api_only_return_encrypted_data, + api_blocking, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_send_encrypted_content_to_group_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_send_encrypted_content_to_group", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api_content = >::sse_decode(&mut deserializer); + let api_message_id = >::sse_decode(&mut deserializer); + let api_only_send_if_no_receipts_are_open = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::send_encrypted_content_to_group( + api_group_id, + api_content, + api_message_id, + api_only_send_if_no_receipts_are_open, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_send_queued_message_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_send_queued_message", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_receipt_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::send_queued_message(api_receipt_id) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_send_text_message_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_send_text_message", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_user_id = ::sse_decode(&mut deserializer); + let api_body = >::sse_decode(&mut deserializer); + let api_push_data = >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::send_text_message( + api_user_id, + api_body, + api_push_data, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_send_typing_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_send_typing", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api_is_typing = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::send_typing(api_group_id, api_is_typing) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_set_background_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_set_background", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_in_background = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::set_background(api_in_background).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_set_login_token_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_set_login_token", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_token = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::set_login_token(api_token).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_set_network_available_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_set_network_available", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_available = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::set_network_available(api_available) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_submit_recovery_share_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_submit_recovery_share", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_notification_id = ::sse_decode(&mut deserializer); + let api_encrypted_message = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::submit_recovery_share( + api_notification_id, + api_encrypted_message, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_update_fcm_token_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_update_fcm_token", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_token = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::api::RustApi::update_fcm_token(api_token).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_update_signed_pre_key_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_update_signed_pre_key", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_id = ::sse_decode(&mut deserializer); + let api_key = >::sse_decode(&mut deserializer); + let api_signature = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::update_signed_pre_key( + api_id, + api_key, + api_signature, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__api__rust_api_upload_pqc_pre_keys_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_upload_pqc_pre_keys", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_ecc_signed_prekey_id = ::sse_decode(&mut deserializer); + let api_ecc_signed_prekey = >::sse_decode(&mut deserializer); + let api_ecc_signed_prekey_signature = >::sse_decode(&mut deserializer); + let api_kyber_signed_prekey_id = ::sse_decode(&mut deserializer); + let api_kyber_signed_prekey = >::sse_decode(&mut deserializer); + let api_kyber_signed_prekey_signature = >::sse_decode(&mut deserializer); + let api_prekeys = + >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::upload_pqc_pre_keys( + api_ecc_signed_prekey_id, + api_ecc_signed_prekey, + api_ecc_signed_prekey_signature, + api_kyber_signed_prekey_id, + api_kyber_signed_prekey, + api_kyber_signed_prekey_signature, + api_prekeys, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__bridge__wrapper__app_database__rust_app_database_execute_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -1121,9 +3685,207 @@ fn wire__crate__bridge__wrapper__rust_utils_recover_secret_impl( }, ) } +fn wire__crate__bridge__groups__update_chat_deletion_time_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "update_chat_deletion_time", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api_delete_messages_after_milliseconds = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::groups::update_chat_deletion_time( + api_group_id, + api_delete_messages_after_milliseconds, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__groups__update_group_name_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "update_group_name", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_group_id = ::sse_decode(&mut deserializer); + let api_group_name = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::groups::update_group_name(api_group_id, api_group_name) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} // Section: related_funcs +fn decode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(String, String, i64, String) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body( + dart_opaque: flutter_rust_bridge::DartOpaque, + arg0: String, + arg1: String, + arg2: i64, + arg3: String, + ) -> () { + let args = vec![ + arg0.into_into_dart().into_dart(), + arg1.into_into_dart().into_dart(), + arg2.into_into_dart().into_dart(), + arg3.into_into_dart().into_dart(), + ]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: String, arg1: String, arg2: i64, arg3: String| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + arg1, + arg2, + arg3, + )) + } +} +fn decode_DartFn_Inputs_String_bool_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(String, bool) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, arg0: String, arg1: bool) -> () { + let args = vec![ + arg0.into_into_dart().into_dart(), + arg1.into_into_dart().into_dart(), + ]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: String, arg1: bool| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + arg1, + )) + } +} +fn decode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(String, i64) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, arg0: String, arg1: i64) -> () { + let args = vec![ + arg0.into_into_dart().into_dart(), + arg1.into_into_dart().into_dart(), + ]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: String, arg1: i64| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + arg1, + )) + } +} fn decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( dart_opaque: flutter_rust_bridge::DartOpaque, ) -> impl Fn() -> flutter_rust_bridge::DartFnFuture< @@ -1160,6 +3922,325 @@ fn decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(dart_opaque.clone())) } } +fn decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn() -> flutter_rust_bridge::DartFnFuture> +{ + use flutter_rust_bridge::IntoDart; + + async fn body( + dart_opaque: flutter_rust_bridge::DartOpaque, + ) -> Vec { + let args = vec![]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok( + >::sse_decode(&mut deserializer), + ), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move || { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(dart_opaque.clone())) + } +} +fn decode_DartFn_Inputs__Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn() -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque) -> () { + let args = vec![]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move || { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(dart_opaque.clone())) + } +} +fn decode_DartFn_Inputs_i_64_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(i64) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, arg0: i64) -> () { + let args = vec![arg0.into_into_dart().into_dart()]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: i64| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + )) + } +} +fn decode_DartFn_Inputs_i_64_String_String_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(i64, String, String) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body( + dart_opaque: flutter_rust_bridge::DartOpaque, + arg0: i64, + arg1: String, + arg2: String, + ) -> () { + let args = vec![ + arg0.into_into_dart().into_dart(), + arg1.into_into_dart().into_dart(), + arg2.into_into_dart().into_dart(), + ]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: i64, arg1: String, arg2: String| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + arg1, + arg2, + )) + } +} +fn decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn( + i64, + Vec, +) -> flutter_rust_bridge::DartFnFuture< + Option, +> { + use flutter_rust_bridge::IntoDart; + + async fn body( + dart_opaque: flutter_rust_bridge::DartOpaque, + arg0: i64, + arg1: Vec, + ) -> Option { + let args = vec![ + arg0.into_into_dart().into_dart(), + arg1.into_into_dart().into_dart(), + ]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: i64, arg1: Vec| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + arg1, + )) + } +} +fn decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(i64, Vec) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, arg0: i64, arg1: Vec) -> () { + let args = vec![ + arg0.into_into_dart().into_dart(), + arg1.into_into_dart().into_dart(), + ]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: i64, arg1: Vec| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + arg1, + )) + } +} +fn decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn( + i64, + Vec, + i32, +) -> flutter_rust_bridge::DartFnFuture { + use flutter_rust_bridge::IntoDart; + + async fn body( + dart_opaque: flutter_rust_bridge::DartOpaque, + arg0: i64, + arg1: Vec, + arg2: i32, + ) -> crate::bridge::callbacks::LegacySignalDecryptResult { + let args = vec![ + arg0.into_into_dart().into_dart(), + arg1.into_into_dart().into_dart(), + arg2.into_into_dart().into_dart(), + ]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok( + ::sse_decode( + &mut deserializer, + ), + ), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: i64, arg1: Vec, arg2: i32| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + arg1, + arg2, + )) + } +} +fn decode_DartFn_Inputs_i_64_opt_String_list_prim_u_8_strict_i_32_Output_opt_list_prim_u_8_strict_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(i64, Option, Vec, i32) -> flutter_rust_bridge::DartFnFuture>> +{ + use flutter_rust_bridge::IntoDart; + + async fn body( + dart_opaque: flutter_rust_bridge::DartOpaque, + arg0: i64, + arg1: Option, + arg2: Vec, + arg3: i32, + ) -> Option> { + let args = vec![ + arg0.into_into_dart().into_dart(), + arg1.into_into_dart().into_dart(), + arg2.into_into_dart().into_dart(), + arg3.into_into_dart().into_dart(), + ]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(>>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: i64, arg1: Option, arg2: Vec, arg3: i32| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + arg1, + arg2, + arg3, + )) + } +} // Section: dart2rust @@ -1195,6 +4276,16 @@ impl SseDecode for StreamSink +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return StreamSink::deserialize(inner); + } +} + impl SseDecode for String { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1203,6 +4294,56 @@ impl SseDecode for String { } } +impl SseDecode for crate::bridge::api::ApiConnectionState { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return match inner { + 0 => crate::bridge::api::ApiConnectionState::Stopped, + 1 => crate::bridge::api::ApiConnectionState::Connecting, + 2 => crate::bridge::api::ApiConnectionState::Connected, + 3 => crate::bridge::api::ApiConnectionState::Authenticating, + 4 => crate::bridge::api::ApiConnectionState::Authenticated, + 5 => crate::bridge::api::ApiConnectionState::Reconnecting, + 6 => crate::bridge::api::ApiConnectionState::Suspended, + 7 => crate::bridge::api::ApiConnectionState::PermanentlyRejected, + _ => unreachable!("Invalid variant for ApiConnectionState: {}", inner), + }; + } +} + +impl SseDecode for crate::bridge::api::ApiEvent { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_kind = ::sse_decode(deserializer); + let mut var_state = + >::sse_decode(deserializer); + let mut var_message = >::sse_decode(deserializer); + return crate::bridge::api::ApiEvent { + kind: var_kind, + state: var_state, + message: var_message, + }; + } +} + +impl SseDecode for crate::bridge::api::ApiEventKind { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return match inner { + 0 => crate::bridge::api::ApiEventKind::ConnectionStateChanged, + 1 => crate::bridge::api::ApiEventKind::TransportError, + 2 => crate::bridge::api::ApiEventKind::Authenticated, + 3 => crate::bridge::api::ApiEventKind::PlanUpdated, + 4 => crate::bridge::api::ApiEventKind::AppOutdated, + 5 => crate::bridge::api::ApiEventKind::NewDeviceRegistered, + 6 => crate::bridge::api::ApiEventKind::LoginTokenMigrated, + _ => unreachable!("Invalid variant for ApiEventKind: {}", inner), + }; + } +} + impl SseDecode for crate::keys::backup_password_keys::BackupPasswordKeys { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1284,6 +4425,13 @@ impl SseDecode for crate::signal::engine::FrbPreKeyBundle { } } +impl SseDecode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i32::().unwrap() + } +} + impl SseDecode for i64 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1325,6 +4473,42 @@ impl SseDecode for crate::bridge::wrapper::app_database::LegacyMigrationReport { } } +impl SseDecode for crate::bridge::callbacks::LegacySignalDecryptResult { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_plaintext = >>::sse_decode(deserializer); + let mut var_decryptionErrorType = >::sse_decode(deserializer); + return crate::bridge::callbacks::LegacySignalDecryptResult { + plaintext: var_plaintext, + decryption_error_type: var_decryptionErrorType, + }; + } +} + +impl SseDecode for crate::bridge::callbacks::LegacySignalEncryptResult { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_ciphertext = >::sse_decode(deserializer); + let mut var_messageType = ::sse_decode(deserializer); + return crate::bridge::callbacks::LegacySignalEncryptResult { + ciphertext: var_ciphertext, + message_type: var_messageType, + }; + } +} + +impl SseDecode for crate::bridge::callbacks::LegacySignalPreKey { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_id = ::sse_decode(deserializer); + let mut var_publicKey = >::sse_decode(deserializer); + return crate::bridge::callbacks::LegacySignalPreKey { + id: var_id, + public_key: var_publicKey, + }; + } +} + impl SseDecode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1363,6 +4547,20 @@ impl SseDecode for Vec { } } +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode( + deserializer, + )); + } + return ans_; + } +} + impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1391,6 +4589,32 @@ impl SseDecode for Vec> { } } +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode( + deserializer, + )); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1450,6 +4674,19 @@ impl SseDecode for Option { } } +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode( + deserializer, + )); + } else { + return None; + } + } +} + impl SseDecode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1461,6 +4698,17 @@ impl SseDecode for Option { } } +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + impl SseDecode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1472,6 +4720,32 @@ impl SseDecode for Option { } } +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some( + ::sse_decode(deserializer), + ); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode( + deserializer, + )); + } else { + return None; + } + } +} + impl SseDecode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1494,6 +4768,36 @@ impl SseDecode for Option> { } } +impl SseDecode for crate::api::server::prekeys::PqcPreKeyInput { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_eccPreKeyId = ::sse_decode(deserializer); + let mut var_eccPreKey = >::sse_decode(deserializer); + let mut var_kyberPreKeyId = ::sse_decode(deserializer); + let mut var_kyberPreKey = >::sse_decode(deserializer); + let mut var_kyberPreKeySignature = >::sse_decode(deserializer); + return crate::api::server::prekeys::PqcPreKeyInput { + ecc_pre_key_id: var_eccPreKeyId, + ecc_pre_key: var_eccPreKey, + kyber_pre_key_id: var_kyberPreKeyId, + kyber_pre_key: var_kyberPreKey, + kyber_pre_key_signature: var_kyberPreKeySignature, + }; + } +} + +impl SseDecode for crate::bridge::api::PreparedOutgoingMessage { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_message = >::sse_decode(deserializer); + let mut var_pushData = >>::sse_decode(deserializer); + return crate::bridge::api::PreparedOutgoingMessage { + message: var_message, + push_data: var_pushData, + }; + } +} + impl SseDecode for (i64, Vec) { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1521,6 +4825,13 @@ impl SseDecode for (String, String) { } } +impl SseDecode for crate::bridge::api::RustApi { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + return crate::bridge::api::RustApi {}; + } +} + impl SseDecode for crate::bridge::wrapper::app_database::RustAppDatabase { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1563,6 +4874,65 @@ impl SseDecode for crate::bridge::wrapper::RustUtils { } } +impl SseDecode for crate::bridge::api::ServerResultEmpty { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + return crate::bridge::api::ServerResultEmpty::Ok; + } + 1 => { + let mut var_field0 = ::sse_decode(deserializer); + return crate::bridge::api::ServerResultEmpty::ErrorCode(var_field0); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseDecode for crate::bridge::api::ServerResultI64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + let mut var_field0 = ::sse_decode(deserializer); + return crate::bridge::api::ServerResultI64::Ok(var_field0); + } + 1 => { + let mut var_field0 = ::sse_decode(deserializer); + return crate::bridge::api::ServerResultI64::ErrorCode(var_field0); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseDecode for crate::bridge::api::ServerResultVecU8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + let mut var_field0 = >::sse_decode(deserializer); + return crate::bridge::api::ServerResultVecU8::Ok(var_field0); + } + 1 => { + let mut var_field0 = ::sse_decode(deserializer); + return crate::bridge::api::ServerResultVecU8::ErrorCode(var_field0); + } + _ => { + unimplemented!(""); + } + } + } +} + impl SseDecode for crate::database::app::SqlExecutionResult { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1620,6 +4990,13 @@ impl SseDecode for u32 { } } +impl SseDecode for u64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u64::().unwrap() + } +} + impl SseDecode for u8 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1647,13 +5024,6 @@ impl SseDecode for usize { } } -impl SseDecode for i32 { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - deserializer.cursor.read_i32::().unwrap() - } -} - fn pde_ffi_dispatcher_primary_impl( func_id: i32, port: flutter_rust_bridge::for_generated::MessagePort, @@ -1663,48 +5033,115 @@ fn pde_ffi_dispatcher_primary_impl( ) { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { - 1 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_current_version_impl(port, ptr, rust_vec_len, data_len), -2 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_new_messages_impl(port, ptr, rust_vec_len, data_len), -3 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_new_messages_impl(port, ptr, rust_vec_len, data_len), -4 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_initialize_or_update_impl(port, ptr, rust_vec_len, data_len), -5 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_should_request_new_messages_impl(port, ptr, rust_vec_len, data_len), -6 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_update_verification_state_for_user_impl(port, ptr, rust_vec_len, data_len), -7 => wire__crate__bridge__callbacks__init_flutter_callbacks_impl(port, ptr, rust_vec_len, data_len), -8 => wire__crate__bridge__initialize_twonly_flutter_impl(port, ptr, rust_vec_len, data_len), -9 => wire__crate__bridge__wrapper__app_database__rust_app_database_execute_impl(port, ptr, rust_vec_len, data_len), -10 => wire__crate__bridge__wrapper__app_database__rust_app_database_legacy_import_complete_impl(port, ptr, rust_vec_len, data_len), -11 => wire__crate__bridge__wrapper__app_database__rust_app_database_migrate_legacy_database_impl(port, ptr, rust_vec_len, data_len), -12 => wire__crate__bridge__wrapper__app_database__rust_app_database_select_impl(port, ptr, rust_vec_len, data_len), -13 => wire__crate__bridge__wrapper__backup__rust_backup_archive_create_backup_archive_impl(port, ptr, rust_vec_len, data_len), -14 => wire__crate__bridge__wrapper__backup__rust_backup_archive_get_backup_download_token_impl(port, ptr, rust_vec_len, data_len), -15 => wire__crate__bridge__wrapper__backup__rust_backup_archive_restore_backup_archive_impl(port, ptr, rust_vec_len, data_len), -16 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_id_impl(port, ptr, rust_vec_len, data_len), -17 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), -18 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_identity_backup_bytes_impl(port, ptr, rust_vec_len, data_len), -19 => wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), -20 => wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_impl(port, ptr, rust_vec_len, data_len), -21 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), -22 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_decrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len), -23 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_encrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len), -24 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len), -25 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len), -26 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(port, ptr, rust_vec_len, data_len), -27 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_serialized_impl(port, ptr, rust_vec_len, data_len), -28 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len), -29 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekey_impl(port, ptr, rust_vec_len, data_len), -30 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekeys_impl(port, ptr, rust_vec_len, data_len), -31 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_key_manager_impl(port, ptr, rust_vec_len, data_len), -32 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(port, ptr, rust_vec_len, data_len), -33 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(port, ptr, rust_vec_len, data_len), -34 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len), -35 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(port, ptr, rust_vec_len, data_len), -36 => wire__crate__bridge__wrapper__signal__rust_signal_decrypt_impl(port, ptr, rust_vec_len, data_len), -37 => wire__crate__bridge__wrapper__signal__rust_signal_encrypt_impl(port, ptr, rust_vec_len, data_len), -38 => wire__crate__bridge__wrapper__signal__rust_signal_generate_bundle_impl(port, ptr, rust_vec_len, data_len), -39 => wire__crate__bridge__wrapper__signal__rust_signal_generate_pqc_prekeys_impl(port, ptr, rust_vec_len, data_len), -40 => wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(port, ptr, rust_vec_len, data_len), -41 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len), -42 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len), + 1 => wire__crate__bridge__groups__add_hidden_contact_impl(port, ptr, rust_vec_len, data_len), +2 => wire__crate__bridge__groups__add_new_group_members_impl(port, ptr, rust_vec_len, data_len), +3 => wire__crate__bridge__groups__create_new_group_impl(port, ptr, rust_vec_len, data_len), +4 => wire__crate__bridge__groups__fetch_group_state_impl(port, ptr, rust_vec_len, data_len), +5 => wire__crate__bridge__groups__fetch_group_states_for_unjoined_groups_impl(port, ptr, rust_vec_len, data_len), +6 => wire__crate__bridge__groups__fetch_missing_group_public_keys_impl(port, ptr, rust_vec_len, data_len), +7 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_current_version_impl(port, ptr, rust_vec_len, data_len), +8 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_new_messages_impl(port, ptr, rust_vec_len, data_len), +9 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_new_messages_impl(port, ptr, rust_vec_len, data_len), +10 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_initialize_or_update_impl(port, ptr, rust_vec_len, data_len), +11 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_should_request_new_messages_impl(port, ptr, rust_vec_len, data_len), +12 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_update_verification_state_for_user_impl(port, ptr, rust_vec_len, data_len), +13 => wire__crate__bridge__callbacks__init_flutter_callbacks_impl(port, ptr, rust_vec_len, data_len), +14 => wire__crate__bridge__initialize_twonly_flutter_impl(port, ptr, rust_vec_len, data_len), +15 => wire__crate__bridge__initialize_twonly_standalone_impl(port, ptr, rust_vec_len, data_len), +16 => wire__crate__bridge__groups__leave_group_impl(port, ptr, rust_vec_len, data_len), +17 => wire__crate__bridge__groups__manage_admin_state_impl(port, ptr, rust_vec_len, data_len), +18 => wire__crate__bridge__groups__remove_member_from_group_impl(port, ptr, rust_vec_len, data_len), +19 => wire__crate__bridge__api__rust_api_add_additional_user_impl(port, ptr, rust_vec_len, data_len), +20 => wire__crate__bridge__api__rust_api_allocate_sequence_impl(port, ptr, rust_vec_len, data_len), +22 => wire__crate__bridge__api__rust_api_change_username_impl(port, ptr, rust_vec_len, data_len), +23 => wire__crate__bridge__api__rust_api_check_for_deleted_usernames_impl(port, ptr, rust_vec_len, data_len), +24 => wire__crate__bridge__api__rust_api_check_for_passwordless_notification_impl(port, ptr, rust_vec_len, data_len), +25 => wire__crate__bridge__api__rust_api_close_impl(port, ptr, rust_vec_len, data_len), +26 => wire__crate__bridge__api__rust_api_confirm_memories_upload_impl(port, ptr, rust_vec_len, data_len), +27 => wire__crate__bridge__api__rust_api_connect_impl(port, ptr, rust_vec_len, data_len), +28 => wire__crate__bridge__api__rust_api_connection_state_impl(port, ptr, rust_vec_len, data_len), +29 => wire__crate__bridge__api__rust_api_delete_account_impl(port, ptr, rust_vec_len, data_len), +30 => wire__crate__bridge__api__rust_api_delete_memory_impl(port, ptr, rust_vec_len, data_len), +31 => wire__crate__bridge__api__rust_api_disable_memories_backup_impl(port, ptr, rust_vec_len, data_len), +32 => wire__crate__bridge__api__rust_api_download_done_impl(port, ptr, rust_vec_len, data_len), +33 => wire__crate__bridge__api__rust_api_events_impl(port, ptr, rust_vec_len, data_len), +34 => wire__crate__bridge__api__rust_api_force_ipa_check_impl(port, ptr, rust_vec_len, data_len), +35 => wire__crate__bridge__api__rust_api_get_memories_url_impl(port, ptr, rust_vec_len, data_len), +36 => wire__crate__bridge__api__rust_api_get_memories_usage_impl(port, ptr, rust_vec_len, data_len), +37 => wire__crate__bridge__api__rust_api_get_plan_balance_impl(port, ptr, rust_vec_len, data_len), +38 => wire__crate__bridge__api__rust_api_get_proof_of_work_impl(port, ptr, rust_vec_len, data_len), +39 => wire__crate__bridge__api__rust_api_get_server_key_for_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len), +40 => wire__crate__bridge__api__rust_api_get_user_by_id_impl(port, ptr, rust_vec_len, data_len), +41 => wire__crate__bridge__api__rust_api_get_user_data_impl(port, ptr, rust_vec_len, data_len), +42 => wire__crate__bridge__api__rust_api_get_user_id_from_username_impl(port, ptr, rust_vec_len, data_len), +43 => wire__crate__bridge__api__rust_api_insert_and_send_additional_data_impl(port, ptr, rust_vec_len, data_len), +44 => wire__crate__bridge__api__rust_api_insert_and_send_ask_about_user_impl(port, ptr, rust_vec_len, data_len), +45 => wire__crate__bridge__api__rust_api_insert_and_send_contact_share_impl(port, ptr, rust_vec_len, data_len), +46 => wire__crate__bridge__api__rust_api_insert_and_send_text_impl(port, ptr, rust_vec_len, data_len), +47 => wire__crate__bridge__api__rust_api_ipa_purchase_impl(port, ptr, rust_vec_len, data_len), +48 => wire__crate__bridge__api__rust_api_load_plan_balance_impl(port, ptr, rust_vec_len, data_len), +49 => wire__crate__bridge__api__rust_api_notify_messages_opened_impl(port, ptr, rust_vec_len, data_len), +50 => wire__crate__bridge__api__rust_api_prepare_queued_message_impl(port, ptr, rust_vec_len, data_len), +51 => wire__crate__bridge__api__rust_api_register_impl(port, ptr, rust_vec_len, data_len), +52 => wire__crate__bridge__api__rust_api_register_passwordless_notification_impl(port, ptr, rust_vec_len, data_len), +53 => wire__crate__bridge__api__rust_api_register_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len), +54 => wire__crate__bridge__api__rust_api_reload_configuration_impl(port, ptr, rust_vec_len, data_len), +55 => wire__crate__bridge__api__rust_api_remove_additional_user_impl(port, ptr, rust_vec_len, data_len), +56 => wire__crate__bridge__api__rust_api_report_user_impl(port, ptr, rust_vec_len, data_len), +57 => wire__crate__bridge__api__rust_api_request_binary_impl(port, ptr, rust_vec_len, data_len), +58 => wire__crate__bridge__api__rust_api_request_contact_by_username_impl(port, ptr, rust_vec_len, data_len), +59 => wire__crate__bridge__api__rust_api_request_memories_upload_impl(port, ptr, rust_vec_len, data_len), +60 => wire__crate__bridge__api__rust_api_retransmit_all_messages_impl(port, ptr, rust_vec_len, data_len), +61 => wire__crate__bridge__api__rust_api_send_binary_impl(port, ptr, rust_vec_len, data_len), +62 => wire__crate__bridge__api__rust_api_send_contact_profile_impl(port, ptr, rust_vec_len, data_len), +63 => wire__crate__bridge__api__rust_api_send_encrypted_content_impl(port, ptr, rust_vec_len, data_len), +64 => wire__crate__bridge__api__rust_api_send_encrypted_content_to_group_impl(port, ptr, rust_vec_len, data_len), +65 => wire__crate__bridge__api__rust_api_send_queued_message_impl(port, ptr, rust_vec_len, data_len), +66 => wire__crate__bridge__api__rust_api_send_text_message_impl(port, ptr, rust_vec_len, data_len), +67 => wire__crate__bridge__api__rust_api_send_typing_impl(port, ptr, rust_vec_len, data_len), +68 => wire__crate__bridge__api__rust_api_set_background_impl(port, ptr, rust_vec_len, data_len), +69 => wire__crate__bridge__api__rust_api_set_login_token_impl(port, ptr, rust_vec_len, data_len), +70 => wire__crate__bridge__api__rust_api_set_network_available_impl(port, ptr, rust_vec_len, data_len), +71 => wire__crate__bridge__api__rust_api_submit_recovery_share_impl(port, ptr, rust_vec_len, data_len), +72 => wire__crate__bridge__api__rust_api_update_fcm_token_impl(port, ptr, rust_vec_len, data_len), +73 => wire__crate__bridge__api__rust_api_update_signed_pre_key_impl(port, ptr, rust_vec_len, data_len), +74 => wire__crate__bridge__api__rust_api_upload_pqc_pre_keys_impl(port, ptr, rust_vec_len, data_len), +75 => wire__crate__bridge__wrapper__app_database__rust_app_database_execute_impl(port, ptr, rust_vec_len, data_len), +76 => wire__crate__bridge__wrapper__app_database__rust_app_database_legacy_import_complete_impl(port, ptr, rust_vec_len, data_len), +77 => wire__crate__bridge__wrapper__app_database__rust_app_database_migrate_legacy_database_impl(port, ptr, rust_vec_len, data_len), +78 => wire__crate__bridge__wrapper__app_database__rust_app_database_select_impl(port, ptr, rust_vec_len, data_len), +79 => wire__crate__bridge__wrapper__backup__rust_backup_archive_create_backup_archive_impl(port, ptr, rust_vec_len, data_len), +80 => wire__crate__bridge__wrapper__backup__rust_backup_archive_get_backup_download_token_impl(port, ptr, rust_vec_len, data_len), +81 => wire__crate__bridge__wrapper__backup__rust_backup_archive_restore_backup_archive_impl(port, ptr, rust_vec_len, data_len), +82 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_id_impl(port, ptr, rust_vec_len, data_len), +83 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), +84 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_identity_backup_bytes_impl(port, ptr, rust_vec_len, data_len), +85 => wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), +86 => wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_impl(port, ptr, rust_vec_len, data_len), +87 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), +88 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_decrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len), +89 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_encrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len), +90 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len), +91 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len), +92 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(port, ptr, rust_vec_len, data_len), +93 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_serialized_impl(port, ptr, rust_vec_len, data_len), +94 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len), +95 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekey_impl(port, ptr, rust_vec_len, data_len), +96 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekeys_impl(port, ptr, rust_vec_len, data_len), +97 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_key_manager_impl(port, ptr, rust_vec_len, data_len), +98 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(port, ptr, rust_vec_len, data_len), +99 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(port, ptr, rust_vec_len, data_len), +100 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len), +101 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(port, ptr, rust_vec_len, data_len), +102 => wire__crate__bridge__wrapper__signal__rust_signal_decrypt_impl(port, ptr, rust_vec_len, data_len), +103 => wire__crate__bridge__wrapper__signal__rust_signal_encrypt_impl(port, ptr, rust_vec_len, data_len), +104 => wire__crate__bridge__wrapper__signal__rust_signal_generate_bundle_impl(port, ptr, rust_vec_len, data_len), +105 => wire__crate__bridge__wrapper__signal__rust_signal_generate_pqc_prekeys_impl(port, ptr, rust_vec_len, data_len), +106 => wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(port, ptr, rust_vec_len, data_len), +107 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len), +108 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len), +109 => wire__crate__bridge__groups__update_chat_deletion_time_impl(port, ptr, rust_vec_len, data_len), +110 => wire__crate__bridge__groups__update_group_name_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -1717,12 +5154,85 @@ fn pde_ffi_dispatcher_sync_impl( ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { + 21 => wire__crate__bridge__api__rust_api_api_base_url_impl(ptr, rust_vec_len, data_len), _ => unreachable!(), } } // Section: rust2dart +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::api::ApiConnectionState { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + Self::Stopped => 0.into_dart(), + Self::Connecting => 1.into_dart(), + Self::Connected => 2.into_dart(), + Self::Authenticating => 3.into_dart(), + Self::Authenticated => 4.into_dart(), + Self::Reconnecting => 5.into_dart(), + Self::Suspended => 6.into_dart(), + Self::PermanentlyRejected => 7.into_dart(), + _ => unreachable!(), + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::api::ApiConnectionState +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::api::ApiConnectionState +{ + fn into_into_dart(self) -> crate::bridge::api::ApiConnectionState { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::api::ApiEvent { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.kind.into_into_dart().into_dart(), + self.state.into_into_dart().into_dart(), + self.message.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::bridge::api::ApiEvent {} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::api::ApiEvent +{ + fn into_into_dart(self) -> crate::bridge::api::ApiEvent { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::api::ApiEventKind { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + Self::ConnectionStateChanged => 0.into_dart(), + Self::TransportError => 1.into_dart(), + Self::Authenticated => 2.into_dart(), + Self::PlanUpdated => 3.into_dart(), + Self::AppOutdated => 4.into_dart(), + Self::NewDeviceRegistered => 5.into_dart(), + Self::LoginTokenMigrated => 6.into_dart(), + _ => unreachable!(), + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::api::ApiEventKind +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::api::ApiEventKind +{ + fn into_into_dart(self) -> crate::bridge::api::ApiEventKind { + self + } +} // Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::keys::backup_password_keys::BackupPasswordKeys { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { @@ -1855,6 +5365,69 @@ impl flutter_rust_bridge::IntoIntoDart flutter_rust_bridge::for_generated::DartAbi { + [ + self.plaintext.into_into_dart().into_dart(), + self.decryption_error_type.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::callbacks::LegacySignalDecryptResult +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::callbacks::LegacySignalDecryptResult +{ + fn into_into_dart(self) -> crate::bridge::callbacks::LegacySignalDecryptResult { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::callbacks::LegacySignalEncryptResult { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.ciphertext.into_into_dart().into_dart(), + self.message_type.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::callbacks::LegacySignalEncryptResult +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::callbacks::LegacySignalEncryptResult +{ + fn into_into_dart(self) -> crate::bridge::callbacks::LegacySignalEncryptResult { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::callbacks::LegacySignalPreKey { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.id.into_into_dart().into_dart(), + self.public_key.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::callbacks::LegacySignalPreKey +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::callbacks::LegacySignalPreKey +{ + fn into_into_dart(self) -> crate::bridge::callbacks::LegacySignalPreKey { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::bridge::wrapper::app_database::LegacyTableMigrationCount { @@ -1880,6 +5453,65 @@ impl } } // Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::server::prekeys::PqcPreKeyInput { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.ecc_pre_key_id.into_into_dart().into_dart(), + self.ecc_pre_key.into_into_dart().into_dart(), + self.kyber_pre_key_id.into_into_dart().into_dart(), + self.kyber_pre_key.into_into_dart().into_dart(), + self.kyber_pre_key_signature.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::server::prekeys::PqcPreKeyInput +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::server::prekeys::PqcPreKeyInput +{ + fn into_into_dart(self) -> crate::api::server::prekeys::PqcPreKeyInput { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::api::PreparedOutgoingMessage { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.message.into_into_dart().into_dart(), + self.push_data.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::api::PreparedOutgoingMessage +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::api::PreparedOutgoingMessage +{ + fn into_into_dart(self) -> crate::bridge::api::PreparedOutgoingMessage { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::api::RustApi { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + Vec::::new().into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::bridge::api::RustApi {} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::api::RustApi +{ + fn into_into_dart(self) -> crate::bridge::api::RustApi { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::bridge::wrapper::app_database::RustAppDatabase { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { Vec::::new().into_dart() @@ -1982,6 +5614,85 @@ impl flutter_rust_bridge::IntoIntoDart } } // Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::api::ServerResultEmpty { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + crate::bridge::api::ServerResultEmpty::Ok => [0.into_dart()].into_dart(), + crate::bridge::api::ServerResultEmpty::ErrorCode(field0) => { + [1.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::api::ServerResultEmpty +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::api::ServerResultEmpty +{ + fn into_into_dart(self) -> crate::bridge::api::ServerResultEmpty { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::api::ServerResultI64 { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + crate::bridge::api::ServerResultI64::Ok(field0) => { + [0.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + crate::bridge::api::ServerResultI64::ErrorCode(field0) => { + [1.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::api::ServerResultI64 +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::api::ServerResultI64 +{ + fn into_into_dart(self) -> crate::bridge::api::ServerResultI64 { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::api::ServerResultVecU8 { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + crate::bridge::api::ServerResultVecU8::Ok(field0) => { + [0.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + crate::bridge::api::ServerResultVecU8::ErrorCode(field0) => { + [1.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::api::ServerResultVecU8 +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::api::ServerResultVecU8 +{ + fn into_into_dart(self) -> crate::bridge::api::ServerResultVecU8 { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::database::app::SqlExecutionResult { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ @@ -2087,6 +5798,15 @@ impl SseEncode for StreamSink +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + unimplemented!("") + } +} + impl SseEncode for String { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2094,6 +5814,58 @@ impl SseEncode for String { } } +impl SseEncode for crate::bridge::api::ApiConnectionState { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode( + match self { + crate::bridge::api::ApiConnectionState::Stopped => 0, + crate::bridge::api::ApiConnectionState::Connecting => 1, + crate::bridge::api::ApiConnectionState::Connected => 2, + crate::bridge::api::ApiConnectionState::Authenticating => 3, + crate::bridge::api::ApiConnectionState::Authenticated => 4, + crate::bridge::api::ApiConnectionState::Reconnecting => 5, + crate::bridge::api::ApiConnectionState::Suspended => 6, + crate::bridge::api::ApiConnectionState::PermanentlyRejected => 7, + _ => { + unimplemented!(""); + } + }, + serializer, + ); + } +} + +impl SseEncode for crate::bridge::api::ApiEvent { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.kind, serializer); + >::sse_encode(self.state, serializer); + >::sse_encode(self.message, serializer); + } +} + +impl SseEncode for crate::bridge::api::ApiEventKind { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode( + match self { + crate::bridge::api::ApiEventKind::ConnectionStateChanged => 0, + crate::bridge::api::ApiEventKind::TransportError => 1, + crate::bridge::api::ApiEventKind::Authenticated => 2, + crate::bridge::api::ApiEventKind::PlanUpdated => 3, + crate::bridge::api::ApiEventKind::AppOutdated => 4, + crate::bridge::api::ApiEventKind::NewDeviceRegistered => 5, + crate::bridge::api::ApiEventKind::LoginTokenMigrated => 6, + _ => { + unimplemented!(""); + } + }, + serializer, + ); + } +} + impl SseEncode for crate::keys::backup_password_keys::BackupPasswordKeys { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2149,6 +5921,13 @@ impl SseEncode for crate::signal::engine::FrbPreKeyBundle { } } +impl SseEncode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i32::(self).unwrap(); + } +} + impl SseEncode for i64 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2185,6 +5964,30 @@ impl SseEncode for crate::bridge::wrapper::app_database::LegacyMigrationReport { } } +impl SseEncode for crate::bridge::callbacks::LegacySignalDecryptResult { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(self.plaintext, serializer); + >::sse_encode(self.decryption_error_type, serializer); + } +} + +impl SseEncode for crate::bridge::callbacks::LegacySignalEncryptResult { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.ciphertext, serializer); + ::sse_encode(self.message_type, serializer); + } +} + +impl SseEncode for crate::bridge::callbacks::LegacySignalPreKey { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.id, serializer); + >::sse_encode(self.public_key, serializer); + } +} + impl SseEncode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2213,6 +6016,16 @@ impl SseEncode for Vec { } } +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2235,6 +6048,26 @@ impl SseEncode for Vec> { } } +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2285,6 +6118,16 @@ impl SseEncode for Option { } } +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + impl SseEncode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2295,6 +6138,16 @@ impl SseEncode for Option { } } +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + impl SseEncode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2305,6 +6158,26 @@ impl SseEncode for Option { } } +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + impl SseEncode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2325,6 +6198,25 @@ impl SseEncode for Option> { } } +impl SseEncode for crate::api::server::prekeys::PqcPreKeyInput { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.ecc_pre_key_id, serializer); + >::sse_encode(self.ecc_pre_key, serializer); + ::sse_encode(self.kyber_pre_key_id, serializer); + >::sse_encode(self.kyber_pre_key, serializer); + >::sse_encode(self.kyber_pre_key_signature, serializer); + } +} + +impl SseEncode for crate::bridge::api::PreparedOutgoingMessage { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.message, serializer); + >>::sse_encode(self.push_data, serializer); + } +} + impl SseEncode for (i64, Vec) { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2349,6 +6241,11 @@ impl SseEncode for (String, String) { } } +impl SseEncode for crate::bridge::api::RustApi { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} +} + impl SseEncode for crate::bridge::wrapper::app_database::RustAppDatabase { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} @@ -2379,6 +6276,62 @@ impl SseEncode for crate::bridge::wrapper::RustUtils { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} } +impl SseEncode for crate::bridge::api::ServerResultEmpty { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + match self { + crate::bridge::api::ServerResultEmpty::Ok => { + ::sse_encode(0, serializer); + } + crate::bridge::api::ServerResultEmpty::ErrorCode(field0) => { + ::sse_encode(1, serializer); + ::sse_encode(field0, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseEncode for crate::bridge::api::ServerResultI64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + match self { + crate::bridge::api::ServerResultI64::Ok(field0) => { + ::sse_encode(0, serializer); + ::sse_encode(field0, serializer); + } + crate::bridge::api::ServerResultI64::ErrorCode(field0) => { + ::sse_encode(1, serializer); + ::sse_encode(field0, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseEncode for crate::bridge::api::ServerResultVecU8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + match self { + crate::bridge::api::ServerResultVecU8::Ok(field0) => { + ::sse_encode(0, serializer); + >::sse_encode(field0, serializer); + } + crate::bridge::api::ServerResultVecU8::ErrorCode(field0) => { + ::sse_encode(1, serializer); + ::sse_encode(field0, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + impl SseEncode for crate::database::app::SqlExecutionResult { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2420,6 +6373,13 @@ impl SseEncode for u32 { } } +impl SseEncode for u64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u64::(self).unwrap(); + } +} + impl SseEncode for u8 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -2455,13 +6415,6 @@ impl SseEncode for usize { } } -impl SseEncode for i32 { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - serializer.cursor.write_i32::(self).unwrap(); - } -} - #[cfg(not(target_family = "wasm"))] mod io { // This file is automatically generated, so please do not edit it. diff --git a/rust/src/keys/backup_password_keys.rs b/rust/src/keys/backup_password_keys.rs index 81612f05..65df43af 100644 --- a/rust/src/keys/backup_password_keys.rs +++ b/rust/src/keys/backup_password_keys.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::error::Result; use scrypt::{scrypt, Params}; use serde::{Deserialize, Serialize}; diff --git a/rust/src/keys/identity_key/mod.rs b/rust/src/keys/identity_key/mod.rs index 06cfb6c0..94364446 100644 --- a/rust/src/keys/identity_key/mod.rs +++ b/rust/src/keys/identity_key/mod.rs @@ -1 +1,6 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + pub(crate) mod signal_identity_key; diff --git a/rust/src/keys/identity_key/signal_identity_key.rs b/rust/src/keys/identity_key/signal_identity_key.rs index 36aac3db..b8fe62c9 100644 --- a/rust/src/keys/identity_key/signal_identity_key.rs +++ b/rust/src/keys/identity_key/signal_identity_key.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use std::collections::HashMap; use serde::{Deserialize, Serialize}; diff --git a/rust/src/keys/main_key.rs b/rust/src/keys/main_key.rs index 04da2436..a430c3b6 100644 --- a/rust/src/keys/main_key.rs +++ b/rust/src/keys/main_key.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::error::Result; use aes_gcm::aead::rand_core::RngCore; use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng}; @@ -77,7 +82,7 @@ impl MainKey { let decrypted = self.decrypt_with_info(info.as_bytes(), encrypted_media_key)?; if decrypted.len() != 32 { - return Err("Invalid decrypted key length".to_string())?; + Err("Invalid decrypted key length".to_string())?; } let mut result = [0u8; 32]; @@ -108,7 +113,7 @@ impl MainKey { fn decrypt_with_info(&self, info: &[u8], encrypted_data: &[u8]) -> Result> { if encrypted_data.len() < 12 { - return Err("Invalid encrypted data length".to_string())?; + Err("Invalid encrypted data length".to_string())?; } let derived_key = self.derive_key(info); diff --git a/rust/src/keys/mod.rs b/rust/src/keys/mod.rs index 2fcfedc1..9de70d2e 100644 --- a/rust/src/keys/mod.rs +++ b/rust/src/keys/mod.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + pub(crate) mod backup_password_keys; mod identity_key; mod main_key; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 1c7fe41a..066d70d6 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,13 +1,23 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +pub mod api; mod backup; pub mod bridge; -mod context; +pub mod context; pub mod database; mod error; +pub use error::TwonlyError; mod frb_generated; mod keys; -mod log; +#[doc(hidden)] +pub mod log; pub mod sealed_sender; mod secure_storage; +pub mod services; pub mod signal; +pub mod user_config; mod user_discovery; mod utils; diff --git a/rust/src/log.rs b/rust/src/log.rs index 854a5351..fa15d783 100644 --- a/rust/src/log.rs +++ b/rust/src/log.rs @@ -1,13 +1,124 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::bridge::callbacks::{get_callbacks, log::DartWriter}; +use std::fmt; +use std::path::Path; use std::sync::{Mutex, OnceLock}; +use tracing::{Event, Subscriber}; use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; use tracing_subscriber::{ - fmt::Layer, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry, + fmt::{format::Writer, FmtContext, FormatEvent, FormatFields, FormattedFields, Layer}, + layer::SubscriberExt, + registry::LookupSpan, + util::SubscriberInitExt, + EnvFilter, Registry, }; static TRACING_GUARDS: OnceLock>> = OnceLock::new(); static TRACING_INIT: OnceLock<()> = OnceLock::new(); +#[derive(Clone, Copy, Debug, Default)] +pub struct ShortEventFormatter { + ansi: bool, +} + +impl ShortEventFormatter { + pub const fn ansi() -> Self { + Self { ansi: true } + } + + pub const fn plain() -> Self { + Self { ansi: false } + } +} + +impl FormatEvent for ShortEventFormatter +where + S: Subscriber + for<'lookup> LookupSpan<'lookup>, + N: for<'writer> FormatFields<'writer> + 'static, +{ + fn format_event( + &self, + ctx: &FmtContext<'_, S, N>, + mut writer: Writer<'_>, + event: &Event<'_>, + ) -> fmt::Result { + let metadata = event.metadata(); + + let (parent, name) = match metadata.file() { + Some(file) => { + let path = Path::new(file); + match (path.parent().and_then(Path::file_name), path.file_name()) { + (Some(p), Some(n)) => (p, n), + _ => return Ok(()), + } + } + None => return Ok(()), + }; + + let ansi = self.ansi; + let time = chrono::Local::now().format("%H:%M:%S"); + if ansi { + write!(writer, "\x1b[2m{time}\x1b[0m ")?; + let level_color = match *metadata.level() { + tracing::Level::TRACE => "\x1b[35m", + tracing::Level::DEBUG => "\x1b[34m", + tracing::Level::INFO => "\x1b[32m", + tracing::Level::WARN => "\x1b[33m", + tracing::Level::ERROR => "\x1b[31m", + }; + write!(writer, "{level_color}{:<5}\x1b[0m ", metadata.level())?; + } else { + write!(writer, "{time} {:<5} ", metadata.level())?; + } + + if ansi { + write!(writer, "\x1b[2m")?; + } + write!( + writer, + "{}/{}:{}", + parent.to_string_lossy(), + name.to_string_lossy(), + metadata.line().unwrap_or_default() + )?; + if ansi { + write!(writer, "\x1b[0m")?; + } + write!(writer, " ")?; + + if let Some(scope) = ctx.event_scope() { + for span in scope.from_root() { + let extensions = span.extensions(); + if let Some(fields) = extensions.get::>() { + if !fields.is_empty() { + if ansi { + for field in fields.fields.split_whitespace() { + let color = if field.starts_with("receipt_id=") { + "\x1b[36m" + } else if field.starts_with("local_user_id=") { + "\x1b[35m" + } else { + "" + }; + write!(writer, "{color}{field}\x1b[0m ")?; + } + } else { + write!(writer, "{fields} ")?; + } + } + } + } + } + + ctx.field_format().format_fields(writer.by_ref(), event)?; + writeln!(writer) + } +} + pub(crate) async fn init_tracing(logs_dir: &std::path::Path, is_dart_available: bool) { let _ = std::fs::create_dir_all(logs_dir); @@ -25,7 +136,7 @@ pub(crate) async fn init_tracing(logs_dir: &std::path::Path, is_dart_available: let stdout_layer = Layer::new() .with_writer(non_blocking_stdout) .with_ansi(true) - .with_target(true); + .event_format(ShortEventFormatter::ansi()); // let file_layer = Layer::new() // .with_writer(non_blocking_file) @@ -52,7 +163,7 @@ pub(crate) async fn init_tracing(logs_dir: &std::path::Path, is_dart_available: let dart_layer = tracing_subscriber::fmt::Layer::new() .with_writer(dart_writer) .with_ansi(false) - .with_target(true); + .event_format(ShortEventFormatter::plain()); let _ = registry.with(dart_layer).try_init(); } else { let _ = registry.try_init(); diff --git a/rust/src/sealed_sender.rs b/rust/src/sealed_sender.rs index be6d23be..1d46495e 100644 --- a/rust/src/sealed_sender.rs +++ b/rust/src/sealed_sender.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use chacha20poly1305::{ aead::{Aead, Payload}, KeyInit, XChaCha20Poly1305, XNonce, @@ -10,12 +15,9 @@ use sha2::Sha256; use std::time::{SystemTime, UNIX_EPOCH}; use thiserror::Error; +use crate::api::proto::client as proto; use crate::context::Context; -pub mod proto { - include!(concat!(env!("OUT_DIR"), "/client_messages.rs")); -} - const PAYLOAD_MAGIC: &str = "twonly-message-envelope-v1"; const ENCRYPTION_CONTEXT: &[u8] = b"twonly-message-envelope-encryption-v1"; const X25519_PUBLIC_KEY_SIZE: usize = 32; @@ -82,6 +84,7 @@ pub enum SealedSenderError { pub(crate) struct SealedSender; impl SealedSender { + #[allow(dead_code)] pub(crate) fn encrypt( from_user_id: i64, recipient_user_id: i64, @@ -104,6 +107,7 @@ impl SealedSender { ) } + #[allow(dead_code)] fn encrypt_at( from_user_id: i64, recipient_user_id: i64, @@ -359,12 +363,16 @@ mod tests { pre_key_store: Default::default(), }); } - let database = context.get_rust_database().await; - sqlx::query( - "INSERT INTO signal_identities (name, identity_key, timestamp) VALUES (?, ?, 0)", + let database = context.get_rust_db().await; + let sender_identity = sender.identity_key().serialize(); + sqlx::query!( + r#" + INSERT INTO signal_identities (name, identity_key, timestamp) + VALUES (?, ?, 0) + "#, + "41", + sender_identity.as_ref(), ) - .bind("41") - .bind(sender.identity_key().serialize().as_ref()) .execute(&database.pool) .await .unwrap(); diff --git a/rust/src/secure_storage.rs b/rust/src/secure_storage.rs index 0d44de4a..4eed0bd7 100644 --- a/rust/src/secure_storage.rs +++ b/rust/src/secure_storage.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use keyring_core::{Entry, Error as KeyringError}; /// A simple wrapper around `keyring-core` for secure storage on iOS, Android, and other platforms. diff --git a/rust/src/services/contacts.rs b/rust/src/services/contacts.rs new file mode 100644 index 00000000..c6830dce --- /dev/null +++ b/rust/src/services/contacts.rs @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::messages::outgoing::send_c2c_message_to_contact; +use crate::api::proto::client::{self as proto, encrypted_content}; +use crate::api::Server; +use crate::bridge::api::ServerResult; +use crate::context::Context; +use crate::database::app::tables::{Contact, Group, UpdateContact}; +use crate::error::{Result, TwonlyError}; +use crate::signal::engine::FrbPreKeyBundle; +use prost::Message as _; +use std::io::Write as _; +use std::sync::Arc; + +pub struct ContactService { + ctx: Arc, +} + +impl ContactService { + pub fn new(ctx: &Arc) -> Self { + Self { ctx: ctx.clone() } + } + + pub async fn request_by_username(&self, username: String, blocking: bool) -> Result<()> { + let user = match Server::get_user_by_username(&self.ctx, username.clone()).await? { + ServerResult::Ok(user) => user, + ServerResult::ErrorCode(code) => { + return Err(TwonlyError::Generic(format!( + "User not found or server error: {code}" + ))); + } + }; + + let missing = TwonlyError::ApiResponseMissingField; + let pqc_bundle = user.pqc_bundle.as_ref().ok_or(missing("pqc_bundle"))?; + let identity_key = user + .public_identity_key + .clone() + .ok_or(missing("public_identity_key"))?; + let registration_id = user.registration_id.ok_or(missing("registration_id"))?; + + let (pre_key_id, pre_key_public, kyber_pre_key_id, kyber_pre_key_public, kyber_signature) = + if let Some(prekey) = &pqc_bundle.prekey { + ( + Some(prekey.ecc_pre_key_id as u32), + Some(prekey.ecc_pre_key.clone()), + prekey.kyber_pre_key_id as u32, + prekey.kyber_pre_key.clone(), + prekey.kyber_pre_key_signature.clone(), + ) + } else { + let prekey = user.prekeys.first(); + ( + prekey.map(|value| value.id as u32), + prekey.map(|value| value.prekey.clone()), + pqc_bundle.kyber_signed_prekey_id as u32, + pqc_bundle.kyber_signed_prekey.clone(), + pqc_bundle.kyber_signed_prekey_signature.clone(), + ) + }; + + self.ctx + .get_signal_engine() + .lock() + .await + .as_ref() + .ok_or(TwonlyError::SignalIdentityNotFound)? + .process_prekey_bundle( + user.user_id.to_string(), + 1, + FrbPreKeyBundle { + registration_id: registration_id as u32, + device_id: 1, + pre_key_id, + pre_key_public, + signed_pre_key_id: pqc_bundle.ecc_signed_prekey_id as u32, + signed_pre_key_public: pqc_bundle.ecc_signed_prekey.clone(), + signed_pre_key_signature: pqc_bundle.ecc_signed_prekey_signature.clone(), + kyber_pre_key_id, + kyber_pre_key_public, + kyber_pre_key_signature: kyber_signature, + identity_key, + }, + ) + .await?; + + let database = self.ctx.get_app_database().await; + let mut transaction = database.pool.begin().await?; + UpdateContact::builder() + .user_id(user.user_id) + .username(username) + .signal_version("v2".to_owned()) + .requested(false) + .blocked(false) + .deleted_by_user(false) + .build() + .insert_on_conflict_update(&mut transaction) + .await?; + transaction.commit().await?; + database.notify_committed(["contacts"]); + + self.send_contact_request( + user.user_id, + encrypted_content::contact_request::Type::Request, + blocking, + ) + .await + } + + pub async fn accept_request(&self, contact_id: i64, blocking: bool) -> Result<()> { + let database = self.ctx.get_app_database().await; + let mut transaction = database.pool.begin().await?; + let contact = Contact::get_contact_by_id(&mut transaction, contact_id) + .await? + .ok_or_else(|| TwonlyError::Generic("contact request does not exist".into()))?; + if contact.requested == 0 { + return Err(TwonlyError::Generic( + "contact has no pending request".into(), + )); + } + + UpdateContact::builder() + .user_id(contact_id) + .requested(false) + .accepted(true) + .deleted_by_user(false) + .build() + .update(&mut transaction) + .await?; + Group::create_direct_chat(&self.ctx, &mut transaction, contact).await?; + transaction.commit().await?; + database.notify_committed(["contacts", "groups"]); + + self.send_contact_request( + contact_id, + encrypted_content::contact_request::Type::Accept, + blocking, + ) + .await + } + + pub async fn reject_request(&self, contact_id: i64, blocking: bool) -> Result<()> { + let db_app = self.ctx.get_app_database().await; + + let mut t = db_app.pool.begin().await?; + + let contact = Contact::get_contact_by_id(&mut t, contact_id) + .await? + .ok_or_else(|| TwonlyError::Generic("contact request does not exist".into()))?; + + if contact.requested == 0 { + return Err(TwonlyError::Generic( + "contact has no pending request".into(), + )); + } + + UpdateContact::builder() + .user_id(contact_id) + .requested(false) + .accepted(false) + .deleted_by_user(true) + .build() + .update(&mut t) + .await?; + + t.commit().await?; + db_app.notify_committed(["contacts"]); + + self.send_contact_request( + contact_id, + encrypted_content::contact_request::Type::Reject, + blocking, + ) + .await + } + + pub async fn send_profile(&self, contact_id: i64) -> Result<()> { + let config = crate::user_config::UserConfig::load_required_from(&self.ctx)?; + + let avatar_svg_compressed = config + .avatar_svg + .as_deref() + .map(|avatar| { + let mut encoder = + flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); + encoder.write_all(avatar.as_bytes())?; + encoder.finish() + }) + .transpose()?; + + send_c2c_message_to_contact() + .ctx(&self.ctx) + .contact_id(contact_id) + .encrypted_content( + proto::EncryptedContent { + contact_update: Some(encrypted_content::ContactUpdate { + r#type: encrypted_content::contact_update::Type::Update as i32, + username: config.username, + display_name: config.display_name, + avatar_svg_compressed, + }), + ..Default::default() + } + .encode_to_vec(), + ) + .call() + .await?; + + Ok(()) + } + + async fn send_contact_request( + &self, + contact_id: i64, + request_type: encrypted_content::contact_request::Type, + blocking: bool, + ) -> Result<()> { + send_c2c_message_to_contact() + .ctx(&self.ctx) + .contact_id(contact_id) + .encrypted_content( + proto::EncryptedContent { + contact_request: Some(encrypted_content::ContactRequest { + r#type: request_type as i32, + }), + ..Default::default() + } + .encode_to_vec(), + ) + .blocking(blocking) + .call() + .await?; + Ok(()) + } +} diff --git a/rust/src/services/groups/crypto.rs b/rust/src/services/groups/crypto.rs new file mode 100644 index 00000000..466a2b08 --- /dev/null +++ b/rust/src/services/groups/crypto.rs @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::proto::client::EncryptedGroupStateEnvelop; +use crate::error::{Result, TwonlyError}; +use chacha20poly1305::aead::{Aead, AeadCore, KeyInit, OsRng}; +use chacha20poly1305::{ChaCha20Poly1305, Nonce}; +use prost::Message; + +pub(crate) fn encrypt(key: &[u8], plaintext: &[u8]) -> Result> { + let cipher = ChaCha20Poly1305::new_from_slice(key) + .map_err(|_| TwonlyError::Generic("invalid group state key".into()))?; + let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng); + let mut sealed = cipher + .encrypt(&nonce, plaintext) + .map_err(|_| TwonlyError::Generic("group state encryption failed".into()))?; + let mac = sealed.split_off(sealed.len().saturating_sub(16)); + Ok(EncryptedGroupStateEnvelop { + nonce: nonce.to_vec(), + encrypted_group_state: sealed, + mac, + } + .encode_to_vec()) +} + +pub(crate) fn decrypt(key: &[u8], envelope: &[u8]) -> Result> { + let envelope = EncryptedGroupStateEnvelop::decode(envelope) + .map_err(|error| TwonlyError::Generic(error.to_string()))?; + let cipher = ChaCha20Poly1305::new_from_slice(key) + .map_err(|_| TwonlyError::Generic("invalid group state key".into()))?; + let nonce = Nonce::from_slice(&envelope.nonce); + let mut sealed = envelope.encrypted_group_state; + sealed.extend(envelope.mac); + cipher + .decrypt(nonce, sealed.as_ref()) + .map_err(|_| TwonlyError::Generic("group state authentication failed".into())) +} + +#[cfg(test)] +mod tests { + #[test] + fn group_state_round_trip() { + let key = [7_u8; 32]; + let encrypted = super::encrypt(&key, b"state").unwrap(); + assert_eq!(super::decrypt(&key, &encrypted).unwrap(), b"state"); + } +} diff --git a/rust/src/services/groups/mod.rs b/rust/src/services/groups/mod.rs new file mode 100644 index 00000000..d127a370 --- /dev/null +++ b/rust/src/services/groups/mod.rs @@ -0,0 +1,593 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +pub(crate) mod crypto; +pub(crate) mod model; + +use crate::api::groups::GroupApi; +use crate::api::messages::outgoing::send_c2c_message_to_contact; +use crate::api::proto::client::{ + encrypted_appended_group_state, encrypted_content, EncryptedAppendedGroupState, + EncryptedContent, EncryptedGroupState, +}; +use crate::api::proto::http_requests::{append_group_state, AppendGroupState, NewGroupState}; +use crate::api::server::Server; +use crate::bridge::api::ServerResult; +use crate::context::Context; +use crate::database::app::tables::{ + Contact, GetGroupPublicKey, GetMissingGroupPublicKeys, GetUnjoinedGroups, InsertGroup, + InsertGroupHistory, InsertGroupMember, UpdateContact, UpdateGroup, UpdateGroupMemberState, +}; +use crate::error::{Result, TwonlyError}; +use crate::services::messages::MessageService; +use crate::utils::new_uuid_v4; +use model::GroupRecord; +use prost::Message as _; +use rand::{RngCore, SeedableRng}; +use std::collections::BTreeSet; +use std::sync::Arc; + +const DEFAULT_DELETE_MS: i64 = 86_400_000; + +pub struct GroupService { + ctx: Arc, +} + +impl GroupService { + pub fn new(ctx: &Arc) -> Self { + Self { ctx: ctx.clone() } + } + + pub async fn create_group(&self, group_name: String, member_ids: Vec) -> Result { + let local = self.ctx.user_id().await?; + let group_id = new_uuid_v4(); + let mut key = vec![0; 32]; + rand::rng().fill_bytes(&mut key); + let mut rng = rand::rngs::StdRng::from_os_rng(); + let identity = libsignal_protocol::IdentityKeyPair::generate(&mut rng); + let members: Vec = std::iter::once(local) + .chain(member_ids.iter().copied()) + .collect::>() + .into_iter() + .collect(); + let group_state = EncryptedGroupState { + member_ids: members, + admin_ids: vec![local], + group_name: group_name.clone(), + delete_messages_after_milliseconds: Some(DEFAULT_DELETE_MS), + padding: vec![], + }; + + GroupApi::create(NewGroupState { + group_id: group_id.clone(), + version_id: 1, + encrypted_group_state: crypto::encrypt(&key, &group_state.encode_to_vec())?, + public_key: identity.identity_key().serialize().to_vec(), + }) + .await?; + + let db = self.ctx.get_app_database().await; + let mut tr = db.pool.begin().await?; + let serialized_identity = identity.serialize().to_vec(); + InsertGroup::builder() + .group_id(group_id.clone()) + .group_name(group_name.clone()) + .state_encryption_key(key.clone()) + .my_group_private_key(serialized_identity) + .build() + .execute(&mut tr) + .await?; + for id in member_ids { + InsertGroupMember::builder() + .group_id(group_id.clone()) + .contact_id(id) + .member_state("normal".into()) + .build() + .execute(&mut tr) + .await?; + } + Self::history(&mut tr, &group_id, "createdGroup", None, None, None).await?; + tr.commit().await?; + db.notify_committed(["groups", "group_members", "group_histories"]); + MessageService::new(&self.ctx) + .send_to_group( + group_id.clone(), + EncryptedContent { + group_create: Some(encrypted_content::GroupCreate { + state_key: key, + group_public_key: identity.identity_key().serialize().to_vec(), + group_name: Some(group_name), + }), + ..Default::default() + } + .encode_to_vec(), + None, + true, + ) + .await?; + Ok(true) + } + + pub async fn fetch_group_state(&self, group_id: String) -> Result { + let database = self.ctx.get_app_database().await; + let group = GroupRecord::load(&database.pool, &group_id).await?; + let Some(server) = GroupApi::fetch_group_state(&group_id).await? else { + let mut tr = database.pool.begin().await?; + UpdateGroup::builder() + .group_id(group_id.clone()) + .left_group(true) + .build() + .execute(&mut tr) + .await?; + tr.commit().await?; + database.notify_committed(["groups"]); + return Ok(false); + }; + let raw = crypto::decrypt(group.state_key()?, &server.encrypted_group_state)?; + let mut group_state = EncryptedGroupState::decode(raw.as_slice()) + .map_err(|error| TwonlyError::Generic(error.to_string()))?; + let mut appended_changes = false; + for appended in &server.appended_group_states { + let Some(tbs) = &appended.append_tbs else { + continue; + }; + let public_key = libsignal_protocol::PublicKey::try_from(tbs.public_key.as_slice()) + .map_err(|error| TwonlyError::Signal(error.to_string()))?; + if !public_key.verify_signature(&tbs.encode_to_vec(), &appended.signature) { + tracing::warn!( + group_id, + "ignored group-state append with invalid signature" + ); + continue; + } + let raw = crypto::decrypt(group.state_key()?, &tbs.encrypted_group_state_append)?; + let append = EncryptedAppendedGroupState::decode(raw.as_slice()) + .map_err(|error| TwonlyError::Generic(error.to_string()))?; + if append.r#type != encrypted_appended_group_state::Type::LeftGroup as i32 { + continue; + } + let local_public_key = group.identity()?.identity_key().serialize(); + let leaving_id = if tbs.public_key.as_slice() == local_public_key.as_ref() { + Some(self.ctx.user_id().await?) + } else { + sqlx::query_scalar!( + "SELECT contact_id FROM group_members WHERE group_id = ? AND group_public_key = ?", + group_id, + tbs.public_key, + ) + .fetch_optional(&database.pool) + .await? + }; + if let Some(leaving_id) = leaving_id { + group_state.member_ids.retain(|id| *id != leaving_id); + group_state.admin_ids.retain(|id| *id != leaving_id); + appended_changes = true; + } + } + if appended_changes && group_state.admin_ids.contains(&self.ctx.user_id().await?) { + GroupApi::update_remote(&group, server.version_id, &group_state, None, None).await?; + } + self.apply_state( + &database.pool, + &group_id, + server.version_id as i64, + &group_state, + ) + .await?; + database.notify_committed(["groups", "group_members", "contacts"]); + Ok(true) + } + + pub async fn update_group_name(&self, group_id: String, name: String) -> Result { + let (db, g) = self.load_group(&group_id).await?; + let (v, mut s) = GroupApi::load_state(&g).await?; + s.group_name = name.clone(); + GroupApi::update_remote(&g, v, &s, None, None).await?; + self.announce( + &group_id, + "updatedGroupName", + None, + Some(name.clone()), + None, + ) + .await?; + let mut tr = db.pool.begin().await?; + Self::history( + &mut tr, + &group_id, + "updatedGroupName", + None, + Some(&name), + None, + ) + .await?; + tr.commit().await?; + self.fetch_group_state(group_id).await + } + + pub async fn update_chat_deletion_time(&self, group_id: String, ms: i64) -> Result { + let (db, g) = self.load_group(&group_id).await?; + let (v, mut s) = GroupApi::load_state(&g).await?; + s.delete_messages_after_milliseconds = Some(ms); + GroupApi::update_remote(&g, v, &s, None, None).await?; + self.announce(&group_id, "changeDisplayMaxTime", None, None, Some(ms)) + .await?; + let mut tr = db.pool.begin().await?; + Self::history( + &mut tr, + &group_id, + "changeDisplayMaxTime", + None, + None, + Some(ms), + ) + .await?; + tr.commit().await?; + self.fetch_group_state(group_id).await + } + + pub async fn manage_admin( + &self, + group_id: String, + contact_id: i64, + remove: bool, + ) -> Result { + let (db, g) = self.load_group(&group_id).await?; + let (v, mut s) = GroupApi::load_state(&g).await?; + + let public_key = GetGroupPublicKey::builder() + .group_id(group_id.clone()) + .contact_id(contact_id) + .build() + .fetch_pool(&db.pool) + .await? + .ok_or_else(|| { + TwonlyError::Generic(format!( + "group public key for contact {contact_id} not found" + )) + })?; + + if remove { + s.admin_ids.retain(|x| *x != contact_id) + } else if !s.admin_ids.contains(&contact_id) { + s.admin_ids.push(contact_id) + }; + let kind = if remove { + "demoteToMember" + } else { + "promoteToAdmin" + }; + GroupApi::update_remote( + &g, + v, + &s, + (!remove).then_some(public_key.clone()), + remove.then_some(public_key), + ) + .await?; + self.announce(&group_id, kind, Some(contact_id), None, None) + .await?; + let mut tr = db.pool.begin().await?; + Self::history(&mut tr, &group_id, kind, Some(contact_id), None, None).await?; + tr.commit().await?; + self.fetch_group_state(group_id).await + } + + pub async fn add_members(&self, group_id: String, ids: Vec) -> Result { + let (db, g) = self.load_group(&group_id).await?; + let (v, mut s) = GroupApi::load_state(&g).await?; + for id in &ids { + if !s.member_ids.contains(id) { + s.member_ids.push(*id) + } + } + GroupApi::update_remote(&g, v, &s, None, None).await?; + let identity = g.identity()?; + for id in ids { + self.announce(&group_id, "addMember", Some(id), None, None) + .await?; + let mut tr = db.pool.begin().await?; + Self::history(&mut tr, &group_id, "addMember", Some(id), None, None).await?; + tr.commit().await?; + send_c2c_message_to_contact() + .ctx(&self.ctx) + .contact_id(id) + .encrypted_content( + EncryptedContent { + group_id: Some(group_id.clone()), + group_create: Some(encrypted_content::GroupCreate { + state_key: g.state_key()?.to_vec(), + group_public_key: identity.identity_key().serialize().to_vec(), + group_name: Some(g.group_name.clone()), + }), + ..Default::default() + } + .encode_to_vec(), + ) + .call() + .await?; + } + self.fetch_group_state(group_id).await + } + + pub async fn remove_member( + &self, + group_id: String, + public_key: Vec, + contact_id: i64, + ) -> Result { + let (db, g) = self.load_group(&group_id).await?; + let (v, mut s) = GroupApi::load_state(&g).await?; + if !s.member_ids.contains(&contact_id) { + return Ok(true); + } + s.member_ids.retain(|x| *x != contact_id); + let was_admin = s.admin_ids.contains(&contact_id); + s.admin_ids.retain(|x| *x != contact_id); + GroupApi::update_remote(&g, v, &s, None, was_admin.then_some(public_key)).await?; + self.announce(&group_id, "removedMember", Some(contact_id), None, None) + .await?; + let mut tr = db.pool.begin().await?; + Self::history( + &mut tr, + &group_id, + "removedMember", + Some(contact_id), + None, + None, + ) + .await?; + tr.commit().await?; + self.fetch_group_state(group_id).await + } + + pub async fn leave_group(&self, group_id: String) -> Result { + let (db, group) = self.load_group(&group_id).await?; + let identity = group.identity()?; + let public_key = identity.identity_key().serialize().to_vec(); + let append = EncryptedAppendedGroupState { + r#type: encrypted_appended_group_state::Type::LeftGroup as i32, + }; + let append_tbs = append_group_state::AppendTbs { + encrypted_group_state_append: crypto::encrypt( + group.state_key()?, + &append.encode_to_vec(), + )?, + public_key: public_key.clone(), + group_id: group_id.clone(), + nonce: GroupApi::get_challenge(&public_key).await?, + }; + let mut rng = rand::rngs::StdRng::from_os_rng(); + let signature = identity + .private_key() + .calculate_signature(&append_tbs.encode_to_vec(), &mut rng) + .map_err(|error| TwonlyError::Signal(error.to_string()))? + .into_vec(); + GroupApi::append(AppendGroupState { + signature, + append_tbs: Some(append_tbs), + version_id: group.state_version_id as u64 + 1, + }) + .await?; + self.announce(&group_id, "leftGroup", None, None, None) + .await?; + let mut tr = db.pool.begin().await?; + Self::history(&mut tr, &group_id, "leftGroup", None, None, None).await?; + UpdateGroup::builder() + .group_id(group_id.clone()) + .left_group(true) + .build() + .execute(&mut tr) + .await?; + tr.commit().await?; + db.notify_committed(["groups", "group_histories"]); + Ok(true) + } + + pub async fn add_hidden_contact(&self, contact_id: i64) -> Result { + let user = match Server::get_user_by_id(&self.ctx, contact_id).await? { + ServerResult::Ok(u) => u, + ServerResult::ErrorCode(code) => { + return Err(TwonlyError::Generic(format!( + "Failed to get user by id: {}", + code + ))); + } + }; + let username = String::from_utf8( + user.username + .ok_or_else(|| TwonlyError::Generic("user response has no username".into()))?, + )?; + let database = self.ctx.get_app_database().await; + let mut tr = database.pool.begin().await?; + UpdateContact::builder() + .user_id(contact_id) + .username(username) + .deleted_by_user(true) + .build() + .insert_on_conflict_update(&mut tr) + .await?; + tr.commit().await?; + database.notify_committed(["contacts"]); + Ok(true) + } + + pub async fn fetch_group_states_for_unjoined_groups(&self) -> Result<()> { + let db = self.ctx.get_app_database().await; + let ids = GetUnjoinedGroups::builder() + .build() + .fetch_all(&db.pool) + .await?; + for id in ids { + if let Err(e) = self.fetch_group_state(id.clone()).await { + tracing::warn!(group_id = id, "group state refresh failed: {e}") + } + } + Ok(()) + } + + pub async fn fetch_missing_group_public_keys(&self) -> Result<()> { + let db = self.ctx.get_app_database().await; + let rows = GetMissingGroupPublicKeys::builder() + .build() + .fetch_all(&db.pool) + .await?; + for row in rows { + send_c2c_message_to_contact() + .ctx(&self.ctx) + .contact_id(row.contact_id) + .encrypted_content( + EncryptedContent { + group_id: Some(row.group_id), + resend_group_public_key: Some(encrypted_content::ResendGroupPublicKey {}), + ..Default::default() + } + .encode_to_vec(), + ) + .call() + .await?; + } + Ok(()) + } + + // --- Private helpers --- + + async fn load_group( + &self, + group_id: &str, + ) -> Result<(Arc, GroupRecord)> { + let db = self.ctx.get_app_database().await; + let row = GroupRecord::load(&db.pool, group_id).await?; + Ok((db, row)) + } + + async fn announce( + &self, + group_id: &str, + action: &str, + affected: Option, + name: Option, + delete_ms: Option, + ) -> Result<()> { + MessageService::new(&self.ctx) + .send_to_group( + group_id.into(), + EncryptedContent { + group_update: Some(encrypted_content::GroupUpdate { + group_action_type: action.into(), + affected_contact_id: affected, + new_group_name: name, + new_delete_messages_after_milliseconds: delete_ms, + }), + ..Default::default() + } + .encode_to_vec(), + None, + false, + ) + .await + } + + async fn history( + tr: &mut sqlx::Transaction<'_, sqlx::Sqlite>, + group_id: &str, + kind: &str, + affected: Option, + name: Option<&str>, + delete_ms: Option, + ) -> Result<()> { + InsertGroupHistory::builder() + .group_id(group_id.to_string()) + .r#type(kind.to_string()) + .maybe_affected_contact_id(affected) + .maybe_new_group_name(name.map(|n| n.to_string())) + .maybe_new_delete_messages_after_milliseconds(delete_ms) + .build() + .execute(tr) + .await?; + Ok(()) + } + + async fn apply_state( + &self, + pool: &sqlx::SqlitePool, + group_id: &str, + version: i64, + group_state: &EncryptedGroupState, + ) -> Result<()> { + let local_user_id = self.ctx.user_id().await?; + let joined = group_state.member_ids.contains(&local_user_id); + let admin = group_state.admin_ids.contains(&local_user_id); + + let mut tr = pool.begin().await?; + + UpdateGroup::builder() + .group_id(group_id.to_string()) + .group_name(group_state.group_name.clone()) + .delete_messages_after_milliseconds(group_state.delete_messages_after_milliseconds) + .is_group_admin(admin) + .joined_group(joined) + .left_group(!joined) + .state_version_id(version as u64) + .build() + .execute(&mut tr) + .await?; + + UpdateGroupMemberState::builder() + .group_id(group_id.to_string()) + .member_state("leftGroup".into()) + .build() + .execute(&mut tr) + .await?; + + for &contact_id in &group_state.member_ids { + if contact_id == local_user_id { + continue; + } + + let exists = Contact::exists(&mut tr, contact_id).await?; + + if !exists { + let user = match Server::get_user_by_id(&self.ctx, contact_id).await? { + ServerResult::Ok(u) => u, + ServerResult::ErrorCode(code) => { + return Err(TwonlyError::Generic(format!( + "Failed to get user by id: {}", + code + ))); + } + }; + let username = String::from_utf8(user.username.ok_or_else(|| { + TwonlyError::Generic("user response has no username".into()) + })?)?; + UpdateContact::builder() + .user_id(contact_id) + .username(username) + .deleted_by_user(true) + .build() + .insert_on_conflict_update(&mut tr) + .await?; + } + + let member_state = if group_state.admin_ids.contains(&contact_id) { + "admin" + } else { + "normal" + }; + + InsertGroupMember::builder() + .group_id(group_id.to_string()) + .contact_id(contact_id) + .member_state(member_state.into()) + .on_conflict_update(true) + .build() + .execute(&mut tr) + .await?; + } + + tr.commit().await?; + Ok(()) + } +} diff --git a/rust/src/services/groups/model.rs b/rust/src/services/groups/model.rs new file mode 100644 index 00000000..397b1b6b --- /dev/null +++ b/rust/src/services/groups/model.rs @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use sqlx::SqlitePool; + +use crate::error::{Result, TwonlyError}; + +#[derive(sqlx::FromRow, Clone)] +pub(crate) struct GroupRecord { + pub group_id: String, + pub group_name: String, + pub state_version_id: i64, + pub state_encryption_key: Option>, + pub my_group_private_key: Option>, +} + +impl GroupRecord { + pub async fn load(pool: &SqlitePool, group_id: &str) -> Result { + sqlx::query_as!( + Self, + r#"SELECT group_id, group_name, state_version_id, + state_encryption_key, my_group_private_key + FROM groups WHERE group_id = ?"#, + group_id, + ) + .fetch_optional(pool) + .await? + .ok_or_else(|| TwonlyError::Generic(format!("group {group_id} does not exist"))) + } + + pub fn state_key(&self) -> Result<&[u8]> { + self.state_encryption_key + .as_deref() + .ok_or_else(|| TwonlyError::Generic("group state key is unavailable".into())) + } + + pub fn identity(&self) -> Result { + libsignal_protocol::IdentityKeyPair::try_from( + self.my_group_private_key + .as_deref() + .ok_or_else(|| TwonlyError::Generic("group private key is unavailable".into()))?, + ) + .map_err(|error| TwonlyError::Signal(error.to_string())) + } +} diff --git a/rust/src/services/messages.rs b/rust/src/services/messages.rs new file mode 100644 index 00000000..6e4210fb --- /dev/null +++ b/rust/src/services/messages.rs @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use crate::api::messages::incoming::client2client::messages; +use crate::api::messages::outgoing::send_c2c_message_to_contact; +use crate::api::proto::client::{self as proto, encrypted_content}; +use crate::context::Context; +use crate::database::app::tables::{Contact, Group}; +use crate::error::{Result, TwonlyError}; +use crate::user_config::UserConfig; +use prost::Message as _; +use std::sync::Arc; + +pub struct MessageService { + ctx: Arc, +} + +impl MessageService { + pub fn new(ctx: &Arc) -> Self { + Self { ctx: ctx.clone() } + } + + pub async fn send_to_group( + &self, + group_id: String, + encrypted_content: Vec, + message_id: Option, + only_send_if_no_receipts_are_open: bool, + ) -> Result<()> { + let mut content = proto::EncryptedContent::decode(encrypted_content.as_slice())?; + content.group_id = Some(group_id.clone()); + let database = self.ctx.get_app_database().await; + if message_id.is_some() + || content.reaction.is_some() + || content.media.is_some() + || content.text_message.is_some() + { + sqlx::query!("UPDATE groups SET last_message_exchange = CAST(strftime('%s','now') AS INTEGER) WHERE group_id = ?", group_id) + .execute(&database.pool).await?; + database.notify_committed(["groups"]); + } + let members = sqlx::query_scalar!( + r#"SELECT contact_id FROM group_members + WHERE group_id = ? AND (member_state IS NULL OR member_state != 'leftGroup')"#, + group_id, + ) + .fetch_all(&database.pool) + .await?; + let bytes = content.encode_to_vec(); + for contact_id in members { + send_c2c_message_to_contact() + .ctx(&self.ctx) + .contact_id(contact_id) + .encrypted_content(bytes.clone()) + .maybe_message_id(message_id.clone()) + .only_send_if_no_receipts_are_open(only_send_if_no_receipts_are_open) + .call() + .await?; + } + Ok(()) + } + + pub async fn insert_and_send_text( + &self, + group_id: String, + text: String, + quote_message_id: Option, + ) -> Result { + let database = self.ctx.get_app_database().await; + let message_id = uuid::Uuid::new_v4().to_string(); + let timestamp = chrono::Utc::now().timestamp_millis(); + sqlx::query!( + "UPDATE groups SET draft_message = NULL WHERE group_id = ?", + group_id + ) + .execute(&database.pool) + .await?; + database.notify_committed(["groups"]); + sqlx::query!( + r#"INSERT INTO messages(group_id, message_id, type, content, quotes_message_id, created_at) + VALUES (?, ?, 'text', ?, ?, ?)"#, + group_id, + message_id, + text, + quote_message_id, + timestamp / 1000, + ) + .execute(&database.pool) + .await?; + database.notify_committed(["messages"]); + self.send_to_group( + group_id, + proto::EncryptedContent { + text_message: Some(encrypted_content::TextMessage { + sender_message_id: message_id.clone(), + text, + timestamp, + quote_message_id, + }), + ..Default::default() + } + .encode_to_vec(), + Some(message_id.clone()), + false, + ) + .await?; + Ok(message_id) + } + + pub async fn insert_and_send_additional_data( + &self, + group_id: String, + message_type: String, + additional_data: Vec, + ) -> Result { + let database = self.ctx.get_app_database().await; + let message_id = uuid::Uuid::new_v4().to_string(); + let timestamp = chrono::Utc::now().timestamp_millis(); + sqlx::query!( + r#"INSERT INTO messages(group_id, message_id, type, additional_message_data, created_at) + VALUES (?, ?, ?, ?, ?)"#, + group_id, + message_id, + message_type, + additional_data, + timestamp / 1000, + ) + .execute(&database.pool) + .await?; + database.notify_committed(["messages"]); + self.send_to_group( + group_id, + proto::EncryptedContent { + additional_data_message: Some(encrypted_content::AdditionalDataMessage { + sender_message_id: message_id.clone(), + additional_message_data: Some(additional_data), + timestamp, + r#type: message_type, + }), + ..Default::default() + } + .encode_to_vec(), + Some(message_id.clone()), + false, + ) + .await?; + Ok(message_id) + } + + pub async fn insert_and_send_contact_share( + &self, + group_id: String, + contact_ids: Vec, + ) -> Result { + let app = self.ctx.get_app_database().await; + let signal = self.ctx.get_rust_db().await; + let mut contacts = Vec::new(); + for contact_id in contact_ids { + let contact = sqlx::query!( + "SELECT username, display_name FROM contacts WHERE user_id = ?", + contact_id + ) + .fetch_optional(&app.pool) + .await?; + let identity = sqlx::query_scalar!( + "SELECT identity_key FROM signal_identities WHERE name = ?", + contact_id.to_string() + ) + .fetch_optional(&signal.pool) + .await?; + if let (Some(contact), Some(public_identity_key)) = (contact, identity) { + contacts.push(proto::SharedContact { + user_id: contact_id, + public_identity_key, + display_name: contact.display_name.unwrap_or(contact.username), + }); + } + } + let data = proto::AdditionalMessageData { + r#type: proto::additional_message_data::Type::Contacts as i32, + link: None, + contacts, + restored_flame_counter: None, + ask_about_user_id: None, + } + .encode_to_vec(); + self.insert_and_send_additional_data(group_id, "contacts".into(), data) + .await + } + + pub async fn insert_and_send_ask_about_user( + &self, + contact_id: i64, + ask_about_user_id: i64, + ) -> Result { + let local_user_id = self + .ctx + .get_key_manager() + .await? + .user_id + .ok_or_else(|| TwonlyError::Generic("local user ID is unavailable".into()))?; + let group_id = Group::direct_chat_id(local_user_id, contact_id); + let database = self.ctx.get_app_database().await; + let mut transaction = database.pool.begin().await?; + let contact = Contact::get_contact_by_id(&mut transaction, contact_id) + .await? + .ok_or_else(|| TwonlyError::Generic("contact does not exist".into()))?; + Group::create_direct_chat(&self.ctx, &mut transaction, contact).await?; + transaction.commit().await?; + database.notify_committed(["groups", "group_members"]); + let data = proto::AdditionalMessageData { + r#type: proto::additional_message_data::Type::AskAboutUser as i32, + link: None, + contacts: Vec::new(), + restored_flame_counter: None, + ask_about_user_id: Some(ask_about_user_id), + } + .encode_to_vec(); + self.insert_and_send_additional_data(group_id, "askAboutUser".into(), data) + .await + } + + pub async fn send_typing(&self, group_id: String, is_typing: bool) -> Result<()> { + if !UserConfig::load_from(&self.ctx)?.is_some_and(|config| config.typing_indicators) { + return Ok(()); + } + self.send_to_group( + group_id, + proto::EncryptedContent { + typing_indicator: Some(encrypted_content::TypingIndicator { + is_typing, + created_at: chrono::Utc::now().timestamp_millis(), + }), + ..Default::default() + } + .encode_to_vec(), + None, + true, + ) + .await + } + + pub async fn edit_text( + &self, + group_id: String, + message_id: String, + text: String, + ) -> Result<()> { + self.send_to_group( + group_id, + proto::EncryptedContent { + message_update: Some(encrypted_content::MessageUpdate { + r#type: encrypted_content::message_update::Type::EditText as i32, + sender_message_id: Some(message_id), + multiple_target_message_ids: Vec::new(), + text: Some(text), + timestamp: chrono::Utc::now().timestamp_millis(), + }), + ..Default::default() + } + .encode_to_vec(), + None, + false, + ) + .await + } + + pub async fn react( + &self, + group_id: String, + message_id: String, + emoji: String, + remove: bool, + ) -> Result<()> { + self.send_to_group( + group_id, + proto::EncryptedContent { + reaction: Some(encrypted_content::Reaction { + target_message_id: message_id, + emoji, + remove, + }), + ..Default::default() + } + .encode_to_vec(), + None, + false, + ) + .await + } + + pub async fn delete_message(&self, group_id: String, message_id: String) -> Result<()> { + self.send_to_group( + group_id, + proto::EncryptedContent { + message_update: Some(encrypted_content::MessageUpdate { + r#type: encrypted_content::message_update::Type::Delete as i32, + sender_message_id: Some(message_id), + multiple_target_message_ids: Vec::new(), + text: None, + timestamp: chrono::Utc::now().timestamp_millis(), + }), + ..Default::default() + } + .encode_to_vec(), + None, + false, + ) + .await + } + + pub async fn notify_opened(&self, contact_id: i64, message_ids: Vec) -> Result<()> { + if message_ids.is_empty() { + return Ok(()); + } + let timestamp = chrono::Utc::now().timestamp_millis(); + let content = proto::EncryptedContent { + message_update: Some(encrypted_content::MessageUpdate { + r#type: encrypted_content::message_update::Type::Opened as i32, + sender_message_id: None, + multiple_target_message_ids: message_ids.clone(), + text: None, + timestamp, + }), + ..Default::default() + }; + send_c2c_message_to_contact() + .ctx(&self.ctx) + .contact_id(contact_id) + .encrypted_content(content.encode_to_vec()) + .call() + .await?; + let database = self.ctx.get_app_database().await; + for message_id in message_ids { + sqlx::query!( + "UPDATE messages SET opened_at = ?, opened_by_all = ? WHERE message_id = ?", + timestamp / 1000, + timestamp / 1000, + message_id + ) + .execute(&database.pool) + .await?; + } + database.notify_committed(["messages"]); + Ok(()) + } + + pub async fn retransmit_all(&self) -> Result<()> { + messages::retransmit_queued_receipts(&self.ctx).await + } + + pub async fn send_receipt(&self, receipt_id: String) -> Result<()> { + messages::send_queued_receipt(&self.ctx, &receipt_id).await + } +} diff --git a/rust/src/services/mod.rs b/rust/src/services/mod.rs new file mode 100644 index 00000000..1031d4c2 --- /dev/null +++ b/rust/src/services/mod.rs @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +pub mod contacts; +pub mod groups; +pub mod messages; diff --git a/rust/src/signal/assert_send.rs b/rust/src/signal/assert_send.rs index 4cfcb625..ee0401f1 100644 --- a/rust/src/signal/assert_send.rs +++ b/rust/src/signal/assert_send.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; diff --git a/rust/src/signal/engine.rs b/rust/src/signal/engine.rs index 93838c9a..bb06d129 100644 --- a/rust/src/signal/engine.rs +++ b/rust/src/signal/engine.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::error::{Result, TwonlyError}; use libsignal_protocol::{ message_encrypt, process_prekey_bundle, CiphertextMessageType, DeviceId, GenericSignedPreKey, @@ -91,16 +96,50 @@ impl RustSignalEngine { Ok(key_pair.serialize().to_vec()) } + pub async fn generate_prekeys(&self, count: usize) -> Result)>> { + let mut store_guard = self.store.lock().await; + let store = &mut *store_guard; + let mut csprng = rand::rngs::StdRng::from_os_rng(); + let mut next_id = sqlx::query_scalar!( + r#"SELECT COALESCE(MAX(pre_key_id), 0) AS "id!: u32" FROM signal_pre_keys"#, + ) + .fetch_one(&store.pool) + .await?; + let mut prekeys = Vec::with_capacity(count); + + for _ in 0..count { + next_id = if next_id >= 16_777_215 { + 1 + } else { + next_id + 1 + }; + let key_pair = libsignal_protocol::KeyPair::generate(&mut csprng); + store + .pre_key_store + .save_pre_key( + next_id.into(), + &libsignal_protocol::PreKeyRecord::new(next_id.into(), &key_pair), + ) + .assert_send() + .await + .map_err(|error| TwonlyError::Signal(error.to_string()))?; + prekeys.push((next_id, key_pair.public_key.serialize().to_vec())); + } + + Ok(prekeys) + } + pub async fn generate_bundle(&self) -> Result { let mut store_guard = self.store.lock().await; let store = &mut *store_guard; let mut csprng = rand::rngs::StdRng::from_os_rng(); let pre_key_id: u32 = { - let id: u32 = - sqlx::query_scalar("SELECT COALESCE(MAX(pre_key_id), 0) FROM signal_pre_keys") - .fetch_one(&store.pool) - .await?; + let id = sqlx::query_scalar!( + r#"SELECT COALESCE(MAX(pre_key_id), 0) AS "id!: u32" FROM signal_pre_keys"#, + ) + .fetch_one(&store.pool) + .await?; if id > 16_777_215 { 1 } else { @@ -109,11 +148,9 @@ impl RustSignalEngine { }; let signed_pre_key_id: u32 = { - let id: u32 = sqlx::query_scalar( - "SELECT COALESCE(MAX(signed_pre_key_id), 0) FROM signal_signed_pre_keys", - ) - .fetch_one(&store.pool) - .await?; + let id = sqlx::query_scalar!( + r#"SELECT COALESCE(MAX(signed_pre_key_id), 0) AS "id!: u32" FROM signal_signed_pre_keys"#, + ).fetch_one(&store.pool).await?; if id > 16_777_215 { 1 } else { @@ -122,11 +159,9 @@ impl RustSignalEngine { }; let kyber_pre_key_id: u32 = { - let id: u32 = sqlx::query_scalar( - "SELECT COALESCE(MAX(kyber_pre_key_id), 0) FROM signal_kyber_pre_keys", - ) - .fetch_one(&store.pool) - .await?; + let id = sqlx::query_scalar!( + r#"SELECT COALESCE(MAX(kyber_pre_key_id), 0) AS "id!: u32" FROM signal_kyber_pre_keys"#, + ).fetch_one(&store.pool).await?; if id > 16_777_215 { 1 } else { @@ -223,10 +258,10 @@ impl RustSignalEngine { device_id: 1, pre_key_id: Some(pre_key_id), pre_key_public: Some(pre_key_pair.public_key.serialize().to_vec()), - signed_pre_key_id: signed_pre_key_id, + signed_pre_key_id, signed_pre_key_public: signed_pre_key_pair.public_key.serialize().to_vec(), signed_pre_key_signature: signature.to_vec(), - kyber_pre_key_id: kyber_pre_key_id, + kyber_pre_key_id, kyber_pre_key_public: kyber_key_pair.public_key.serialize().to_vec(), kyber_pre_key_signature: kyber_signature.to_vec(), identity_key: store @@ -255,11 +290,9 @@ impl RustSignalEngine { ); let mut kyber_pre_key_id: u32 = { - let id = sqlx::query_scalar( - "SELECT COALESCE(MAX(kyber_pre_key_id), 0) FROM signal_kyber_pre_keys", - ) - .fetch_one(&store.pool) - .await?; + let id = sqlx::query_scalar!( + r#"SELECT COALESCE(MAX(kyber_pre_key_id), 0) AS "id!: u32" FROM signal_kyber_pre_keys"#, + ).fetch_one(&store.pool).await?; if id > 16_777_215 { 1 @@ -269,10 +302,11 @@ impl RustSignalEngine { }; let mut pre_key_id: u32 = { - let id = - sqlx::query_scalar("SELECT COALESCE(MAX(pre_key_id), 0) FROM signal_pre_keys") - .fetch_one(&store.pool) - .await?; + let id = sqlx::query_scalar!( + r#"SELECT COALESCE(MAX(pre_key_id), 0) AS "id!: u32" FROM signal_pre_keys"#, + ) + .fetch_one(&store.pool) + .await?; if id > 16_777_215 { 1 @@ -450,7 +484,7 @@ impl RustSignalEngine { let serialized = ciphertext.serialize(); let mut res = Vec::with_capacity(serialized.len() + 1); - res.extend_from_slice(&serialized); + res.extend_from_slice(serialized); res.push(ciphertext.message_type() as u8); Ok(res) @@ -621,4 +655,77 @@ mod tests { assert_eq!(plaintext.to_vec(), decrypted); } + + #[tokio::test] + async fn test_twonly_api_100_messages() -> std::result::Result<(), Box> { + use crate::database::signal::Database; + + let _ = pretty_env_logger::try_init(); + + // 1. Setup in-memory databases + let alice_db = Database::new(&"sqlite::memory:".to_string(), None, false).await?; + alice_db.run_migrations().await?; + + let bob_db = Database::new(&"sqlite::memory:".to_string(), None, false).await?; + bob_db.run_migrations().await?; + + // 2. Setup Alice and Bob identity keys + let alice_identity_bytes = RustSignalEngine::generate_identity_key_pair()?; + let bob_identity_bytes = RustSignalEngine::generate_identity_key_pair()?; + + // 3. Initialize engines with the DB pools + let alice_engine = RustSignalEngine::new_with_pool( + alice_db.pool.clone(), + alice_identity_bytes, + 1234, + "alice".to_string(), + )?; + let bob_engine = RustSignalEngine::new_with_pool( + bob_db.pool.clone(), + bob_identity_bytes, + 5678, + "bob".to_string(), + )?; + + // 4. Bob generates a bundle + let bob_bundle = bob_engine.generate_bundle().await?; + + // 5. Alice processes Bob's bundle + alice_engine + .process_prekey_bundle("bob".to_string(), 1, bob_bundle) + .await?; + + // 6. Exchange 100 messages + let mut alice_to_bob = true; + for i in 1..=100 { + if alice_to_bob { + let plaintext = format!("Message {} from Alice to Bob", i).into_bytes(); + + let ciphertext = alice_engine + .encrypt_message("bob".to_string(), 1, plaintext.clone()) + .await?; + + let decrypted = bob_engine + .decrypt_message("alice".to_string(), 1, ciphertext) + .await?; + + assert_eq!(plaintext, decrypted); + } else { + let plaintext = format!("Message {} from Bob to Alice", i).into_bytes(); + + let ciphertext = bob_engine + .encrypt_message("alice".to_string(), 1, plaintext.clone()) + .await?; + + let decrypted = alice_engine + .decrypt_message("bob".to_string(), 1, ciphertext) + .await?; + + assert_eq!(plaintext, decrypted); + } + alice_to_bob = !alice_to_bob; + } + + Ok(()) + } } diff --git a/rust/src/signal/mod.rs b/rust/src/signal/mod.rs index 24e12826..a6385ebe 100644 --- a/rust/src/signal/mod.rs +++ b/rust/src/signal/mod.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + pub mod assert_send; pub mod engine; pub mod store; diff --git a/rust/src/signal/store.rs b/rust/src/signal/store.rs index c547b8d8..e7ecaf17 100644 --- a/rust/src/signal/store.rs +++ b/rust/src/signal/store.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use async_trait::async_trait; use libsignal_protocol::{ Direction, GenericSignedPreKey, IdentityChange, IdentityKey, IdentityKeyPair, IdentityKeyStore, @@ -46,7 +51,7 @@ pub struct DbIdentityKeyStore { #[async_trait(?Send)] impl IdentityKeyStore for DbIdentityKeyStore { async fn get_identity_key_pair(&self) -> Result { - Ok(self.identity_key_pair.clone()) + Ok(self.identity_key_pair) } async fn get_local_registration_id(&self) -> Result { @@ -65,26 +70,35 @@ impl IdentityKeyStore for DbIdentityKeyStore { .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))? .as_millis() as i64; - let existing: Option<(Vec,)> = - sqlx::query_as("SELECT identity_key FROM signal_identities WHERE name = ?") - .bind(name) - .fetch_optional(&self.pool) - .await - .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; + let existing = sqlx::query_scalar!( + r#"SELECT identity_key FROM signal_identities WHERE name = ?"#, + name, + ) + .fetch_optional(&self.pool) + .await + .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; - let changed = if let Some(row) = existing { - row.0 != identity_bytes.as_ref() + let changed = if let Some(stored) = existing { + stored != identity_bytes.as_ref() } else { false }; - sqlx::query("INSERT INTO signal_identities (name, identity_key, timestamp) VALUES (?, ?, ?) ON CONFLICT(name) DO UPDATE SET identity_key = excluded.identity_key, timestamp = excluded.timestamp") - .bind(name) - .bind(identity_bytes.as_ref()) - .bind(timestamp) - .execute(&self.pool) - .await - .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; + sqlx::query!( + r#" + INSERT INTO signal_identities(name, identity_key, timestamp) + VALUES (?, ?, ?) + ON CONFLICT(name) DO UPDATE SET + identity_key = excluded.identity_key, + timestamp = excluded.timestamp + "#, + name, + identity_bytes.as_ref(), + timestamp, + ) + .execute(&self.pool) + .await + .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; Ok(IdentityChange::from_changed(changed)) } @@ -98,14 +112,15 @@ impl IdentityKeyStore for DbIdentityKeyStore { let name = address.name(); let identity_bytes = identity.serialize(); - let row: Option<(Vec,)> = - sqlx::query_as("SELECT identity_key FROM signal_identities WHERE name = ?") - .bind(name) - .fetch_optional(&self.pool) - .await - .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; + let row = sqlx::query_scalar!( + r#"SELECT identity_key FROM signal_identities WHERE name = ?"#, + name + ) + .fetch_optional(&self.pool) + .await + .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; - if let Some((stored_key,)) = row { + if let Some(stored_key) = row { Ok(stored_key == identity_bytes.as_ref()) } else { Ok(true) @@ -118,14 +133,15 @@ impl IdentityKeyStore for DbIdentityKeyStore { ) -> Result, SignalProtocolError> { let name = address.name(); - let row: Option<(Vec,)> = - sqlx::query_as("SELECT identity_key FROM signal_identities WHERE name = ?") - .bind(name) - .fetch_optional(&self.pool) - .await - .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; + let row = sqlx::query_scalar!( + r#"SELECT identity_key FROM signal_identities WHERE name = ?"#, + name + ) + .fetch_optional(&self.pool) + .await + .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; - if let Some((bytes,)) = row { + if let Some(bytes) = row { let key = IdentityKey::decode(&bytes) .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; Ok(Some(key)) @@ -143,14 +159,15 @@ pub struct DbPreKeyStore { impl PreKeyStore for DbPreKeyStore { async fn get_pre_key(&self, prekey_id: PreKeyId) -> Result { let id: u32 = prekey_id.into(); - let row: Option<(Vec,)> = - sqlx::query_as("SELECT record_bytes FROM signal_pre_keys WHERE pre_key_id = ?") - .bind(id) - .fetch_optional(&self.pool) - .await - .map_err(|_| SignalProtocolError::InvalidPreKeyId)?; + let row = sqlx::query_scalar!( + r#"SELECT record_bytes FROM signal_pre_keys WHERE pre_key_id = ?"#, + id + ) + .fetch_optional(&self.pool) + .await + .map_err(|_| SignalProtocolError::InvalidPreKeyId)?; - if let Some((bytes,)) = row { + if let Some(bytes) = row { PreKeyRecord::deserialize(&bytes).map_err(|_| SignalProtocolError::InvalidPreKeyId) } else { Err(SignalProtocolError::InvalidPreKeyId) @@ -167,19 +184,23 @@ impl PreKeyStore for DbPreKeyStore { .serialize() .map_err(|_| SignalProtocolError::InvalidPreKeyId)?; - sqlx::query("INSERT INTO signal_pre_keys (pre_key_id, record_bytes) VALUES (?, ?) ON CONFLICT(pre_key_id) DO UPDATE SET record_bytes = excluded.record_bytes") - .bind(id) - .bind(bytes) - .execute(&self.pool) - .await - .map_err(|_| SignalProtocolError::InvalidPreKeyId)?; + sqlx::query!( + r#" + INSERT INTO signal_pre_keys(pre_key_id, record_bytes) VALUES (?, ?) + ON CONFLICT(pre_key_id) DO UPDATE SET record_bytes = excluded.record_bytes + "#, + id, + bytes, + ) + .execute(&self.pool) + .await + .map_err(|_| SignalProtocolError::InvalidPreKeyId)?; Ok(()) } async fn remove_pre_key(&mut self, prekey_id: PreKeyId) -> Result<(), SignalProtocolError> { let id: u32 = prekey_id.into(); - sqlx::query("DELETE FROM signal_pre_keys WHERE pre_key_id = ?") - .bind(id) + sqlx::query!(r#"DELETE FROM signal_pre_keys WHERE pre_key_id = ?"#, id) .execute(&self.pool) .await .map_err(|_| SignalProtocolError::InvalidPreKeyId)?; @@ -198,15 +219,15 @@ impl SignedPreKeyStore for DbSignedPreKeyStore { signed_prekey_id: SignedPreKeyId, ) -> Result { let id: u32 = signed_prekey_id.into(); - let row: Option<(Vec,)> = sqlx::query_as( - "SELECT record_bytes FROM signal_signed_pre_keys WHERE signed_pre_key_id = ?", + let row = sqlx::query_scalar!( + r#"SELECT record_bytes FROM signal_signed_pre_keys WHERE signed_pre_key_id = ?"#, + id, ) - .bind(id) .fetch_optional(&self.pool) .await .map_err(|_| SignalProtocolError::InvalidSignedPreKeyId)?; - if let Some((bytes,)) = row { + if let Some(bytes) = row { SignedPreKeyRecord::deserialize(&bytes) .map_err(|_| SignalProtocolError::InvalidSignedPreKeyId) } else { @@ -224,12 +245,19 @@ impl SignedPreKeyStore for DbSignedPreKeyStore { .serialize() .map_err(|_| SignalProtocolError::InvalidSignedPreKeyId)?; - sqlx::query("INSERT INTO signal_signed_pre_keys (signed_pre_key_id, record_bytes) VALUES (?, ?) ON CONFLICT(signed_pre_key_id) DO UPDATE SET record_bytes = excluded.record_bytes") - .bind(id) - .bind(bytes) - .execute(&self.pool) - .await - .map_err(|_| SignalProtocolError::InvalidSignedPreKeyId)?; + sqlx::query!( + r#" + INSERT INTO signal_signed_pre_keys(signed_pre_key_id, record_bytes) + VALUES (?, ?) + ON CONFLICT(signed_pre_key_id) + DO UPDATE SET record_bytes = excluded.record_bytes + "#, + id, + bytes, + ) + .execute(&self.pool) + .await + .map_err(|_| SignalProtocolError::InvalidSignedPreKeyId)?; Ok(()) } } @@ -245,15 +273,15 @@ impl KyberPreKeyStore for DbKyberPreKeyStore { kyber_prekey_id: KyberPreKeyId, ) -> Result { let id: u32 = kyber_prekey_id.into(); - let row: Option<(Vec,)> = sqlx::query_as( - "SELECT record_bytes FROM signal_kyber_pre_keys WHERE kyber_pre_key_id = ?", + let row = sqlx::query_scalar!( + r#"SELECT record_bytes FROM signal_kyber_pre_keys WHERE kyber_pre_key_id = ?"#, + id, ) - .bind(id) .fetch_optional(&self.pool) .await .map_err(|_| SignalProtocolError::InvalidKyberPreKeyId)?; - if let Some((bytes,)) = row { + if let Some(bytes) = row { KyberPreKeyRecord::deserialize(&bytes) .map_err(|_| SignalProtocolError::InvalidKyberPreKeyId) } else { @@ -271,12 +299,19 @@ impl KyberPreKeyStore for DbKyberPreKeyStore { .serialize() .map_err(|_| SignalProtocolError::InvalidKyberPreKeyId)?; - sqlx::query("INSERT INTO signal_kyber_pre_keys (kyber_pre_key_id, record_bytes) VALUES (?, ?) ON CONFLICT(kyber_pre_key_id) DO UPDATE SET record_bytes = excluded.record_bytes") - .bind(id) - .bind(bytes) - .execute(&self.pool) - .await - .map_err(|_| SignalProtocolError::InvalidKyberPreKeyId)?; + sqlx::query!( + r#" + INSERT INTO signal_kyber_pre_keys(kyber_pre_key_id, record_bytes) + VALUES (?, ?) + ON CONFLICT(kyber_pre_key_id) + DO UPDATE SET record_bytes = excluded.record_bytes + "#, + id, + bytes, + ) + .execute(&self.pool) + .await + .map_err(|_| SignalProtocolError::InvalidKyberPreKeyId)?; Ok(()) } @@ -303,16 +338,16 @@ impl SessionStore for DbSessionStore { let name = address.name(); let device_id: u32 = address.device_id().into(); - let row: Option<(Vec,)> = sqlx::query_as( - "SELECT record_bytes FROM signal_sessions WHERE name = ? AND device_id = ?", + let row = sqlx::query_scalar!( + r#"SELECT record_bytes FROM signal_sessions WHERE name = ? AND device_id = ?"#, + name, + device_id, ) - .bind(name) - .bind(device_id) .fetch_optional(&self.pool) .await .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; - if let Some((bytes,)) = row { + if let Some(bytes) = row { let record = SessionRecord::deserialize(&bytes) .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; Ok(Some(record)) @@ -332,13 +367,20 @@ impl SessionStore for DbSessionStore { .serialize() .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; - sqlx::query("INSERT INTO signal_sessions (name, device_id, record_bytes) VALUES (?, ?, ?) ON CONFLICT(name, device_id) DO UPDATE SET record_bytes = excluded.record_bytes") - .bind(name) - .bind(device_id) - .bind(bytes) - .execute(&self.pool) - .await - .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; + sqlx::query!( + r#" + INSERT INTO signal_sessions(name, device_id, record_bytes) + VALUES (?, ?, ?) + ON CONFLICT(name, device_id) + DO UPDATE SET record_bytes = excluded.record_bytes + "#, + name, + device_id, + bytes, + ) + .execute(&self.pool) + .await + .map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?; Ok(()) } } diff --git a/rust/src/user_config.rs b/rust/src/user_config.rs new file mode 100644 index 00000000..0f3cbc74 --- /dev/null +++ b/rust/src/user_config.rs @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + +use serde::{Deserialize, Serialize}; +use std::{fs::File, path::PathBuf}; + +use crate::{ + context::Context, + error::{twonly_error, Result, TwonlyError}, +}; + +fn default_required_send_images() -> i64 { + 4 +} + +fn default_can_use_login_token_for_auth() -> bool { + true +} + +/// Read-only Rust view of the persisted `user.json` file. +/// +/// This deliberately contains only fields consumed by Rust. Unknown Dart +/// fields are ignored by Serde. Loading uses `File::open`, so this module can +/// never create, truncate, or modify the configuration file. +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct UserConfig { + pub user_id: Option, + #[serde(default)] + pub device_id: i64, + #[serde(default)] + pub app_version: i64, + + #[serde(default = "default_can_use_login_token_for_auth")] + pub can_use_login_token_for_auth: bool, + #[serde(default)] + pub is_user_discovery_enabled: bool, + #[serde(default = "default_required_send_images")] + pub required_send_images: i64, + #[serde(default)] + pub user_discovery_requires_manual_approval: bool, + pub username: Option, + pub display_name: Option, + pub avatar_svg: Option, + #[serde(default)] + pub avatar_counter: i64, + #[serde(default = "default_true")] + pub ask_for_friend_promotions: bool, + #[serde(default = "default_true")] + pub typing_indicators: bool, +} + +fn default_true() -> bool { + true +} + +impl UserConfig { + pub(crate) fn load_required_from(context: &Context) -> Result { + Self::load_from(context)?.ok_or_else(|| twonly_error!("user configuration is unavailable")) + } + + pub(crate) fn load_from(context: &Context) -> Result> { + let path = PathBuf::from(context.data_dir()) + .join("keyvalue") + .join("user.json"); + if !path.exists() { + return Ok(None); + } + let file = File::open(&path)?; + serde_json::from_reader(file).map(Some).map_err(|error| { + TwonlyError::Generic(format!( + "invalid user configuration {}: {error}", + path.display() + )) + }) + } +} + +#[cfg(test)] +mod tests { + use super::UserConfig; + + #[test] + fn parses_only_rust_fields_and_applies_defaults() { + let config: UserConfig = serde_json::from_str( + r#"{ + "userId": 42, + "username": "alice", + "displayName": "Alice", + "unrelatedFlutterSetting": {"ignored": true} + }"#, + ) + .unwrap(); + + assert_eq!(config.user_id, Some(42)); + assert_eq!(config.username.as_deref(), Some("alice")); + assert_eq!(config.required_send_images, 4); + + assert!(config.can_use_login_token_for_auth); + assert!(!config.is_user_discovery_enabled); + } +} diff --git a/rust/src/user_discovery/error.rs b/rust/src/user_discovery/error.rs index 9cc234a0..fd4b6767 100644 --- a/rust/src/user_discovery/error.rs +++ b/rust/src/user_discovery/error.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use prost::DecodeError; use thiserror::Error; diff --git a/rust/src/user_discovery/mod.rs b/rust/src/user_discovery/mod.rs index c683f882..181082d7 100644 --- a/rust/src/user_discovery/mod.rs +++ b/rust/src/user_discovery/mod.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + pub mod error; pub mod stores; #[cfg(test)] diff --git a/rust/src/user_discovery/stores/in_memory_store.rs b/rust/src/user_discovery/stores/in_memory_store.rs index af1c901e..d7fbbcc0 100644 --- a/rust/src/user_discovery/stores/in_memory_store.rs +++ b/rust/src/user_discovery/stores/in_memory_store.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::user_discovery::error::{Result, UserDiscoveryError}; use crate::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore}; use crate::user_discovery::UserID; diff --git a/rust/src/user_discovery/stores/mod.rs b/rust/src/user_discovery/stores/mod.rs index f4106d9e..cf55c239 100644 --- a/rust/src/user_discovery/stores/mod.rs +++ b/rust/src/user_discovery/stores/mod.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + #[cfg(test)] mod in_memory_store; #[cfg(test)] diff --git a/rust/src/user_discovery/stores/native.rs b/rust/src/user_discovery/stores/native.rs index c3b80726..05409338 100644 --- a/rust/src/user_discovery/stores/native.rs +++ b/rust/src/user_discovery/stores/native.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::database::{app::AppDatabase, signal::Database}; use crate::keys::KeyManager; use crate::user_discovery::error::{Result, UserDiscoveryError}; @@ -50,13 +55,13 @@ impl UserDiscoveryStore for NativeUserDiscoveryStore { async fn set_shares(&self, shares: Vec>) -> Result<()> { let db = self.database().await; let mut tx = db.pool.begin().await.map_err(store_error)?; - sqlx::query!("DELETE FROM user_discovery_shares") + sqlx::query!(r#"DELETE FROM user_discovery_shares"#) .execute(&mut *tx) .await .map_err(store_error)?; for share in shares { sqlx::query!( - "INSERT INTO user_discovery_shares (share) VALUES (?)", + r#"INSERT INTO user_discovery_shares (share) VALUES (?)"#, share ) .execute(&mut *tx) @@ -461,10 +466,15 @@ mod tests { .await .unwrap(); app_db.run_migrations().await.unwrap(); - sqlx::query("INSERT INTO contacts (user_id, username) VALUES (1, 'one'), (2, 'two')") - .execute(&app_db.pool) - .await - .unwrap(); + sqlx::query!( + r#" + INSERT INTO contacts (user_id, username) + VALUES (1, 'one'), (2, 'two') + "# + ) + .execute(&app_db.pool) + .await + .unwrap(); let app_handle = Arc::new(RwLock::new(Arc::new(app_db))); let store = NativeUserDiscoveryStore::new(app_handle, temp.path().to_str().unwrap()); @@ -526,10 +536,13 @@ mod tests { let mut rng = rand::rng(); let identity = IdentityKeyPair::generate(&mut rng); let public_key = identity.identity_key().serialize().to_vec(); - sqlx::query( - "INSERT INTO signal_identities (name, identity_key, timestamp) VALUES ('1', ?, 0)", + sqlx::query!( + r#" + INSERT INTO signal_identities (name, identity_key, timestamp) + VALUES ('1', ?, 0) + "#, + &public_key, ) - .bind(&public_key) .execute(&rust_handle.read().await.pool) .await .unwrap(); diff --git a/rust/src/user_discovery/tests.rs b/rust/src/user_discovery/tests.rs index dc740c6c..43dd0437 100644 --- a/rust/src/user_discovery/tests.rs +++ b/rust/src/user_discovery/tests.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use crate::user_discovery::stores::InMemoryStore; use crate::user_discovery::traits::tests::TestingUtils; use crate::user_discovery::{UserDiscovery, UserDiscoveryStore, UserDiscoveryVersion, UserID}; diff --git a/rust/src/user_discovery/traits.rs b/rust/src/user_discovery/traits.rs index a7b65a98..5ca1425d 100644 --- a/rust/src/user_discovery/traits.rs +++ b/rust/src/user_discovery/traits.rs @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + #[cfg(test)] use std::collections::HashMap; diff --git a/rust/src/utils.rs b/rust/src/utils.rs index c4bba078..45c05d84 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -1,6 +1,19 @@ +/* + * Copyright (c) 2026, Tobias Müller git@tsmr.eu + * + */ + use std::sync::Arc; use tokio::sync::{RwLock, RwLockReadGuard}; +pub(crate) fn milliseconds_to_seconds(timestamp: i64) -> i64 { + timestamp.div_euclid(1_000) +} + +pub(crate) fn new_uuid_v4() -> String { + uuid::Uuid::new_v4().to_string() +} + #[derive(Default, Clone)] pub(crate) struct Shared(Arc>); impl Shared { @@ -14,3 +27,15 @@ impl Shared { // *self.0.write().await = value; // } } + +#[cfg(test)] +mod tests { + use super::milliseconds_to_seconds; + + #[test] + fn converts_milliseconds_to_seconds() { + assert_eq!(milliseconds_to_seconds(1_999), 1); + assert_eq!(milliseconds_to_seconds(1_000), 1); + assert_eq!(milliseconds_to_seconds(999), 0); + } +} diff --git a/rust/tests/api.rs b/rust/tests/api.rs new file mode 100644 index 00000000..47b2c3d7 --- /dev/null +++ b/rust/tests/api.rs @@ -0,0 +1,356 @@ +mod api_testing; + +pub(crate) use api_testing::tester::Tester; +use rust_lib_twonly::api::Server; +use rust_lib_twonly::bridge::api::ApiConnectionState; +use rust_lib_twonly::bridge::api::ServerResult; +use rust_lib_twonly::database::app::tables::Group; +use rust_lib_twonly::services::contacts::ContactService; +use rust_lib_twonly::services::groups::GroupService; +use rust_lib_twonly::services::messages::MessageService; + +#[tokio::test] +async fn test_connect_to_dev_server() -> anyhow::Result<()> { + let _ = tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .with_ansi(true) + .event_format(rust_lib_twonly::log::ShortEventFormatter::ansi()) + .try_init(); + + let mut tester_a = { + let mut tester = Tester::new().await?; + tester.wait_until(ApiConnectionState::Connected).await?; + tester.register_and_authenticate().await?; + tester.wait_until(ApiConnectionState::Authenticated).await?; + tester + }; + tracing::info!(tester = "a", user_id = tester_a.user_id, "Tester is ready"); + + let tester_b = { + let mut tester = Tester::new().await?; + tester.wait_until(ApiConnectionState::Connected).await?; + tester.register_and_authenticate().await?; + tester.wait_until(ApiConnectionState::Authenticated).await?; + tester + }; + tracing::info!(tester = "b", user_id = tester_b.user_id, "Tester is ready"); + + let group_id = Group::direct_chat_id(tester_a.user_id, tester_b.user_id); + + // + // Testing: Contact related messages + // + { + // Tester A requests Tester B + ContactService::new(&tester_a.context) + .request_by_username(tester_b.username.clone(), true) + .await?; + + tester_b + .wait_for_contact_state(tester_a.user_id, false, true) + .await?; + + // Tester B rejects Tester A + ContactService::new(&tester_b.context) + .reject_request(tester_a.user_id, true) + .await?; + + tester_a + .wait_for_contact_state(tester_b.user_id, false, false) + .await?; + + // Tester A requests Tester B again + ContactService::new(&tester_a.context) + .request_by_username(tester_b.username.clone(), true) + .await?; + + tester_b + .wait_for_contact_state(tester_a.user_id, false, true) + .await?; + + // Tester B accepts Tester A + ContactService::new(&tester_b.context) + .accept_request(tester_a.user_id, true) + .await?; + + tester_a + .wait_for_contact_state(tester_b.user_id, true, false) + .await?; + } + + // + // Testing: Profile updates + // + { + // Tester A changes the username + let new_username = format!( + "usr_{}", + &uuid::Uuid::new_v4().to_string().replace("-", "")[..8] + ); + + // 1. This requires an API call + let res = Server::change_username(&tester_a.context, new_username.clone()).await?; + match res { + ServerResult::Ok(_) => {} + ServerResult::ErrorCode(e) => panic!("change_username failed with error code: {}", e), + } + + // We also need to update the local UserConfig so send_profile picks it up. + tester_a.update_username(new_username.clone())?; + + // 2. Instead of directly sending the profile, we send a text message. + // The sender_profile_counter is incremented in user.json, so the text message + // will carry a higher counter, prompting Tester B to request a profile update. + MessageService::new(&tester_a.context) + .insert_and_send_text(group_id.clone(), "Hello with new profile!".into(), None) + .await?; + + // 3. Wait until the Tester B username has the new username in the contact table + tester_b + .wait_for_contact_username(tester_a.user_id, &new_username) + .await?; + } + + // + // Testing: Text message related messages + // + { + // TesterA -> TesterB: Send a text message + let message_id = MessageService::new(&tester_a.context) + .insert_and_send_text(group_id.clone(), "Initial text".into(), None) + .await?; + tester_b + .wait_for_text_message(&message_id, tester_a.user_id, "Initial text") + .await?; + + // TesterA -> TesterB: Edit this text message + MessageService::new(&tester_a.context) + .edit_text(group_id.clone(), message_id.clone(), "Edited text".into()) + .await?; + tester_b + .wait_for_text_message(&message_id, tester_a.user_id, "Edited text") + .await?; + + // TesterA -> TesterB: React to this message + MessageService::new(&tester_a.context) + .react(group_id.clone(), message_id.clone(), "👍".into(), false) + .await?; + tester_b + .wait_for_reaction(&message_id, tester_a.user_id, "👍") + .await?; + + // TesterB -> TesterA: React to this message + MessageService::new(&tester_b.context) + .react(group_id.clone(), message_id.clone(), "❤️".into(), false) + .await?; + tester_a + .wait_for_reaction(&message_id, tester_b.user_id, "❤️") + .await?; + + // TesterB -> TesterA: Delete reaction to this message + MessageService::new(&tester_b.context) + .react(group_id.clone(), message_id.clone(), "❤️".into(), true) + .await?; + tester_a + .wait_for_reaction_deleted(&message_id, tester_b.user_id, "❤️") + .await?; + + // TesterA -> TesterB: Delete this text message + MessageService::new(&tester_a.context) + .delete_message(group_id, message_id.clone()) + .await?; + tester_b.wait_for_message_deleted(&message_id).await?; + } + + // + // Testing: Testing the group + // + { + // Setup tester_c + let tester_c = { + let mut tester = Tester::new().await?; + tester.wait_until(ApiConnectionState::Connected).await?; + tester.register_and_authenticate().await?; + tester.wait_until(ApiConnectionState::Authenticated).await?; + tester + }; + tracing::info!(tester = "c", user_id = tester_c.user_id, "Tester is ready"); + + // Tester A adds Tester C as a contact (request → accept) + { + ContactService::new(&tester_a.context) + .request_by_username(tester_c.username.clone(), true) + .await?; + tester_c + .wait_for_contact_state(tester_a.user_id, false, true) + .await?; + ContactService::new(&tester_c.context) + .accept_request(tester_a.user_id, true) + .await?; + tester_a + .wait_for_contact_state(tester_c.user_id, true, false) + .await?; + } + + let group_service_a = GroupService::new(&tester_a.context); + + // 1. Create a group with tester_a, tester_b, tester_c + let group_name = "Test Group"; + group_service_a + .create_group(group_name.into(), vec![tester_b.user_id, tester_c.user_id]) + .await?; + + // Find the group_id that tester_a just created (it's the only non-direct-chat group) + let group_id = { + let database = tester_a.context.get_app_database().await; + sqlx::query_scalar!( + "SELECT group_id FROM groups WHERE is_direct_chat = 0 ORDER BY rowid DESC LIMIT 1" + ) + .fetch_one(&database.pool) + .await? + }; + tracing::info!(group_id, "Group created by tester_a"); + + // Wait for tester_b and tester_c to receive the group + tester_b + .wait_for_group_exists(&group_id, group_name) + .await?; + tester_c + .wait_for_group_exists(&group_id, group_name) + .await?; + tracing::info!("All testers see the group"); + + // 2. Send a text message in the group + let group_msg_id = MessageService::new(&tester_a.context) + .insert_and_send_text(group_id.clone(), "Hello group!".into(), None) + .await?; + + tester_b + .wait_for_text_message(&group_msg_id, tester_a.user_id, "Hello group!") + .await?; + tester_c + .wait_for_text_message(&group_msg_id, tester_a.user_id, "Hello group!") + .await?; + + tracing::info!("Group text message received by all members"); + + // 3. Update the group name + let new_group_name = "Renamed Group"; + + group_service_a + .update_group_name(group_id.clone(), new_group_name.into()) + .await?; + + tester_b + .wait_for_group_name(&group_id, new_group_name) + .await?; + tester_c + .wait_for_group_name(&group_id, new_group_name) + .await?; + + tracing::info!("Group name updated and visible to all members"); + + // 4. Promote tester_b to admin + // tester_a needs tester_b's public key to promote them. We simulate a message from tester_b + // so that tester_a can request the missing public key. + { + let db_a = tester_a.context.get_app_database().await; + sqlx::query!( + "UPDATE group_members SET last_message = CAST(strftime('%s','now') AS INTEGER) WHERE group_id = ? AND contact_id = ?", + group_id, + tester_b.user_id + ) + .execute(&db_a.pool) + .await?; + group_service_a.fetch_missing_group_public_keys().await?; + + // Wait for tester_b to respond with the group join containing the public key + // (We just wait a moment to let the messages exchange) + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + } + + group_service_a + .manage_admin(group_id.clone(), tester_b.user_id, false) + .await?; + + // Fetch state on tester_b to verify admin status + GroupService::new(&tester_b.context) + .fetch_group_state(group_id.clone()) + .await?; + { + let database = tester_b.context.get_app_database().await; + let is_admin = sqlx::query_scalar!( + "SELECT is_group_admin FROM groups WHERE group_id = ?", + group_id + ) + .fetch_one(&database.pool) + .await?; + assert_eq!(is_admin, 1, "tester_b should be admin after promotion"); + } + + tracing::info!("tester_b promoted to admin"); + + // 5. Demote tester_b from admin + group_service_a + .manage_admin(group_id.clone(), tester_b.user_id, true) + .await?; + + GroupService::new(&tester_b.context) + .fetch_group_state(group_id.clone()) + .await?; + { + let database = tester_b.context.get_app_database().await; + let is_admin = sqlx::query_scalar!( + "SELECT is_group_admin FROM groups WHERE group_id = ?", + group_id + ) + .fetch_one(&database.pool) + .await?; + assert_eq!(is_admin, 0, "tester_b should not be admin after demotion"); + } + + tracing::info!("tester_b demoted from admin"); + + // 6. Remove tester_c from the group + // Note: tester_c is not an admin, so their public key is not needed to remove them. + // We pass an empty vec![] instead of waiting for a key exchange. + group_service_a + .remove_member(group_id.clone(), vec![], tester_c.user_id) + .await?; + + // tester_b should see tester_c removed + GroupService::new(&tester_b.context) + .fetch_group_state(group_id.clone()) + .await?; + tester_b + .wait_for_group_member_removed(&group_id, tester_c.user_id) + .await?; + + // tester_c should see themselves as left + GroupService::new(&tester_c.context) + .fetch_group_state(group_id.clone()) + .await?; + + tester_c.wait_for_group_left(&group_id).await?; + tracing::info!("tester_c removed from the group"); + + // 7. tester_b leaves the group + GroupService::new(&tester_b.context) + .leave_group(group_id.clone()) + .await?; + tester_b.wait_for_group_left(&group_id).await?; + + // tester_a should see tester_b removed after fetching state + group_service_a.fetch_group_state(group_id.clone()).await?; + tester_a + .wait_for_group_member_removed(&group_id, tester_b.user_id) + .await?; + + tracing::info!("tester_b left the group"); + } + + Ok(()) +} diff --git a/rust/tests/api_testing/mod.rs b/rust/tests/api_testing/mod.rs new file mode 100644 index 00000000..3bcc02ca --- /dev/null +++ b/rust/tests/api_testing/mod.rs @@ -0,0 +1 @@ +pub(crate) mod tester; diff --git a/rust/tests/api_testing/tester.rs b/rust/tests/api_testing/tester.rs new file mode 100644 index 00000000..3587fd54 --- /dev/null +++ b/rust/tests/api_testing/tester.rs @@ -0,0 +1,470 @@ +use libsignal_protocol::{GenericSignedPreKey, IdentityKeyPair, KeyPair, SignedPreKeyRecord}; +use rand::SeedableRng; +use rust_lib_twonly::api::{ApiRuntime, Server}; +use rust_lib_twonly::bridge::api::{ApiConnectionState, ServerResult}; +use rust_lib_twonly::context::Context; +use sha2::{Digest, Sha256}; +use std::sync::Arc; +use tempfile::TempDir; +use tokio::time::{sleep, Duration}; + +pub(crate) struct Tester { + pub context: Arc, + pub username: String, + pub lang_code: String, + pub is_ios: bool, + pub user_id: i64, + // drop will auto delete the folder + _temp_dir: TempDir, +} + +impl Tester { + pub async fn wait_for_contact_state( + &self, + user_id: i64, + accepted: bool, + requested: bool, + ) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let state = sqlx::query!( + "SELECT accepted, requested FROM contacts WHERE user_id = ?", + user_id + ) + .fetch_optional(&database.pool) + .await?; + if state.is_some_and(|contact| { + contact.accepted == i64::from(accepted) && contact.requested == i64::from(requested) + }) { + return Ok(()); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "contact {user_id} did not reach accepted={accepted}, requested={requested}" + )) + } + + pub async fn wait_for_contact_username( + &self, + user_id: i64, + expected_username: &str, + ) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let username = + sqlx::query_scalar!("SELECT username FROM contacts WHERE user_id = ?", user_id) + .fetch_optional(&database.pool) + .await?; + if let Some(username) = username { + if username == expected_username { + return Ok(()); + } + } else { + tracing::info!("username is None for user_id={}", user_id); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "contact {user_id} did not reach username={expected_username}" + )) + } + + pub async fn wait_for_text_message( + &self, + message_id: &str, + sender_id: i64, + expected_text: &str, + ) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let message = sqlx::query!( + "SELECT sender_id, content, is_deleted_from_sender FROM messages WHERE message_id = ?", + message_id + ) + .fetch_optional(&database.pool) + .await?; + if message.is_some_and(|message| { + message.sender_id == Some(sender_id) + && message.content.as_deref() == Some(expected_text) + && message.is_deleted_from_sender == 0 + }) { + return Ok(()); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "message {message_id} from {sender_id} did not contain {expected_text:?}" + )) + } + + pub async fn wait_for_reaction( + &self, + message_id: &str, + sender_id: i64, + emoji: &str, + ) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let exists = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM reactions WHERE message_id = ? AND sender_id = ? AND emoji = ?)", + message_id, + sender_id, + emoji, + ) + .fetch_one(&database.pool) + .await?; + if exists != 0 { + return Ok(()); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "reaction {emoji:?} from {sender_id} did not arrive for message {message_id}" + )) + } + + pub async fn wait_for_reaction_deleted( + &self, + message_id: &str, + sender_id: i64, + emoji: &str, + ) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let exists = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM reactions WHERE message_id = ? AND sender_id = ? AND emoji = ?)", + message_id, + sender_id, + emoji, + ) + .fetch_one(&database.pool) + .await?; + if exists == 0 { + return Ok(()); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "reaction {emoji:?} from {sender_id} was not deleted for message {message_id}" + )) + } + + pub async fn wait_for_message_deleted(&self, message_id: &str) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let deleted = sqlx::query_scalar!( + "SELECT is_deleted_from_sender FROM messages WHERE message_id = ?", + message_id + ) + .fetch_optional(&database.pool) + .await?; + if deleted == Some(1) { + return Ok(()); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "message {message_id} was not deleted by its sender" + )) + } + + pub async fn wait_for_group_exists( + &self, + group_id: &str, + group_name: &str, + ) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let row = sqlx::query!("SELECT group_name FROM groups WHERE group_id = ?", group_id) + .fetch_optional(&database.pool) + .await?; + if row.is_some_and(|r| r.group_name == group_name) { + return Ok(()); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "group {group_id} with name {group_name:?} did not appear" + )) + } + + pub async fn wait_for_group_member( + &self, + group_id: &str, + contact_id: i64, + ) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let state = sqlx::query_scalar!( + "SELECT member_state FROM group_members WHERE group_id = ? AND contact_id = ?", + group_id, + contact_id, + ) + .fetch_optional(&database.pool) + .await?; + if state.is_some_and(|s| s.as_deref() != Some("leftGroup")) { + return Ok(()); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "member {contact_id} did not appear in group {group_id}" + )) + } + + pub async fn wait_for_group_member_removed( + &self, + group_id: &str, + contact_id: i64, + ) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let state = sqlx::query_scalar!( + "SELECT member_state FROM group_members WHERE group_id = ? AND contact_id = ?", + group_id, + contact_id, + ) + .fetch_optional(&database.pool) + .await?; + match state { + None => return Ok(()), + Some(s) if s.as_deref() == Some("leftGroup") => return Ok(()), + _ => {} + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "member {contact_id} was not removed from group {group_id}" + )) + } + + pub async fn wait_for_group_name( + &self, + group_id: &str, + expected_name: &str, + ) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let name = + sqlx::query_scalar!("SELECT group_name FROM groups WHERE group_id = ?", group_id) + .fetch_optional(&database.pool) + .await?; + if name.as_deref() == Some(expected_name) { + return Ok(()); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "group {group_id} name did not update to {expected_name:?}" + )) + } + + pub async fn wait_for_group_left(&self, group_id: &str) -> anyhow::Result<()> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let left = + sqlx::query_scalar!("SELECT left_group FROM groups WHERE group_id = ?", group_id) + .fetch_optional(&database.pool) + .await?; + if left == Some(1) { + return Ok(()); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!("group {group_id} was not marked as left")) + } + + pub async fn get_group_public_key( + &self, + group_id: &str, + contact_id: i64, + ) -> anyhow::Result> { + for _ in 0..100 { + let database = self.context.get_app_database().await; + let key = sqlx::query_scalar!( + "SELECT group_public_key FROM group_members WHERE group_id = ? AND contact_id = ?", + group_id, + contact_id, + ) + .fetch_optional(&database.pool) + .await?; + if let Some(Some(key)) = key { + return Ok(key); + } + sleep(Duration::from_millis(100)).await; + } + Err(anyhow::anyhow!( + "group_public_key for contact {contact_id} in group {group_id} not found" + )) + } + + pub async fn new() -> anyhow::Result { + let temp_dir = tempfile::tempdir()?; + let db_dir = temp_dir.path().join("db"); + let data_dir = temp_dir.path().join("data"); + + std::fs::create_dir_all(data_dir.join("keyvalue"))?; + + let config = rust_lib_twonly::user_config::UserConfig { + app_version: 100, + device_id: 1, + can_use_login_token_for_auth: true, + ..Default::default() + }; + std::fs::write( + data_dir.join("keyvalue").join("user.json"), + serde_json::to_string(&config).unwrap(), + )?; + + let context = Context::init_for_testing(db_dir, data_dir).await?; + + { + let mut csprng = rand::rngs::StdRng::from_os_rng(); + let identity_pair = IdentityKeyPair::generate(&mut csprng); + let registration_id: u32 = rand::Rng::random::(&mut csprng) & 0x7FFFFFFF; + let timestamp = libsignal_protocol::Timestamp::from_epoch_millis( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + ); + + let signed_pre_key_pair = KeyPair::generate(&mut csprng); + let signature = identity_pair + .private_key() + .calculate_signature_for_multipart_message( + &[&signed_pre_key_pair.public_key.serialize()], + &mut csprng, + ) + .map_err(|e| anyhow::anyhow!("Signal error: {}", e))?; + + let signed_prekey = + SignedPreKeyRecord::new(1.into(), timestamp, &signed_pre_key_pair, &signature); + let mut pre_key_store = std::collections::HashMap::new(); + pre_key_store.insert( + 1, + signed_prekey + .serialize() + .map_err(|e| anyhow::anyhow!("Signal error: {}", e))?, + ); + + context + .inject_test_signal_identity( + identity_pair.serialize().to_vec(), + registration_id as i64, + pre_key_store, + ) + .await?; + } + + let username = format!( + "usr_{}", + &uuid::Uuid::new_v4().to_string().replace("-", "")[..8] + ); + + Ok(Self { + context, + username, + lang_code: "en".to_string(), + is_ios: false, + user_id: 0, + _temp_dir: temp_dir, + }) + } + + pub fn update_username(&mut self, new_username: String) -> anyhow::Result<()> { + self.username = new_username.clone(); + + let path = self + ._temp_dir + .path() + .join("data") + .join("keyvalue") + .join("user.json"); + let content = std::fs::read_to_string(&path)?; + let mut config: rust_lib_twonly::user_config::UserConfig = serde_json::from_str(&content)?; + config.username = Some(new_username); + config.avatar_counter += 1; + + std::fs::write(&path, serde_json::to_string(&config)?)?; + Ok(()) + } + + pub async fn register_and_authenticate(&mut self) -> anyhow::Result<()> { + let ServerResult::Ok(pow) = Server::get_proof_of_work(&self.context).await? else { + return Err(anyhow::anyhow!("got no proof of work")); + }; + + let proof_of_work = Tester::calculate_pow(&pow.prefix, pow.difficulty as usize); + + let user_id = Server::register( + &self.context, + self.username.clone(), + proof_of_work, + self.lang_code.clone(), + self.is_ios, + ) + .await?; + + let ServerResult::Ok(user_id) = user_id else { + return Err(anyhow::anyhow!("got no user_id")); + }; + + self.user_id = user_id; + self.context.inject_test_user_id(user_id).await?; + + // Reload the API configuration so the new login_token/user_id is picked up by the API Client + ApiRuntime::reload_configuration(&self.context).await?; + + // After the user has register, it should reconnected and reauthenticated + ApiRuntime::close(&self.context).await?; + ApiRuntime::connect(&self.context).await?; + + self.wait_until(ApiConnectionState::Authenticated).await?; + + Server::generate_and_upload_pqc_pre_keys(&self.context).await?; + + Ok(()) + } + + pub fn is_valid(difficulty: usize, digest: &[u8]) -> bool { + let bits = digest + .iter() + .map(|b| format!("{:08b}", b)) + .collect::(); + bits.starts_with(&"0".repeat(difficulty)) + } + + pub fn calculate_pow(prefix: &str, difficulty: usize) -> i64 { + let mut i = 0; + loop { + i += 1; + let s = format!("{}{}", prefix, i); + let mut hasher = Sha256::new(); + hasher.update(s.as_bytes()); + let digest = hasher.finalize(); + if Self::is_valid(difficulty, &digest) { + return i; + } + } + } + + pub(crate) async fn wait_until(&self, required: ApiConnectionState) -> anyhow::Result<()> { + let mut state = ApiRuntime::connection_state(&self.context).await?; + + for _ in 0..100 { + state = ApiRuntime::connection_state(&self.context).await?; + if state == required { + break; + } + sleep(Duration::from_millis(10)).await; + } + + assert_eq!( + state, required, + "API should be in {required:?} and not in {state:?}" + ); + Ok(()) + } +} diff --git a/rust/tests/pqxdh_tests.rs b/rust/tests/pqxdh_tests.rs deleted file mode 100644 index 2b1e15d4..00000000 --- a/rust/tests/pqxdh_tests.rs +++ /dev/null @@ -1,73 +0,0 @@ -#[tokio::test] -async fn test_twonly_api_100_messages() -> Result<(), Box> { - use rust_lib_twonly::database::signal::Database; - use rust_lib_twonly::signal::engine::RustSignalEngine; - - let _ = pretty_env_logger::try_init(); - - // 1. Setup in-memory databases - let alice_db = Database::new(&"sqlite::memory:".to_string(), None, false).await?; - alice_db.run_migrations().await?; - - let bob_db = Database::new(&"sqlite::memory:".to_string(), None, false).await?; - bob_db.run_migrations().await?; - - // 2. Setup Alice and Bob identity keys - let alice_identity_bytes = RustSignalEngine::generate_identity_key_pair()?; - let bob_identity_bytes = RustSignalEngine::generate_identity_key_pair()?; - - // 3. Initialize engines with the DB pools - let alice_engine = RustSignalEngine::new_with_pool( - alice_db.pool.clone(), - alice_identity_bytes, - 1234, - "alice".to_string(), - )?; - let bob_engine = RustSignalEngine::new_with_pool( - bob_db.pool.clone(), - bob_identity_bytes, - 5678, - "bob".to_string(), - )?; - - // 4. Bob generates a bundle - let bob_bundle = bob_engine.generate_bundle().await?; - - // 5. Alice processes Bob's bundle - alice_engine - .process_prekey_bundle("bob".to_string(), 1, bob_bundle) - .await?; - - // 6. Exchange 100 messages - let mut alice_to_bob = true; - for i in 1..=100 { - if alice_to_bob { - let plaintext = format!("Message {} from Alice to Bob", i).into_bytes(); - - let ciphertext = alice_engine - .encrypt_message("bob".to_string(), 1, plaintext.clone()) - .await?; - - let decrypted = bob_engine - .decrypt_message("alice".to_string(), 1, ciphertext) - .await?; - - assert_eq!(plaintext, decrypted); - } else { - let plaintext = format!("Message {} from Bob to Alice", i).into_bytes(); - - let ciphertext = bob_engine - .encrypt_message("alice".to_string(), 1, plaintext.clone()) - .await?; - - let decrypted = alice_engine - .decrypt_message("bob".to_string(), 1, ciphertext) - .await?; - - assert_eq!(plaintext, decrypted); - } - alice_to_bob = !alice_to_bob; - } - - Ok(()) -} diff --git a/test/mocks/platform_channels.dart b/test/mocks/platform_channels.dart index a722b058..35f65abf 100644 --- a/test/mocks/platform_channels.dart +++ b/test/mocks/platform_channels.dart @@ -40,38 +40,43 @@ void setupPlatformChannelMocks() { return null; } - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel( - 'plugins.it_crowd.double_tapp/flutter_secure_storage', - ), - mockHandler, - ); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'), - mockHandler, - ); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('dev.fluttercommunity.plus/connectivity'), - (call) async { - if (call.method == 'check') { - return ['wifi']; - } - return null; - }, - ); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel( - 'be.tramesch.workmanager/foreground_channel_workmanager', - ), - (call) async => true, - ); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('com.bbflight.background_downloader'), - (call) async { - if (call.method == 'enqueue') { - return true; - } - return null; - }, - ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel( + 'plugins.it_crowd.double_tapp/flutter_secure_storage', + ), + mockHandler, + ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'), + mockHandler, + ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('dev.fluttercommunity.plus/connectivity'), + (call) async { + if (call.method == 'check') { + return ['wifi']; + } + return null; + }, + ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel( + 'be.tramesch.workmanager/foreground_channel_workmanager', + ), + (call) async => true, + ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('com.bbflight.background_downloader'), + (call) async { + if (call.method == 'enqueue') { + return true; + } + return null; + }, + ); } diff --git a/test/mocks/test_client.dart b/test/mocks/test_client.dart index 478814c6..af3b4f2b 100644 --- a/test/mocks/test_client.dart +++ b/test/mocks/test_client.dart @@ -71,7 +71,7 @@ class TestClient { final difficulty = powRes.$1!.difficulty.toInt(); final proof = await calculatePoW(prefix, difficulty); - final regRes = await api.register(username, '', proof); + final regRes = await api.register(username, null, proof); if (regRes.isError) { throw Exception('Registration failed: ${regRes.error}'); } diff --git a/test/services/group_service_test.dart b/test/services/group_service_test.dart deleted file mode 100644 index 7bd6712a..00000000 --- a/test/services/group_service_test.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'dart:io'; - -import 'package:drift/drift.dart' hide isNotNull, isNull; -import 'package:drift/native.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/locator.dart'; -import 'package:twonly/src/database/tables/groups.table.dart'; -import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/userdata.model.dart'; -import 'package:twonly/src/services/api/api.service.dart'; -import 'package:twonly/src/services/group.service.dart'; -import 'package:twonly/src/services/user.service.dart'; - -class RealHttpOverrides extends HttpOverrides { - @override - HttpClient createHttpClient(SecurityContext? context) { - return super.createHttpClient(context) - ..badCertificateCallback = (cert, host, port) => true; - } -} - -void main() { - if (!Platform.isMacOS) { - return; - } - - TestWidgetsFlutterBinding.ensureInitialized(); - HttpOverrides.global = RealHttpOverrides(); - - setUp(() async { - await locator.reset(); - locator - ..registerSingleton( - TwonlyDB.forTesting( - DatabaseConnection( - NativeDatabase.memory(), - closeStreamsSynchronously: true, - ), - ), - ) - ..registerSingleton(UserService()) - ..registerSingleton(ApiService()); - - userService.currentUser = UserData( - userId: 1, - username: 'test_user', - displayName: 'Test User', - subscriptionPlan: 'Free', - currentSetupPage: null, - appVersion: 100, - ); - userService.isUserCreated = true; - AppEnvironment.initTesting(); - // Log.init(); - - expect(apiService.apiHost, 'dev-api.twonly.eu'); - }); - - tearDown(() async { - await twonlyDB.close(); - }); - - group('GroupServices Tests', () { - Future createTestGroup() async { - await twonlyDB.contactsDao.insertContact( - ContactsCompanion.insert( - userId: const Value(2), - username: 'user2', - ), - ); - final members = [ - (await twonlyDB.contactsDao.getContactById(2))!, - ]; - final result = await createNewGroup('Test Group', members); - expect(result, true); - return (await twonlyDB.groupsDao.getAllGroups()).first; - } - - test('Full Group Life Cycle', () async { - late bool success; - // 1. Create Group - var group = await createTestGroup(); - - // 2. Add New Member - await twonlyDB.contactsDao.insertContact( - ContactsCompanion.insert( - userId: const Value(3), - username: 'user3', - ), - ); - success = await addNewGroupMembers(group, [3]); - expect(success, true); - - // 3. Upgrade to Admin - // Generate a real-looking key - group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; - final dummyPubKey = Curve.generateKeyPair().publicKey.serialize(); - success = await manageAdminState(group, dummyPubKey, 2, false); - expect(success, true); - - // 4. Change Time - group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; - success = await updateChatDeletionTime(group, 7200000); - expect(success, true); - - // 5. Remove Member 3 - group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; - success = await removeMemberFromGroup(group, dummyPubKey, 3); - expect(success, true); - - // 6. Remove Member 2 - group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; - success = await removeMemberFromGroup(group, dummyPubKey, 2); - expect(success, true); - - // 7. Leave Group (Myself) - final keyPair = IdentityKeyPair.fromSerialized( - group.myGroupPrivateKey!, - ); - group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; - success = await removeMemberFromGroup( - group, - keyPair.getPublicKey().serialize(), - userService.currentUser.userId, - ); - expect(success, false); - - // Final verification of DB state - final finalGroup = await twonlyDB.groupsDao.getGroup(group.groupId); - expect(finalGroup, isNotNull); - - final history = await twonlyDB.groupsDao - .watchGroupActions(group.groupId) - .first; - - expect(history.length, 7); - expect(history[0].type, GroupActionType.createdGroup); - expect(history[1].type, GroupActionType.addMember); - expect(history[2].type, GroupActionType.promoteToAdmin); - expect(history[3].type, GroupActionType.changeDisplayMaxTime); - expect(history[4].type, GroupActionType.removedMember); - expect(history[5].type, GroupActionType.removedMember); - expect(history[6].type, GroupActionType.removedMember); - }); - }); -} diff --git a/test/services/messages_purge_test.dart b/test/services/messages_purge_test.dart index 7a347f64..29024fd6 100644 --- a/test/services/messages_purge_test.dart +++ b/test/services/messages_purge_test.dart @@ -38,81 +38,90 @@ void main() { await twonlyDB.close(); }); - test('purgeMessageTable preserves unopened messages and deletes expired ones', () async { - final now = clock.now(); - const retentionMs = 7200000; // 2 hours - final deletionLimit = now.subtract(const Duration(milliseconds: retentionMs)); + test( + 'purgeMessageTable preserves unopened messages and deletes expired ones', + () async { + final now = clock.now(); + const retentionMs = 7200000; // 2 hours + final deletionLimit = now.subtract( + const Duration(milliseconds: retentionMs), + ); - // 1. Insert a group with 2 hour retention policy - await twonlyDB.groupsDao.createNewGroup( - GroupsCompanion.insert( - groupId: 'test_group', - groupName: 'Test Group', - deleteMessagesAfterMilliseconds: const Value(retentionMs), - ), - ); + // 1. Insert a group with 2 hour retention policy + await twonlyDB.groupsDao.createNewGroup( + GroupsCompanion.insert( + groupId: 'test_group', + groupName: 'Test Group', + deleteMessagesAfterMilliseconds: const Value(retentionMs), + ), + ); - // 2. Insert test messages: - // Msg A: Unopened (openedByAll is null) - await twonlyDB.messagesDao.insertMessage( - MessagesCompanion.insert( - messageId: 'msg_a_unopened', - groupId: 'test_group', - type: 'text', - createdAt: Value(deletionLimit.subtract(const Duration(minutes: 5))), // older than deletion threshold - ), - ); + // 2. Insert test messages: + // Msg A: Unopened (openedByAll is null) + await twonlyDB.messagesDao.insertMessage( + MessagesCompanion.insert( + messageId: 'msg_a_unopened', + groupId: 'test_group', + type: 'text', + createdAt: Value( + deletionLimit.subtract(const Duration(minutes: 5)), + ), // older than deletion threshold + ), + ); - // Msg B: Opened long ago (openedByAll is older than deletion threshold) - await twonlyDB.messagesDao.insertMessage( - MessagesCompanion.insert( - messageId: 'msg_b_opened_expired', - groupId: 'test_group', - type: 'text', - openedByAll: Value(deletionLimit.subtract(const Duration(minutes: 5))), - createdAt: Value(deletionLimit.subtract(const Duration(minutes: 30))), - ), - ); + // Msg B: Opened long ago (openedByAll is older than deletion threshold) + await twonlyDB.messagesDao.insertMessage( + MessagesCompanion.insert( + messageId: 'msg_b_opened_expired', + groupId: 'test_group', + type: 'text', + openedByAll: Value( + deletionLimit.subtract(const Duration(minutes: 5)), + ), + createdAt: Value(deletionLimit.subtract(const Duration(minutes: 30))), + ), + ); - // Msg C: Opened recently (openedByAll is newer than deletion threshold) - await twonlyDB.messagesDao.insertMessage( - MessagesCompanion.insert( - messageId: 'msg_c_opened_recent', - groupId: 'test_group', - type: 'text', - openedByAll: Value(deletionLimit.add(const Duration(minutes: 5))), - createdAt: Value(deletionLimit.subtract(const Duration(minutes: 10))), - ), - ); + // Msg C: Opened recently (openedByAll is newer than deletion threshold) + await twonlyDB.messagesDao.insertMessage( + MessagesCompanion.insert( + messageId: 'msg_c_opened_recent', + groupId: 'test_group', + type: 'text', + openedByAll: Value(deletionLimit.add(const Duration(minutes: 5))), + createdAt: Value(deletionLimit.subtract(const Duration(minutes: 10))), + ), + ); - // Msg D: Deleted from sender, older than threshold - await twonlyDB.messagesDao.insertMessage( - MessagesCompanion.insert( - messageId: 'msg_d_sender_deleted_expired', - groupId: 'test_group', - type: 'text', - isDeletedFromSender: const Value(true), - createdAt: Value(deletionLimit.subtract(const Duration(minutes: 5))), - ), - ); + // Msg D: Deleted from sender, older than threshold + await twonlyDB.messagesDao.insertMessage( + MessagesCompanion.insert( + messageId: 'msg_d_sender_deleted_expired', + groupId: 'test_group', + type: 'text', + isDeletedFromSender: const Value(true), + createdAt: Value(deletionLimit.subtract(const Duration(minutes: 5))), + ), + ); - // Run purge - await twonlyDB.messagesDao.purgeMessageTable(); + // Run purge + await twonlyDB.messagesDao.purgeMessageTable(); - // Verify database state - final allMessages = await twonlyDB.select(twonlyDB.messages).get(); - final remainingIds = allMessages.map((m) => m.messageId).toList(); + // Verify database state + final allMessages = await twonlyDB.select(twonlyDB.messages).get(); + final remainingIds = allMessages.map((m) => m.messageId).toList(); - // msg_a_unopened should be preserved because it was never opened (openedByAll was null) - expect(remainingIds.contains('msg_a_unopened'), isTrue); + // msg_a_unopened should be preserved because it was never opened (openedByAll was null) + expect(remainingIds.contains('msg_a_unopened'), isTrue); - // msg_b_opened_expired should be deleted because openedByAll < deletionLimit - expect(remainingIds.contains('msg_b_opened_expired'), isFalse); + // msg_b_opened_expired should be deleted because openedByAll < deletionLimit + expect(remainingIds.contains('msg_b_opened_expired'), isFalse); - // msg_c_opened_recent should be preserved because openedByAll >= deletionLimit - expect(remainingIds.contains('msg_c_opened_recent'), isTrue); + // msg_c_opened_recent should be preserved because openedByAll >= deletionLimit + expect(remainingIds.contains('msg_c_opened_recent'), isTrue); - // msg_d_sender_deleted_expired should be deleted because isDeletedFromSender is true and createdAt < deletionLimit - expect(remainingIds.contains('msg_d_sender_deleted_expired'), isFalse); - }); + // msg_d_sender_deleted_expired should be deleted because isDeletedFromSender is true and createdAt < deletionLimit + expect(remainingIds.contains('msg_d_sender_deleted_expired'), isFalse); + }, + ); }