From 5fd80e6dd6b848f7c4c9ea5155069d4d7a85a25c Mon Sep 17 00:00:00 2001 From: Filip Date: Wed, 11 Mar 2026 16:06:00 +0100 Subject: [PATCH] initial commit --- .claude/settings.local.json | 18 + .env | 19 + .gitignore | 12 + CLAUDE.md | 1041 +++ README.md | 341 + SECURITY_AUDIT.md | 363 + TODO.md | 22 + certs/README.md | 101 + certs/cloudflare.ini.example | 11 + certs/reload-server.sh | 28 + certs/setup-tls.sh | 108 + chat_core.py | 3481 +++++++++ client.py | 899 +++ crypto_utils.py | 935 +++ db.py | 1714 +++++ gemini.md | 152 + gui_client.py | 6338 +++++++++++++++++ ios_client/EncryptedChat/App/AppState.swift | 18 + .../EncryptedChat/App/EncryptedChatApp.swift | 36 + .../EncryptedChat/Core/ChatClient.swift | 1644 +++++ .../EncryptedChat/Core/KeyStorage.swift | 397 ++ .../EncryptedChat/Core/MessageCache.swift | 65 + .../EncryptedChat/Crypto/CryptoErrors.swift | 95 + .../EncryptedChat/Crypto/CryptoUtils.swift | 196 + .../EncryptedChat/Crypto/DoubleRatchet.swift | 371 + .../EncryptedChat/Crypto/Ed25519Crypto.swift | 73 + .../Crypto/FieldArithmetic.swift | 231 + .../EncryptedChat/Crypto/KeyEncryption.swift | 106 + .../EncryptedChat/Crypto/RSACrypto.swift | 309 + .../EncryptedChat/Crypto/SenderKeyState.swift | 175 + .../EncryptedChat/Crypto/X25519Crypto.swift | 77 + ios_client/EncryptedChat/Crypto/X3DH.swift | 118 + .../EncryptedChat/Models/Conversation.swift | 46 + .../EncryptedChat/Models/DeviceBundle.swift | 43 + .../EncryptedChat/Models/Invitation.swift | 9 + ios_client/EncryptedChat/Models/Message.swift | 33 + ios_client/EncryptedChat/Models/User.swift | 19 + .../Network/ConnectionManager.swift | 188 + .../Network/ProtocolHandler.swift | 90 + .../EncryptedChat/Utilities/Constants.swift | 38 + .../EncryptedChat/Utilities/Extensions.swift | 132 + .../ViewModels/AuthViewModel.swift | 114 + .../ViewModels/ChatViewModel.swift | 131 + .../ViewModels/ConversationListVM.swift | 127 + .../ViewModels/ProfileViewModel.swift | 66 + .../EncryptedChat/Views/Auth/LoginView.swift | 134 + .../Views/Auth/PairingView.swift | 49 + .../Views/Auth/RegisterView.swift | 4 + .../EncryptedChat/Views/Chat/ChatView.swift | 164 + .../Views/Chat/ImageViewerView.swift | 43 + .../Views/Chat/MessageBubbleView.swift | 123 + .../Views/Chat/MessageInputView.swift | 55 + .../Views/Chat/SearchOverlayView.swift | 46 + .../Views/Components/CircularAvatarView.swift | 46 + .../Components/ConnectionIndicator.swift | 35 + .../Views/Components/OnlineDotOverlay.swift | 15 + .../Conversations/ConversationListView.swift | 99 + .../Conversations/ConversationRowView.swift | 58 + .../Conversations/NewConversationSheet.swift | 100 + .../Views/Groups/CreateGroupSheet.swift | 4 + .../Views/Groups/GroupInfoView.swift | 123 + .../Views/Groups/InvitationBanner.swift | 41 + .../Views/Profile/EditProfileView.swift | 4 + .../Views/Profile/ProfileView.swift | 111 + ios_client/incremental_sync_changes.md | 239 + ios_client/project.yml | 33 + ios_client/v0.8.4_changes.md | 1036 +++ protocol.py | 142 + requirements.txt | 11 + scaling.md | 252 + schema.sql | 189 + server.py | 2933 ++++++++ tests/PENTEST_CLIENT.md | 79 + tests/pentest_client.py | 338 + theme.py | 539 ++ .../041d292d-c94a-4c46-b8f0-b4ac02536d50.enc | Bin 0 -> 86523 bytes .../055eca42-b4b5-4211-b819-030dc601e9b4.enc | Bin 0 -> 49684 bytes .../0cb3e08e-b294-437d-8228-abf97c996311.enc | Bin 0 -> 179149 bytes .../189af45e-fa60-4fd6-8a3f-8313921d1f48.enc | Bin 0 -> 86033 bytes .../1ce346fd-54f6-4e7a-85c4-7c5098e01d2a.enc | Bin 0 -> 4739 bytes .../1fb0cffc-1e95-4f21-aba9-90fc398d1bb2.enc | Bin 0 -> 8898 bytes .../23737dc9-334b-4818-a258-758596e75aef.enc | Bin 0 -> 59095 bytes .../37ef215b-b30e-4339-a7ab-43445df27526.enc | Bin 0 -> 85740 bytes .../41384719-ab8c-4b65-9823-91217d3bf3d3.enc | Bin 0 -> 119893 bytes .../4f661a78-fd57-469f-8af1-fd88bf8c167e.enc | Bin 0 -> 33032 bytes .../572a6f37-6a4f-4004-a95a-9e10a3080b7e.enc | Bin 0 -> 119773 bytes .../6aabd2ba-c64b-40ae-960a-a7a161c337db.enc | Bin 0 -> 43137 bytes .../6deec74d-940d-498a-8f91-38424b935a13.enc | Bin 0 -> 354833 bytes .../73680cd7-4a47-4944-980f-4225e019527c.enc | Bin 0 -> 229979 bytes .../7e32ef79-2c29-4466-8c1a-cd5cee3e430c.enc | Bin 0 -> 31454 bytes .../8cc77d3d-f28a-4b9c-80b6-adc6ec672214.enc | Bin 0 -> 8833 bytes .../8e81df99-8ae0-4348-842b-a3d0de0510f5.enc | Bin 0 -> 119823 bytes .../9321986f-0918-4e59-b30b-bc8086a20508.enc | Bin 0 -> 354833 bytes .../9a17095f-58bc-461c-a8cc-43a20ec78392.enc | Bin 0 -> 51360 bytes .../a5eb6b09-47ab-43c0-9d44-bef051c56a17.enc | Bin 0 -> 100941 bytes .../0b282232-9214-4fbe-a72d-2f07a74760e3.png | Bin 0 -> 14304 bytes .../14420bdd-f4e3-4e57-87e9-4c553361cb1c.png | Bin 0 -> 23324 bytes .../1c4b7c64-fb08-4cc6-948f-5c9e46aa5b35.jpg | Bin 0 -> 34020 bytes .../59abf7ba-576c-4052-9042-6ccd18e68ded.png | Bin 0 -> 9222 bytes .../74e3f5a5-df49-4f76-ba39-c5b5aab2e77b.jpg | Bin 0 -> 8882 bytes .../88014d90-bcbc-499b-9fd8-8147ecf850c2.png | Bin 0 -> 34171 bytes .../b819e594-fd8f-4686-99d8-ca1e25b23082.jpg | Bin 0 -> 34623 bytes .../c8565a7f-7aee-44f1-a9b7-d7bbec0c3d81.jpg | Bin 0 -> 15004 bytes .../ca301dba-e119-4e7a-9776-3a226488fcf0.jpg | Bin 0 -> 33641 bytes .../d461ed16-5d8c-4a1d-984a-af784d3f0925.jpg | Bin 0 -> 33641 bytes ...p_d299bac6-8fa1-46bc-bc13-8ace9c6ada10.png | Bin 0 -> 17808 bytes .../b544b412-50ea-4ba1-9798-9410ec209bb3.enc | Bin 0 -> 33032 bytes .../c9996203-b91a-4afc-b0fc-f8eb578495ed.enc | Bin 0 -> 73930 bytes .../ca076a30-da8c-42e3-9d8a-f560221ae691.enc | Bin 0 -> 13480 bytes .../ce6de468-c848-4e7f-a2ce-d30dd7adc901.enc | Bin 0 -> 123626 bytes .../d5ea4c6e-ee82-49d9-a6fb-c437af7c0bdf.enc | Bin 0 -> 60567 bytes .../e88e52c5-9ca6-47d9-89bd-7f22def1f745.enc | Bin 0 -> 61258 bytes .../ecc7f33d-b013-409a-ad8a-94be1a8fba1d.enc | Bin 0 -> 411864 bytes .../f095935b-d216-415e-866a-f2a44e95c506.enc | Bin 0 -> 52355 bytes .../f28e6ac4-1e84-4c9a-b9fb-3af94f6e143e.enc | Bin 0 -> 17872 bytes zaloha/.gitignore | 9 + zaloha/CLAUDE.md | 758 ++ zaloha/README.md | 314 + zaloha/chat_core.py | 2609 +++++++ zaloha/client.py | 636 ++ zaloha/crypto_utils.py | 812 +++ zaloha/db.py | 1293 ++++ zaloha/gui_client.py | 3335 +++++++++ zaloha/protocol.py | 125 + zaloha/requirements.txt | 7 + zaloha/schema.sql | 158 + zaloha/server.py | 2053 ++++++ 127 files changed, 39684 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 .env create mode 100644 CLAUDE.md create mode 100644 SECURITY_AUDIT.md create mode 100644 TODO.md create mode 100644 certs/README.md create mode 100644 certs/cloudflare.ini.example create mode 100755 certs/reload-server.sh create mode 100755 certs/setup-tls.sh create mode 100644 chat_core.py create mode 100644 client.py create mode 100644 crypto_utils.py create mode 100644 db.py create mode 100644 gemini.md create mode 100644 gui_client.py create mode 100644 ios_client/EncryptedChat/App/AppState.swift create mode 100644 ios_client/EncryptedChat/App/EncryptedChatApp.swift create mode 100644 ios_client/EncryptedChat/Core/ChatClient.swift create mode 100644 ios_client/EncryptedChat/Core/KeyStorage.swift create mode 100644 ios_client/EncryptedChat/Core/MessageCache.swift create mode 100644 ios_client/EncryptedChat/Crypto/CryptoErrors.swift create mode 100644 ios_client/EncryptedChat/Crypto/CryptoUtils.swift create mode 100644 ios_client/EncryptedChat/Crypto/DoubleRatchet.swift create mode 100644 ios_client/EncryptedChat/Crypto/Ed25519Crypto.swift create mode 100644 ios_client/EncryptedChat/Crypto/FieldArithmetic.swift create mode 100644 ios_client/EncryptedChat/Crypto/KeyEncryption.swift create mode 100644 ios_client/EncryptedChat/Crypto/RSACrypto.swift create mode 100644 ios_client/EncryptedChat/Crypto/SenderKeyState.swift create mode 100644 ios_client/EncryptedChat/Crypto/X25519Crypto.swift create mode 100644 ios_client/EncryptedChat/Crypto/X3DH.swift create mode 100644 ios_client/EncryptedChat/Models/Conversation.swift create mode 100644 ios_client/EncryptedChat/Models/DeviceBundle.swift create mode 100644 ios_client/EncryptedChat/Models/Invitation.swift create mode 100644 ios_client/EncryptedChat/Models/Message.swift create mode 100644 ios_client/EncryptedChat/Models/User.swift create mode 100644 ios_client/EncryptedChat/Network/ConnectionManager.swift create mode 100644 ios_client/EncryptedChat/Network/ProtocolHandler.swift create mode 100644 ios_client/EncryptedChat/Utilities/Constants.swift create mode 100644 ios_client/EncryptedChat/Utilities/Extensions.swift create mode 100644 ios_client/EncryptedChat/ViewModels/AuthViewModel.swift create mode 100644 ios_client/EncryptedChat/ViewModels/ChatViewModel.swift create mode 100644 ios_client/EncryptedChat/ViewModels/ConversationListVM.swift create mode 100644 ios_client/EncryptedChat/ViewModels/ProfileViewModel.swift create mode 100644 ios_client/EncryptedChat/Views/Auth/LoginView.swift create mode 100644 ios_client/EncryptedChat/Views/Auth/PairingView.swift create mode 100644 ios_client/EncryptedChat/Views/Auth/RegisterView.swift create mode 100644 ios_client/EncryptedChat/Views/Chat/ChatView.swift create mode 100644 ios_client/EncryptedChat/Views/Chat/ImageViewerView.swift create mode 100644 ios_client/EncryptedChat/Views/Chat/MessageBubbleView.swift create mode 100644 ios_client/EncryptedChat/Views/Chat/MessageInputView.swift create mode 100644 ios_client/EncryptedChat/Views/Chat/SearchOverlayView.swift create mode 100644 ios_client/EncryptedChat/Views/Components/CircularAvatarView.swift create mode 100644 ios_client/EncryptedChat/Views/Components/ConnectionIndicator.swift create mode 100644 ios_client/EncryptedChat/Views/Components/OnlineDotOverlay.swift create mode 100644 ios_client/EncryptedChat/Views/Conversations/ConversationListView.swift create mode 100644 ios_client/EncryptedChat/Views/Conversations/ConversationRowView.swift create mode 100644 ios_client/EncryptedChat/Views/Conversations/NewConversationSheet.swift create mode 100644 ios_client/EncryptedChat/Views/Groups/CreateGroupSheet.swift create mode 100644 ios_client/EncryptedChat/Views/Groups/GroupInfoView.swift create mode 100644 ios_client/EncryptedChat/Views/Groups/InvitationBanner.swift create mode 100644 ios_client/EncryptedChat/Views/Profile/EditProfileView.swift create mode 100644 ios_client/EncryptedChat/Views/Profile/ProfileView.swift create mode 100644 ios_client/incremental_sync_changes.md create mode 100644 ios_client/project.yml create mode 100644 ios_client/v0.8.4_changes.md create mode 100644 protocol.py create mode 100644 requirements.txt create mode 100644 scaling.md create mode 100644 schema.sql create mode 100644 server.py create mode 100644 tests/PENTEST_CLIENT.md create mode 100644 tests/pentest_client.py create mode 100644 theme.py create mode 100644 uploads/041d292d-c94a-4c46-b8f0-b4ac02536d50.enc create mode 100644 uploads/055eca42-b4b5-4211-b819-030dc601e9b4.enc create mode 100644 uploads/0cb3e08e-b294-437d-8228-abf97c996311.enc create mode 100644 uploads/189af45e-fa60-4fd6-8a3f-8313921d1f48.enc create mode 100644 uploads/1ce346fd-54f6-4e7a-85c4-7c5098e01d2a.enc create mode 100644 uploads/1fb0cffc-1e95-4f21-aba9-90fc398d1bb2.enc create mode 100644 uploads/23737dc9-334b-4818-a258-758596e75aef.enc create mode 100644 uploads/37ef215b-b30e-4339-a7ab-43445df27526.enc create mode 100644 uploads/41384719-ab8c-4b65-9823-91217d3bf3d3.enc create mode 100644 uploads/4f661a78-fd57-469f-8af1-fd88bf8c167e.enc create mode 100644 uploads/572a6f37-6a4f-4004-a95a-9e10a3080b7e.enc create mode 100644 uploads/6aabd2ba-c64b-40ae-960a-a7a161c337db.enc create mode 100644 uploads/6deec74d-940d-498a-8f91-38424b935a13.enc create mode 100644 uploads/73680cd7-4a47-4944-980f-4225e019527c.enc create mode 100644 uploads/7e32ef79-2c29-4466-8c1a-cd5cee3e430c.enc create mode 100644 uploads/8cc77d3d-f28a-4b9c-80b6-adc6ec672214.enc create mode 100644 uploads/8e81df99-8ae0-4348-842b-a3d0de0510f5.enc create mode 100644 uploads/9321986f-0918-4e59-b30b-bc8086a20508.enc create mode 100644 uploads/9a17095f-58bc-461c-a8cc-43a20ec78392.enc create mode 100644 uploads/a5eb6b09-47ab-43c0-9d44-bef051c56a17.enc create mode 100644 uploads/avatars/0b282232-9214-4fbe-a72d-2f07a74760e3.png create mode 100644 uploads/avatars/14420bdd-f4e3-4e57-87e9-4c553361cb1c.png create mode 100644 uploads/avatars/1c4b7c64-fb08-4cc6-948f-5c9e46aa5b35.jpg create mode 100644 uploads/avatars/59abf7ba-576c-4052-9042-6ccd18e68ded.png create mode 100644 uploads/avatars/74e3f5a5-df49-4f76-ba39-c5b5aab2e77b.jpg create mode 100644 uploads/avatars/88014d90-bcbc-499b-9fd8-8147ecf850c2.png create mode 100644 uploads/avatars/b819e594-fd8f-4686-99d8-ca1e25b23082.jpg create mode 100644 uploads/avatars/c8565a7f-7aee-44f1-a9b7-d7bbec0c3d81.jpg create mode 100644 uploads/avatars/ca301dba-e119-4e7a-9776-3a226488fcf0.jpg create mode 100644 uploads/avatars/d461ed16-5d8c-4a1d-984a-af784d3f0925.jpg create mode 100644 uploads/avatars/group_d299bac6-8fa1-46bc-bc13-8ace9c6ada10.png create mode 100644 uploads/b544b412-50ea-4ba1-9798-9410ec209bb3.enc create mode 100644 uploads/c9996203-b91a-4afc-b0fc-f8eb578495ed.enc create mode 100644 uploads/ca076a30-da8c-42e3-9d8a-f560221ae691.enc create mode 100644 uploads/ce6de468-c848-4e7f-a2ce-d30dd7adc901.enc create mode 100644 uploads/d5ea4c6e-ee82-49d9-a6fb-c437af7c0bdf.enc create mode 100644 uploads/e88e52c5-9ca6-47d9-89bd-7f22def1f745.enc create mode 100644 uploads/ecc7f33d-b013-409a-ad8a-94be1a8fba1d.enc create mode 100644 uploads/f095935b-d216-415e-866a-f2a44e95c506.enc create mode 100644 uploads/f28e6ac4-1e84-4c9a-b9fb-3af94f6e143e.enc create mode 100644 zaloha/.gitignore create mode 100644 zaloha/CLAUDE.md create mode 100644 zaloha/README.md create mode 100644 zaloha/chat_core.py create mode 100644 zaloha/client.py create mode 100644 zaloha/crypto_utils.py create mode 100644 zaloha/db.py create mode 100644 zaloha/gui_client.py create mode 100644 zaloha/protocol.py create mode 100644 zaloha/requirements.txt create mode 100644 zaloha/schema.sql create mode 100644 zaloha/server.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..b4f0316 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,18 @@ +{ + "permissions": { + "allow": [ + "Bash(python3:*)", + "Bash(ls:*)", + "Bash(pip3 show:*)", + "Bash(.venv/bin/python3:*)", + "Bash(python:*)", + "Bash(wc:*)", + "Bash(grep:*)", + "Bash(chmod:*)", + "Bash(find:*)", + "Bash(fc-list:*)", + "Bash(sudo ls:*)", + "Bash(mkdir:*)" + ] + } +} diff --git a/.env b/.env new file mode 100644 index 0000000..855c0c3 --- /dev/null +++ b/.env @@ -0,0 +1,19 @@ +MYSQL_HOST=192.168.1.112 +MYSQL_PORT=3306 +MYSQL_USER=sifrator +MYSQL_PASSWORD=Brouk100+1 +MYSQL_DATABASE=encrypted_chat + +#SERVER_HOST=192.168.88.65 +SERVER_HOST=0.0.0.0 +SERVER_PORT=9999 + +TLS_ENABLED=true +TLS_CERT_FILE=/home/filip/encrypted_chat/certs/fullchain.pem +TLS_KEY_FILE=/home/filip/encrypted_chat/certs/privkey.pem + +SMTP_HOST=smtp.protonmail.ch +SMTP_PORT=587 +SMTP_USER=cryptedchat@dw-technics.com +SMTP_PASS=DBL5GKTJA28KQRZF +SMTP_FROM=cryptedchat@dw-technics.com diff --git a/.gitignore b/.gitignore index 36b13f1..fbc2bda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +<<<<<<< HEAD # ---> Python # Byte-compiled / optimized / DLL files __pycache__/ @@ -174,3 +175,14 @@ cython_debug/ # PyPI configuration file .pypirc +======= +__pycache__/ +*.pyc +#.env +#.env.* +.encrypted_chat/ +certs/* +!certs/*.sh +!certs/*.example +!certs/README.md +>>>>>>> d506e65 (initial commit) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9fcae88 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,1041 @@ +# Encrypted Chat — Project Context + +End-to-end encrypted chat with forward secrecy (X3DH + Double Ratchet, Signal Protocol). +Server stores and relays opaque blobs — never sees plaintext. RSA retained for login only. + +## Files + +| File | Lines | Purpose | +|------|-------|---------| +| `schema.sql` | ~172 | MySQL schema (users, devices, signed_prekeys, one_time_prekeys, conversations, conversation_members, group_invitations, messages, message_recipients, message_reactions, group_sender_keys, message_reads, image_uploads, user_profiles) | +| `db.py` | ~1500 | MySQL CRUD — one connection per call, `dictionary=True` cursors, returns dicts. Includes profile CRUD, `get_user_contacts()`, `update_conversation_creator()`, `get_conversation()`. Phantom user CRUD + `upgrade_phantom_user()`. Invitation CRUD. Group avatar. Device CRUD. Per-device prekey/session management. Reactions CRUD (`add_reaction`, `remove_reaction`, `get_reactions`). Pins CRUD (`pin_message`, `unpin_message`, `get_pinned_messages`). | +| `server.py` | ~2200 | Asyncio TCP server, handler dispatch, rate limiting, real-time notifications via `connected_clients` dict. Profile + avatar handlers. Online/offline status push. Leave group, delete conversation, group invitations, group avatar handlers. Phantom user support. Graceful shutdown. 4 asyncio.Lock guards (H4 fix). Device registration + per-device key bundles + per-device notifications. SPK age reporting in `get_prekey_count`. Reactions, pins, pinned messages handlers. | +| `protocol.py` | ~114 | Newline-delimited JSON protocol, `ProtocolReader`/`ProtocolWriter`, `encode_binary`/`decode_binary` (base64). Constants: `VERSION`, `MAX_MESSAGE_BYTES`, `MAX_IMAGE_BYTES`, `MAX_FILE_BYTES`, `IMAGE_CHUNK_SIZE`. | +| `crypto_utils.py` | ~950 | Ed25519, X25519, AES-256-GCM, HKDF, PBKDF2, X3DH, `DoubleRatchet` (with state snapshot/rollback), `SenderKeyState` (with state snapshot/rollback). RSA for login only. ECP1 password-based key encryption format (600k PBKDF2 iterations). Contact key verification: `compute_fingerprint()`, `format_fingerprint()`, `compute_safety_number()`, `encode_verification_qr()`, `decode_verification_qr()`. Message padding: `pad_plaintext()`, `unpad_plaintext()`. | +| `chat_core.py` | ~2850 | `ChatClient` class — session management, X3DH/ratchet encryption, local key storage, reconnect, profiles, file sharing, leave group, delete conversation, invitations, group avatar. Multi-device: per-device sessions, device_id persistence, device bundle cache. SPK rotation (7-day) with grace period. Reactions, pins, forwarding methods. Contact key verification: TOFU registry, explicit verification, safety numbers, QR code verify. Used by CLI + GUI | +| `client.py` | ~520 | Interactive CLI client — reactions, pin, forward, pinned messages, verify contact, show fingerprint commands | +| `gui_client.py` | ~3600 | PyQt6 GUI — `AsyncBridge` QThread bridges asyncio <-> Qt signals, `MainWindow`, `UserProfileDialog`, `VerificationDialog`, connection indicator + auto-reconnect, online status, file sharing, leave group, unread badges, circular avatars in conv list, online green dot overlay, group invitations UI, delete conversation, group avatar support, message reactions (emoji badges), forwarding with dialog, pin/unpin with indicator, pinned messages list, @mentions autocomplete, contact verification indicators (conv list green checkmark, E2E label status, key change warning dialog) | +| `ios_client/` | ~6200 | Native iOS client (Swift/SwiftUI) — wire-compatible with Python server. 47 Swift files: CryptoKit crypto (AES-GCM, HKDF, Ed25519, X25519), pure Swift GF(2^255-19) field arithmetic for Ed→X conversion, Security.framework RSA-4096, Network.framework TCP+TLS, Signal Protocol (X3DH + Double Ratchet + Sender Keys), SwiftUI views. Uses `project.yml` (XcodeGen). | + +## Architecture & Data Flow + +### Encryption: X3DH + Double Ratchet (Signal Protocol) + +**Keys per user:** +- **RSA-4096** — Login challenge-response only (server stores public key). Password-encrypted with ECP1 format (PBKDF2 600k iterations + AES-256-GCM). +- **Identity Key (IK)** — Ed25519 (signing) + converted to X25519 (for DH in X3DH). Password-encrypted with ECP1 format. +- **Signed Pre-Key (SPK)** — X25519, signed by IK, uploaded to server. **Rotates every 7 days** (M4). Previous SPK kept for grace period (in-flight X3DH). +- **One-Time Pre-Keys (OPK)** — X25519, consumed on X3DH initiation, auto-replenished when count < 20 + +**DM flow:** +1. Alice fetches Bob's per-device key bundles (IK, SPK per device, OPK per device) -> X3DH per device -> shared secret per device +2. Double Ratchet initialized from shared secret — one session per (user, device) pair +3. Each message: symmetric ratchet (HMAC chain) -> message key -> AES-256-GCM +4. Each reply direction change: DH ratchet (new X25519 keypair) -> new root + chain keys +5. Per-device ciphertext — each recipient device gets individually encrypted blob +6. Self-encrypted copy uses SELF_DEVICE_ID sentinel, readable by all own devices + +**Group flow (Sender Keys):** +1. Each sender has own SenderKeyState per group +2. Sender key distributed to members via pairwise Double Ratchet (as control DM with `_sender_key` field) +3. Group messages: symmetric ratchet on sender key -> AES-256-GCM +4. Same ciphertext replicated to all recipients (efficient) + +### Contact Key Verification (Out-of-Band Trust) + +**Problem:** Server stores identity keys but could MITM new sessions by substituting keys. Users need a way to verify keys out-of-band. + +**Design:** Entirely client-side — zero server changes. Server already stores identity keys in `users.identity_key` (32B Ed25519) and returns them via `get_user_info`/`get_key_bundle`. Verification state is local-only (privacy by design — server never learns who verified whom). + +**Trust model:** +``` +First contact → TOFU (Trust On First Use) → "trusted" (key recorded) + ↓ + Out-of-band verify → "verified" (explicit confirmation) + ↓ + Key change detected → "changed" / "changed_verified" (WARNING) + ↓ + Accept new key → "trusted" (verification reset) +``` + +**Verification methods:** +1. **Safety numbers** — 60-digit number (12 groups × 5 digits), deterministic for each pair (lower user_id's fingerprint first). Both users see the same number — compare in person or over trusted channel. +2. **QR codes** — Encode `0x01 + uid_len + uid + identity_key` (70 bytes). One user shows QR, other scans → automatic verification. +3. **Fingerprints** — Per-user 30-digit number (6 groups × 5 digits). Visual comparison. + +**Algorithm (Signal NumericFingerprint compatible):** +- Fingerprint: SHA-512 iterated 5200× on `version(2B) + identity_key(32B) + user_id(UTF-8)`, truncated to 32 bytes +- Display: `int(bytes[i*5:(i+1)*5], big-endian) % 100000`, zero-padded to 5 digits + +**Local storage** (encrypted with `_local_key`, AES-256-GCM): +- `known_identity_keys.bin` — TOFU registry: `{user_id → {identity_key hex, first_seen, last_seen}}` +- `verified_contacts.bin` — Explicit verification: `{user_id → {identity_key hex, verified_at, method}}` + +**Tamper resistance:** Oba soubory jsou šifrovány AES-256-GCM (klíč odvozen z identity key přes HKDF). Na rozdíl od session/sender key souborů (které mají plaintext migration fallback kvůli zpětné kompatibilitě) verifikační soubory **nemají žádný plaintext fallback** — pokud dešifrování selže, vrátí se prázdný dict. Útočník s přístupem k disku (ale bez znalosti hesla/identity key) tedy nemůže: +- Podvrhnout falešný "verified" status (injekce do `verified_contacts.bin`) +- Potlačit TOFU key-change warning (injekce do `known_identity_keys.bin`) +- Nejhorší případ při manipulaci = verifikace se resetuje (prázdný stav), nikdy se nepřijme podvržená hodnota + +**Threat model:** +- **DNS únos / podvržený server (existující kontakt):** TOFU detekuje key change → warning dialog. Útočník nemůže tiše podvrhnout klíč. +- **DNS únos / podvržený server (první kontakt):** TOFU věří prvnímu klíči — MITM úspěšný. Obrana: out-of-band verifikace safety number přes jiný kanál (telefon, osobně). Pokud se čísla neshodnou → odhaleno. +- **Kompromitovaný reálný server:** Server podmění klíč v `get_key_bundle` → TOFU detekuje změnu (stejné jako DNS únos). Server neví kdo je verified (stav je lokální) → nemůže cíleně obejít. +- **Disk access bez hesla:** Verifikační soubory šifrovány AES-256-GCM, žádný plaintext fallback → nelze podvrhnout. Viz tamper resistance výše. +- **TLS je první linie obrany:** S platným certifikátem DNS únos nestačí. Bez TLS / s `TLS_INSECURE` je MITM triviální. Verifikace kontaktů je druhá linie — chrání i při kompromitovaném serveru. + +**Data flow:** +1. `_get_user_info()` calls `check_identity_key()` → records TOFU or detects change +2. Key change → `_key_change_cb` fires → GUI shows warning dialog +3. User opens VerificationDialog → sees safety number + QR → marks verified +4. `_rebuild_conv_list()` queries `get_verification_status()` → shows green checkmark for verified DMs +5. E2E label in chat header shows "Verified" (green) or "Encrypted" (muted) + +**QR code encoding detail:** +- Raw binary payload (`0x01 + uid_len + uid + identity_key`) je před vložením do QR zakódován jako **base64** (ASCII-safe) +- Důvod: QR čtečky (pyzbar/zbar) re-kódují binární data přes UTF-8 → byty > 127 se zkomolí +- Při skenování se base64 dekóduje zpět na raw bytes → `decode_verification_qr()` +- iOS implementace musí použít stejný base64 wrapper (viz iOS spec níže) + +**Self-verification exclusion:** +- Vlastní user_id se nikdy nezobrazuje jako "unverified" — TOFU registr neobsahuje vlastní klíč (ten je na disku) +- GUI: security section v UserProfileDialog se nezobrazuje pro vlastní profil, verified badge v group info přeskakuje vlastní uid + +### Protocol + +Newline-delimited JSON over TCP (optional TLS). Fields: `type`, `status`, `data`, `request_id`. +Binary data encoded as base64 via `encode_binary()`/`decode_binary()`. + +**Request/response pattern:** Client sends `{"type": "...", "request_id": "uuid", ...}`, server responds with same `request_id`. Notifications (push) have no `request_id`. + +### Server notifications (push to connected clients) +- `new_message` — per-recipient ciphertext included +- `messages_read` — conversation_id + user_id + message_ids +- `message_deleted` — message_id + conversation_id +- `conversation_created` — conversation_id, name, created_by, members[] (pushed to added members) +- `member_added` — conversation_id, user_id, username, email (pushed to all members except requester) +- `member_removed` — conversation_id, user_id (pushed to removed member + remaining members) +- `group_invitation` — conversation_id, conversation_name, invited_by, invited_by_username (pushed to invited user) +- `conversation_renamed` — conversation_id, name, renamed_by (pushed to all members except renamer) +- `session_reset` — from_user_id, from_device_id (pushed to peer when session reset requested) +- `message_reacted` — message_id, conversation_id, user_id, username, reaction, action (pushed to members) +- `message_pinned` — message_id, conversation_id, user_id, username (pushed to members) +- `message_unpinned` — message_id, conversation_id, user_id, username (pushed to members) +- `user_online` — user_id (pushed to contacts when user connects) +- `user_offline` — user_id (pushed to contacts when user's last connection drops) +- `online_users` — user_ids[] (sent to user on login — list of currently online contacts) + +## DB Schema (schema.sql) + +``` +users: id, username, email (UNIQUE), rsa_public_key (TEXT), identity_key (BLOB 32B Ed25519), created_at +devices: id, user_id FK, device_name (nullable), created_at, last_seen_at +signed_prekeys: id, user_id FK, device_id (nullable), public_key (BLOB 32B), signature (BLOB 64B), created_at +one_time_prekeys: id, user_id FK, device_id (nullable), public_key (BLOB 32B) +conversations: id, created_at, name (nullable), created_by (nullable), avatar_file (nullable) +conversation_members: conversation_id + user_id (composite PK), joined_at +group_invitations: id, conversation_id FK, user_id FK, invited_by FK, created_at, UNIQUE(conversation_id, user_id) +messages: id, conversation_id FK, sender_id FK, sender_device_id (nullable), ratchet_header (BLOB JSON), + x3dh_header (BLOB JSON nullable), sender_chain_id (BLOB nullable), sender_chain_n (INT nullable), + created_at, deleted_at, image_file_id, pinned_at (nullable), pinned_by (nullable) +message_recipients: message_id + user_id + device_id (composite PK), encrypted_content (BLOB), nonce (BLOB), + ratchet_header (BLOB nullable), x3dh_header (BLOB nullable) +message_reactions: id, message_id FK, user_id FK, reaction VARCHAR(32), created_at, UNIQUE(message_id, user_id, reaction) +group_sender_keys: conversation_id + sender_id + device_id (composite PK), chain_id (BLOB 32B), created_at +message_reads: message_id + user_id (composite PK), read_at +image_uploads: file_id (PK), conversation_id FK, uploader_id FK, file_size, completed, created_at +user_profiles: user_id (PK FK), phone, phone_visible, email_visible, location, location_visible, avatar_file, updated_at +``` + +Constant: `SELF_DEVICE_ID = "00000000-0000-0000-0000-000000000000"` — sentinel for self-encrypted copies and legacy rows. + +Index: `messages(conversation_id, created_at)` for query performance. + +## Server Protocol — All Message Types + +### Pre-login (no session required) +| Type | Handler | Purpose | +|------|---------|---------| +| `register` | `handle_register_start` | Start registration (username, email, public_key, identity_key) | +| `register_confirm` | `handle_register_confirm` | Confirm with 6-digit code | +| `login_start` | `handle_login_start` | Get RSA challenge | +| `login_finish` | `handle_login_finish` | Respond with RSA signature -> session. Client sends `client_version`, server returns `server_version` in response. Also sends `online_users` and `user_online` notifications. | +| `get_user_info` | `handle_get_user_info` | Get user info + identity_key (by email or user_id) | +| `pairing_start` | `handle_pairing_start` | New device starts pairing (gets 8-digit code) | +| `pairing_poll` | `handle_pairing_poll` | New device polls for key payload | + +### Post-login (session required) +| Type | Handler | Purpose | +|------|---------|---------| +| `upload_prekeys` | `handle_upload_prekeys` | Upload SPK + batch of OPKs (server verifies SPK signature) | +| `get_key_bundle` | `handle_get_key_bundle` | Fetch key bundle for X3DH (consumes one OPK) | +| `get_prekey_count` | `handle_get_prekey_count` | Check remaining OPK count + SPK age (`spk_created_at`) for rotation | +| `ensure_prekeys` | `handle_ensure_prekeys` | Combined get_prekey_count + upload_prekeys in single round-trip. Returns count + spk_created_at + upload status. | +| `create_conversation` | `handle_create_conversation` | Create conversation — DMs auto-add both; groups add creator only + create invitations for others | +| `find_conversation` | `handle_find_conversation` | Find existing DM by email | +| `add_member` | `handle_add_member` | Create invitation for user to join group (was: direct add) | +| `remove_member` | `handle_remove_member` | Remove member (creator only) | +| `leave_group` | `handle_leave_group` | Voluntarily leave a group (transfers creator if needed, blocks DM leave) | +| `rename_conversation` | `handle_rename_conversation` | Rename group conversation (creator only, max 100 chars), pushes `conversation_renamed` to members | +| `delete_conversation` | `handle_delete_conversation` | Delete conversation — DMs: remove self; groups: creator-only, removes all members + files | +| `accept_invitation` | `handle_accept_invitation` | Accept pending group invitation → add to members, notify others | +| `decline_invitation` | `handle_decline_invitation` | Decline pending group invitation | +| `list_invitations` | `handle_list_invitations` | List user's pending invitations (with conv name + inviter username) | +| `list_conversations` | `handle_list_conversations` | List all user's conversations (includes avatar_file) | +| `send_message` | `handle_send_message` | Send encrypted message (ratchet_header + recipients[]) | +| `get_messages` | `handle_get_messages` | Get messages (returns per-user ciphertext, JOINs message_recipients). Supports `after_ts` for incremental sync. ROW_NUMBER dedup when both device-specific and SELF_DEVICE_ID rows exist. | +| `mark_read` | `handle_mark_read` | Mark messages as read | +| `delete_message` | `handle_delete_message` | Soft-delete message (sender only) | +| `rotate_keys` | `handle_rotate_keys` | Rotate RSA login key, disconnect other sessions | +| `pairing_claim` | `handle_pairing_claim` | Authorized device claims pairing code | +| `pairing_send` | `handle_pairing_send` | Authorized device sends encrypted key payload | +| `upload_image_start/chunk/end` | Image/file upload | Chunked encrypted upload (32KB chunks). `file_type` param: `"image"` (5MB limit) or `"file"` (50MB limit). | +| `download_image` | Image/file download | Chunked download with offset | +| `get_profile` | `handle_get_profile` | Get user profile (respects visibility for other users) | +| `update_profile` | `handle_update_profile` | Update own profile (phone, location, visibility toggles) | +| `update_avatar` | `handle_update_avatar` | Upload user avatar (base64, max 2MB, JPEG/PNG) | +| `get_avatar` | `handle_get_avatar` | Download user's avatar | +| `update_group_avatar` | `handle_update_group_avatar` | Upload group avatar (base64, max 2MB, JPEG/PNG, creator only) | +| `get_group_avatar` | `handle_get_group_avatar` | Download group avatar | +| `get_deleted_since` | `handle_get_deleted_since` | Get message IDs deleted since a given timestamp (for incremental sync) | +| `reencrypt_messages` | `handle_reencrypt_messages` | Batch upsert message history with self-key (max 500/request, for device pairing + received msg self-encryption) | +| `list_devices` | `handle_list_devices` | List all devices for current user | +| `remove_device` | `handle_remove_device` | Remove a device (not current device) | +| `session_reset` | `handle_session_reset` | Notify peer to reset corrupted Double Ratchet session (push `session_reset` to peer) | +| `react_message` | `handle_react_message` | Add/remove emoji reaction on a message. Push `message_reacted` to members. | +| `pin_message` | `handle_pin_message` | Pin/unpin a message. Push `message_pinned`/`message_unpinned` to members. | +| `get_pinned_messages` | `handle_get_pinned_messages` | Get list of pinned messages for a conversation. | + +## Key Classes & Functions + +### crypto_utils.py + +**Password-based key encryption (ECP1 format):** +- `PBKDF2_ITERATIONS = 600_000` — OWASP 2023 compliant +- `_encrypt_private_key(raw_bytes, password) -> bytes` — PBKDF2-HMAC-SHA256 + AES-256-GCM. Format: `_ECP1_MAGIC(4) + salt(16) + nonce(12) + ciphertext_with_tag` +- `_decrypt_private_key(data, password) -> bytes` — Detects ECP1 magic prefix, derives key, decrypts + +**RSA (login only):** `generate_rsa_keypair()`, `serialize_private_key()` (ECP1 with password, PEM without), `serialize_public_key()`, `load_private_key()` (auto-detects ECP1 vs legacy PEM), `load_public_key()`, `rsa_sign()`, `rsa_verify()` + +**AES-256-GCM:** `aes_encrypt(plaintext, key=None) -> (key, nonce, ct, tag)`, `aes_decrypt(key, nonce, ct, tag) -> plaintext` + +**Ed25519:** `generate_identity_keypair()`, `serialize_ed25519_private()` (ECP1 with password, 32-byte raw without), `serialize_ed25519_private_raw()`, `serialize_ed25519_public()`, `load_ed25519_private()` (auto-detects ECP1 vs legacy PEM vs raw), `load_ed25519_public()`, `ed25519_sign()`, `ed25519_verify()` + +**X25519:** `generate_x25519_keypair()`, `serialize_x25519_private()`, `serialize_x25519_public()`, `load_x25519_private()`, `load_x25519_public()`, `x25519_dh()` + +**Key conversion:** `ed25519_private_to_x25519()` (SHA-512 + clamp), `ed25519_public_to_x25519()` (Montgomery u-coordinate) + +**HKDF:** `hkdf_derive()`, `kdf_rk(root_key, dh_output) -> (new_root_key, chain_key)`, `kdf_ck(chain_key) -> (new_chain_key, message_key)` + +**X3DH:** `generate_signed_prekey(identity_private) -> {private, public, signature, id}`, `generate_one_time_prekeys(count=50) -> [{private, public, id}]`, `x3dh_initiate(ik_private_ed, ik_public_remote_ed, spk_remote, spk_signature, opk_remote?) -> (shared_secret, ek_priv, ek_pub)`, `x3dh_respond(ik_private_ed, spk_private, ik_remote_ed, ek_remote, opk_private?) -> shared_secret` + +**DoubleRatchet class:** +- `init_alice(shared_secret, bob_spk_pub)` — initiator, performs first DH ratchet +- `init_bob(shared_secret, spk_pair)` — responder, waits for first message +- `encrypt(plaintext) -> {header: {dh_pub, n, pn}, ciphertext, nonce}` — AAD = serialized header +- `decrypt(header_dict, ciphertext, nonce)` — handles DH ratchet step if new dh_pub, skipped messages. **State snapshot/rollback on failure (M9):** `_snapshot()` captures all mutable state before modifications, `_restore()` rolls back on any exception. +- `_snapshot() -> dict` / `_restore(snap)` — Snapshot: dh_pair, dh_remote, root_key, send/recv chain keys, counters, skipped dict. Used internally by `decrypt()`. +- `export_state() -> bytes` / `import_state(data) -> DoubleRatchet` — JSON serialization + +**SenderKeyState class:** +- `__init__(sender_key=None)` — generates random 32B key if None +- `encrypt(plaintext) -> {chain_id, n, ciphertext, nonce}` — AAD = chain_id + message number +- `decrypt(chain_id_hex, n, ciphertext, nonce)` — fast-forwards chain if needed. **State snapshot/rollback on failure (M9):** snapshots chain_key, n, _known_keys before fast-forward, restores on exception. +- `export_key() -> bytes` — for distribution to group members +- `from_key(exported_key) -> SenderKeyState` — receiver initializes from exported key +- `export_state() / import_state()` — full state persistence + +**Contact Key Verification:** +- `FINGERPRINT_VERSION = 0` — version byte for fingerprint algorithm +- `compute_fingerprint(user_id, identity_key_bytes, iterations=5200) -> bytes` — iterated SHA-512, truncated to 32 bytes. Matches Signal's NumericFingerprint. +- `format_fingerprint(fp_bytes) -> str` — 32 bytes → 6 groups of 5 digits (30 digits), 2 lines +- `compute_safety_number(my_uid, my_ik, their_uid, their_ik) -> str` — 60 digits (12 groups of 5), deterministic ordering (lower uid first), 3 lines of 4 groups +- `encode_verification_qr(user_id, identity_key_bytes) -> bytes` — `0x01 + uid_len(1B) + uid(UTF-8) + ik(32B)` +- `decode_verification_qr(data) -> (user_id, identity_key_bytes)` — inverse of encode + +**Message Padding:** +- `_PAD_MAGIC = b"\x01"` — prefix byte distinguishing padded from legacy unpadded messages +- `_PAD_BUCKETS = [64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536]` — target sizes +- `pad_plaintext(plaintext) -> bytes` — Pad to nearest bucket. Format: `0x01 + plaintext + random_padding + pad_length(4B big-endian)`. +- `unpad_plaintext(data) -> bytes` — Remove padding. Legacy unpadded messages (starting with `{`) returned unchanged. + +### chat_core.py + +**Local key storage** (`~/.encrypted_chat/{email}/`): +``` +private.pem / public.pem — RSA (login, ECP1 format when password-encrypted) +identity_private.bin / identity_public.bin — Ed25519 (ECP1 format when password-encrypted, 32B raw otherwise) +device_id.txt — This device's UUID +spk_private.bin / spk_id.txt — Current signed prekey (AES-256-GCM via local_key) +prev_spk_private.bin / prev_spk_id.txt — Previous SPK for grace period (AES-256-GCM via local_key) +opk_private/{opk_id}.bin — One-time prekeys (AES-256-GCM via local_key) +login_lockout.json — Brute-force lockout state (failed_attempts, locked_until) +sessions/{user_id}_{device_id}.bin — Double Ratchet states (per peer device) +sender_keys/{conv_id}.bin — Own sender key states +sender_keys_recv/{conv_id}_{sender_id}_{device_id}.bin — Received sender keys (per sender device) +known_identity_keys.bin — TOFU registry: {user_id -> {identity_key hex, first_seen, last_seen}} +verified_contacts.bin — Explicit verification: {user_id -> {identity_key hex, verified_at, method}} +``` + +Storage functions: `save_keys()`, `load_keys()`, `_save_identity_keys()`, `_load_identity_keys()`, `_save_spk(local_key=)`, `_load_spk(local_key=)`, `_save_prev_spk(local_key=)`, `_load_prev_spk(local_key=)`, `_save_opk_private(local_key=)`, `_load_opk_private(local_key=)`, `_delete_opk_private()`, `_save_session()`, `_load_session()`, `_save_sender_key_state()`, `_load_sender_key_state()`, `_save_recv_sender_key()`, `_load_recv_sender_key()`, `_save_known_identity_keys()`, `_load_known_identity_keys()`, `_save_verified_contacts()`, `_load_verified_contacts()` + +Lockout functions: `_check_lockout(email) -> float`, `_record_failed_attempt(email)`, `_clear_lockout(email)`. Constants: `_LOCKOUT_BASE_SECONDS=2`, `_LOCKOUT_MAX_SECONDS=300`. + +**ChatClient attributes:** +- `private_key` / `public_key` — RSA (login) +- `identity_private` / `identity_public` — Ed25519 +- `spk_private` / `spk_id` — Current SPK +- `_prev_spk_private` / `_prev_spk_id` — Previous SPK for grace period (M4) +- `opk_privates: dict[str, X25519PrivateKey]` — OPK private keys by ID +- `device_id: str | None` — this device's UUID (persisted to disk) +- `sessions: dict[str, DoubleRatchet]` — "user_id:device_id" -> ratchet (per peer device) +- `sender_key_states: dict[str, SenderKeyState]` — conv_id -> own sender key +- `recv_sender_keys: dict[str, SenderKeyState]` — "conv_id:sender_id:device_id" -> their key +- `_device_bundle_cache: dict[str, tuple[float, list]]` — user_id -> (timestamp, device_bundles) with 5-min TTL +- `_pending_self_encrypt: list[dict]` — queue of received messages to self-encrypt for multi-device access +- `_user_cache: dict[str, dict]` — user_id -> {identity_key, username, email, identity_key_status} +- `connected: bool` — current connection state +- `_known_identity_keys: dict` — TOFU registry (user_id -> {identity_key hex, first_seen, last_seen}) +- `_verified_contacts: dict` — explicit verification (user_id -> {identity_key hex, verified_at, method}) +- `_key_change_cb: Callable | None` — callback fired on identity key change (GUI wires to warning dialog) + +**Key methods:** +- `register()` — Generates RSA + Ed25519, sends to server +- `confirm_registration()` — Confirms code, uploads prekeys (SPK + 50 OPKs) +- `login()` — Loads keys from disk (including prev_spk for grace period), RSA challenge-response, auto `_ensure_prekeys()` +- `_ensure_prekeys()` — Checks OPK count AND SPK age. Replenishes OPKs if < 20, **rotates SPK if >= 7 days old** (M4). Saves old SPK as grace period before generating new one. +- `_get_device_bundles(peer_user_id)` — Fetches per-device key bundles with 5-min TTL cache +- `_get_or_create_session(peer_user_id, peer_device_id, bundle)` — Loads from memory/disk or creates via X3DH, keyed by "user:device" +- `_process_x3dh_header(sender_id, x3dh_header, sender_device_id, spk_override?)` — Bob side of X3DH. `spk_override` param allows using previous SPK for grace period (M4). +- `send_message(conv_id, text, members, reply_to?)` — Routes to `_send_dm` or `_send_group_message` +- `_send_dm()` — Per-device Double Ratchet (encrypts for each of recipient's devices), self-encrypted copy with SELF_DEVICE_ID +- `_send_group_message()` — Sender Keys, distributes key if new (per-device) +- `_distribute_sender_key()` — Sends sender key as control message via per-device pairwise ratchet, includes sender_device_id +- `_decrypt_dm()` — Handles X3DH header for new sessions, returns None for control messages. On X3DH decrypt failure, retries with previous SPK (M4 grace period). +- `_decrypt_group()` — Uses received sender key chain +- `decrypt_notification()` — Returns None for control messages (sender key distribution) +- `get_messages()` — Cache-first with incremental sync (`after_ts`). Decrypts new messages, self-encrypts received messages for multi-device, syncs deletions via `get_deleted_since`. Returns merged cache + new messages. +- `_build_messages_from_cache()` — Builds sorted message list from cache dict +- `_queue_self_encrypt()` / `_flush_self_encrypt()` — Queue and upload self-encrypted copies of received messages +- `authorize_device()` — Exports RSA + Ed25519 only (simplified for multi-device — no session/sender key transfer) +- `pairing_wait()` — Imports RSA + identity key from paired device (new device generates own SPK + OPKs on login) +- `reconnect()` — Closes connection, re-establishes TCP + RSA login using in-memory keys +- `get_profile(user_id?)` — Gets user profile from server +- `update_profile(**fields)` — Updates own profile (phone, location, visibility) +- `update_avatar(image_data)` — Uploads avatar +- `get_avatar(user_id)` — Downloads avatar bytes +- `send_file(conv_id, file_path, members, reply_to?)` — Encrypt + chunked upload + send message with `file` payload +- `download_file(file_id, file_info)` — Chunked download + AES-GCM decrypt +- `leave_group(conv_id)` — Leave group, clean up local sender keys +- `rename_conversation(conv_id, name)` — Rename group (creator only) +- `delete_conversation(conv_id)` — Delete conversation, clean up local sender keys +- `accept_invitation(conv_id)` — Accept group invitation +- `decline_invitation(conv_id)` — Decline group invitation +- `list_invitations()` — Fetch pending invitations +- `update_group_avatar(conv_id, image_data)` — Upload group avatar +- `get_group_avatar(conv_id)` — Download group avatar +- `search_messages(conv_id, query)` — Search decrypted message cache (client-side only) +- `reset_session(peer_user_id, peer_device_id?)` — Delete local session + notify peer via server +- `handle_session_reset_notification(from_user_id, from_device_id?)` — Handle incoming session reset +- `_load_verification_stores()` — Load TOFU + verified contacts from disk (called on login/registration/pairing) +- `check_identity_key(user_id, ik_bytes) -> str` — TOFU check, returns "new"/"trusted"/"verified"/"changed"/"changed_verified" +- `verify_contact(user_id, ik_bytes, method)` — Mark contact as explicitly verified +- `unverify_contact(user_id)` — Remove explicit verification +- `accept_key_change(user_id, new_ik_bytes)` — Accept changed key, remove old verification +- `get_verification_status(user_id) -> str` — Returns "verified"/"trusted"/"unverified" +- `get_safety_number(peer_user_id) -> str` — Formatted 60-digit safety number +- `get_my_fingerprint() -> str` — Formatted 30-digit own fingerprint +- `get_peer_fingerprint(peer_user_id) -> str` — Formatted 30-digit peer fingerprint +- `get_verification_qr_data() -> bytes` — QR code payload for own identity +- `verify_qr_code(qr_data) -> (ok, user_id, message)` — Decode + verify scanned QR code + +### gui_client.py + +**AsyncBridge (QThread):** Runs asyncio event loop, `schedule(coro)` queues coroutines, pyqtSignals emit results back to Qt main thread. + +**Key signals:** `login_result`, `conversations_loaded`, `messages_loaded`, `message_sent`, `new_notification`, `messages_read_notification`, `message_deleted_notification`, `conversation_updated`, `connection_state_changed`, `profile_loaded`, `profile_updated`, `avatar_loaded`, `online_status_changed`, `online_users_loaded`, `file_sent`, `file_downloaded`, `group_left`, `conversation_deleted`, `invitations_loaded`, `invitation_result`, `invitation_received`, `group_avatar_loaded`, `group_avatar_updated`, `session_reset_notification`, `key_change_warning` + +**MainWindow:** Dark theme (Catppuccin Mocha), conversation list with circular avatars + online green dot overlay + unread count badges + verification checkmark, message bubbles with colored left border, context menu (reply, delete, view image, download file), image thumbnails via QTextDocument resources (`thumb://{file_id}`), file cards with download links (`file://{file_id}`), connection indicator dot (green/red/orange), profile button, attach menu (Image/File), Leave Group button in group info, delete conversation button (trash icon in header), group avatar display + change in group info dialog, invitation list (amber border) above conversation list with right-click accept/decline. E2E label clickable → opens VerificationDialog. Key change warning dialog on identity key change. + +**UserProfileDialog:** View (read-only) and edit (own profile) modes. Fields: avatar (circular crop), username, email, phone, location, visibility toggles. Avatar upload/download. Security section (viewing others): verification status + fingerprint. Opened from "My Profile" button or user info button in group info dialog. + +**VerificationDialog:** Frameless dialog for contact verification. Shows: peer name, verification status, safety number (monospace), QR code image, both fingerprints. Buttons: "Mark as Verified" / "Remove Verification" / "Scan QR Code" (via pyzbar). QR generation via `qrcode` library. + +**Avatar system in conversation list:** +- `_avatar_cache: dict[str, QPixmap]` — user avatars by user_id +- `_group_avatar_cache: dict[str, QPixmap]` — group avatars by conv_id +- `_avatar_requested: set[str]` / `_group_avatar_requested: set[str]` — dedup download requests +- `_make_circular_avatar(pixmap, size=32)` — QPainter circular crop +- `_make_default_avatar(username, size=32)` — colored circle with initial letter (deterministic color from username hash) +- `_add_online_dot(avatar)` — green dot overlay bottom-right +- `_get_conv_avatar(conv)` — returns QIcon (DM: user avatar + online dot; group: group avatar or default) +- Periodic refresh every 2 minutes via `_refresh_timer` / `_on_periodic_refresh()` + +## Important Implementation Details + +### X3DH Header Caching +When `_get_or_create_session()` creates a new session via X3DH, it attaches the X3DH header as `ratchet._x3dh_header`. The next `_send_dm()` reads and deletes it. This ensures the X3DH header is only sent with the first message. + +### Self-Encryption for DMs +Sender uses `derive_self_encryption_key(identity_private)` to encrypt their own copy of sent messages with a static AES key. Uses `SELF_DEVICE_ID` sentinel so all own devices can read it. This allows reading own sent messages when fetching history from any device. **Received messages** are also self-encrypted after decryption (via `_pending_self_encrypt` queue + `_flush_self_encrypt()`), creating SELF_DEVICE_ID copies so other devices of the same user can read them. `batch_reencrypt_messages()` uses INSERT ON DUPLICATE KEY UPDATE (upsert) to handle both cases. + +### Sender Key Distribution as Control Messages +Sender keys are distributed via normal `send_message` protocol (per-device pairwise ratchet). The payload contains `_sender_key: {conv_id, key, sender_device_id}` field. On decryption, `_decrypt_dm()` detects this field, stores the sender key keyed by `"conv_id:sender_id:sender_device_id"`, and returns `None` (not shown to user). + +### Group Messages: Dummy Ratchet Header +Group messages use `{"dh_pub": "00"*32, "n": 0, "pn": 0}` as ratchet_header because the server requires it, but groups use sender keys instead of Double Ratchet. + +### Multi-Device Architecture +Each device has independent Double Ratchet sessions. Sessions are keyed by `"peer_user_id:peer_device_id"`. When sending a DM, the client fetches per-device key bundles via `_get_device_bundles()` and encrypts separately for each device. The server registers devices at login (`handle_login_finish`), assigns device IDs, and routes notifications with `device_entries` arrays (one entry per recipient device). Device IDs are persisted to `~/.encrypted_chat/{email}/device_id.txt`. Old session files (`{user_id}.bin`) are automatically migrated to `{user_id}_{device_id}.bin` on first load. + +### Server Session Model +`connected_clients: dict[str, list[ProtocolWriter]]` — one user can have multiple connections (multi-device). `writer_device_map: dict[int, str]` maps `id(writer)` to `device_id`. Notifications are pushed to all connections except the sender's current one. + +### Device Registration +On `login_finish`, server checks for `device_id` in the request. If present and valid (belongs to user), reuses it. Otherwise creates a new device. Device ID returned in response and stored on client disk. `list_devices` and `remove_device` handlers for device management. + +### Simplified Pairing (Multi-Device) +`authorize_device()` only exports RSA + identity key (no sessions/sender keys). New device generates its own SPK + OPKs on first login, creates independent sessions via X3DH. Old messages readable via self-encryption (shared identity key). `reencrypt_history()` still runs to ensure all messages have self-encrypted copies. + +### Real-time Conversation Notifications +`handle_create_conversation`, `handle_add_member`, `handle_remove_member`, `handle_leave_group`, `handle_delete_conversation`, `handle_accept_invitation` push notifications to affected members via `connected_clients`. Types: `conversation_created`, `member_added`, `member_removed`, `group_invitation`. GUI handles these via `conversation_updated` signal -> refreshes conversation list. + +### Connection State + Auto-Reconnect +`ChatClient.connected` flag tracks TCP connection state. `_background_listener` sets `connected = False` when server closes connection and **fails all pending futures** with `ConnectionError` (prevents `send_and_recv` from hanging forever). `send_and_recv` has a 30s timeout via `asyncio.wait_for` and catches `ConnectionError`/`TimeoutError`. `reconnect()` re-establishes TCP + RSA challenge-response using in-memory keys (no password needed, includes `device_id`). GUI `_notification_loop` detects listener death -> triggers `_auto_reconnect` with exponential backoff (1s->2s->4s->...->30s). Connection indicator dot: green (connected), red (disconnected), orange (reconnecting). + +### Server Per-Message Error Handling +Server dispatch loop wraps each handler call in individual try/except. Handler crashes return "Internal server error" response instead of killing the entire connection. Errors logged with `exc_info=True` for full tracebacks. GUI `_do_send_message`/`_do_find_or_create_and_send` catch exceptions and emit error signal (prevents silent hang when send fails). + +### Online/Offline Status +- `db.get_user_contacts(user_id)` returns all user IDs sharing at least one conversation +- On login (`handle_login_finish`): server sends `online_users` list to new user + `user_online` to all contacts (only if user was fully offline before) +- On disconnect (`handle_client` finally block): if last connection drops, server sends `user_offline` to all contacts +- `_background_listener` routes `user_online`, `user_offline`, `online_users` to notification queue +- GUI: `_online_users: set[str]` tracks online users, green dot overlay on circular avatar in DM conversation list + green circle emoji in group info member list + +### Leave Group +- `handle_leave_group` in server.py: validates membership, blocks DM leave (len<=2 and no name), transfers creator to first remaining member if creator leaves, removes member, notifies remaining via `member_removed` +- `ChatClient.leave_group()`: sends request, cleans up local sender key states on success +- GUI: red "Leave Group" button in group info dialog, confirmation dialog, resets view on success + +### Delete Conversation +- **DMs:** Any member can delete. Only removes the deleting user from `conversation_members`. If both users delete, 0 members remain → conversation + files cleaned up. +- **Groups:** Only the creator (admin) can delete. Removes ALL members, cleans up `.enc` files from disk, deletes conversation via CASCADE. +- Server notifies remaining members via `member_removed` push. +- GUI: trash icon button in conversation header. Visible for DMs always, for groups only when user is creator. +- `chat_core.py`: cleans up local sender key states after successful delete. + +### Group Invitations +- **Flow:** `create_conversation` (group) or `add_member` → creates invitation → pushes `group_invitation` notification → invitee sees in invitation list → Accept (adds to members, notifies) / Decline (deletes invitation) +- **DMs are unaffected:** `create_conversation` for DMs still auto-adds both members +- **DB:** `group_invitations` table with UNIQUE(conversation_id, user_id) to prevent duplicates +- **Server:** `handle_accept_invitation` verifies invitation exists, adds member, deletes invitation, notifies existing members via `member_added`. `handle_decline_invitation` just deletes. +- **GUI:** `inv_list` QListWidget (max 120px, amber border) above `conv_list`. Right-click → Accept/Decline. `invitation_received` signal triggers refresh + notification banner. +- **Routing fix (IMPORTANT):** `group_invitation` must be in the notification types list in `chat_core.py:_background_listener` (~line 304). Without it, invitations get routed to `_response_queue` and the GUI never sees them. + +### Group Avatars +- Stored as files in `UPLOAD_DIR/avatars/group_{conv_id}.{ext}` (PNG or JPEG detected from magic bytes) +- `conversations.avatar_file` column stores the filename +- `list_conversations` response includes `avatar_file` so GUI knows which groups have avatars +- GUI: `_group_avatar_cache` dict, `_get_conv_avatar()` returns group avatar icon or default letter circle +- Group Info dialog shows 64px circular avatar + "Change Avatar" button (creator only) +- Periodic refresh every 2 minutes re-downloads all known group avatars + +### File Sharing +- Reuses image upload/download infrastructure (`upload_image_start/chunk/end`, `download_image`) +- `upload_image_start` accepts optional `file_type` param: `"image"` (MAX_IMAGE_BYTES=5MB) or `"file"` (MAX_FILE_BYTES=50MB) +- `ChatClient.send_file()`: reads raw file, AES-256-GCM encrypts, chunked upload, sends message with `file` field in payload (`{file_id, aes_key, iv, filename, size, mime_type}`) +- `ChatClient.download_file()`: identical to `download_image()` — chunked download + AES-GCM decrypt +- GUI: attach button is dropdown menu (Image / File), file messages render as styled cards with paperclip icon (transparent background, border) and clickable download link (`file://{file_id}`), context menu "Download file" option +- Files stored as `.enc` in UPLOAD_DIR, same as images + +### Unread Count Badges +- `_unread_counts: dict[str, int]` replaces old `_unread_convs: set` +- `_on_notification()` increments count per conversation +- `_on_conv_selected()` clears count for selected conversation +- Display: `(3) Username` with bold font, instead of old `● Username` + +### User Profiles +`user_profiles` table separated from `users` (clean separation, users = auth only). Default profile created on registration (`db.create_default_profile`). Visibility rules applied server-side in `db.get_user_profile(viewer_id)`. Avatars stored as files in `UPLOAD_DIR/avatars/{user_id}.{ext}` (not in DB). Format detection from magic bytes (PNG header vs default JPEG). UserProfileDialog shows circular cropped avatar (QPainter). + +### Prekey Replenishment + SPK Rotation +After login, `_ensure_prekeys()` is called as a background task. Checks two things: +1. **OPK count** — if < 20, generates and uploads a new batch of 50 +2. **SPK age** — server returns `spk_created_at` in `get_prekey_count` response. If SPK is >= 7 days old (`SPK_ROTATION_DAYS`), triggers rotation: saves current SPK as `prev_spk_private.bin`/`prev_spk_id.txt` (grace period), generates new SPK, uploads to server. + +### Password-Based Key Encryption (ECP1 Format) — M3 +Private keys (RSA, Ed25519) are encrypted with a custom envelope instead of `BestAvailableEncryption`: +- **Key derivation:** PBKDF2-HMAC-SHA256 with 600,000 iterations (OWASP 2023 compliant) +- **Encryption:** AES-256-GCM with the derived key, magic bytes as AAD +- **Format:** `_ECP1_MAGIC("ECP1", 4B) + salt(16B) + nonce(12B) + ciphertext_with_tag(N+16B)` +- **Backward compatibility:** `load_private_key()` and `load_ed25519_private()` detect ECP1 magic prefix. If absent, fall back to legacy PEM parsing (old `BestAvailableEncryption` format). On next save, files are re-encrypted in ECP1 format. +- **Functions:** `_encrypt_private_key()`, `_decrypt_private_key()` in `crypto_utils.py` +- **Applied to:** `serialize_private_key()` (RSA), `serialize_ed25519_private()` (Ed25519) + +### SPK Rotation (7-Day Cycle) — M4 +Signed Pre-Keys rotate periodically to limit exposure from a compromised SPK: +- **Rotation interval:** `SPK_ROTATION_DAYS = 7` (constant in `chat_core.py`) +- **Trigger:** `_ensure_prekeys()` checks `spk_created_at` from `get_prekey_count` response. If age >= 7 days, calls `_generate_and_upload_prekeys()`. +- **Grace period:** Before generating a new SPK, the current one is saved as `prev_spk_private.bin` / `prev_spk_id.txt`. Loaded on login into `_prev_spk_private` / `_prev_spk_id`. +- **Fallback on decrypt:** When `_decrypt_dm()` processes an X3DH header and decryption fails with the current SPK, it retries with the previous SPK via `_process_x3dh_header(..., spk_override=self._prev_spk_private)`. This handles in-flight X3DH initiated before rotation. +- **Server side:** `get_signed_prekey()` in `db.py` returns `created_at` column. `handle_get_prekey_count` includes `spk_created_at` (ISO format) in response. +- **Server SPK replacement:** `store_signed_prekey()` deletes old SPK and inserts new one — only one active SPK per device on server. + +### Ratchet State Rollback on Decrypt Failure — M9 +Both `DoubleRatchet.decrypt()` and `SenderKeyState.decrypt()` modify internal state (chain keys, counters, DH keys) before attempting AES-GCM decryption. If decryption fails (corrupted data, wrong key, AAD mismatch), the state would be permanently corrupted. + +**DoubleRatchet fix:** +- `_snapshot()` captures all mutable fields: `dh_pair`, `dh_remote`, `root_key`, `send_chain_key`, `recv_chain_key`, `send_n`, `recv_n`, `prev_send_n`, `skipped` dict (shallow copy) +- `decrypt()` takes snapshot before any state modification, wraps the entire DH ratchet + chain advance + AES-GCM decrypt in try/except, calls `_restore()` on failure +- Special case: skipped message decryption (no state modification needed) — if AES-GCM fails, the popped key is restored to `skipped` dict + +**SenderKeyState fix:** +- Before fast-forwarding the chain, snapshots `chain_key`, `n`, `_known_keys` (shallow copy) +- On any exception during fast-forward or AES-GCM decrypt, all three are restored + +### Message Reactions +- `ALLOWED_REACTIONS = {"thumbsup", "heart", "laugh", "surprised", "sad", "thumbsdown"}` in `db.py` +- `message_reactions` table with UNIQUE(message_id, user_id, reaction) — one reaction type per user per message +- `handle_react_message`: validates UUID, reaction, membership. Adds/removes reaction. Pushes `message_reacted` to all members. +- `get_messages` response includes `reactions: [{user_id, reaction, created_at}]` per message (batch query via `db.get_reactions()`) +- GUI: React submenu in context menu with toggle (add if not present, remove if already reacted). Badges below message text showing emoji + count, highlighted border if own reaction. Real-time update via `_on_reaction_notification`. +- Forwarding uses `forwarded_from` metadata in plaintext payload — no new server protocol needed, just client convention. + +### Pinned Messages +- `messages.pinned_at` (DATETIME) and `messages.pinned_by` (CHAR(36)) columns +- `handle_pin_message`: validates UUID, membership, pins/unpins. Pushes `message_pinned`/`message_unpinned`. +- `handle_get_pinned_messages`: returns pinned message metadata for a conversation +- `get_messages` response includes `pinned_at` and `pinned_by` per message +- GUI: Pin/Unpin in context menu, pin emoji in message header, "Pinned" button in chat header opens dialog with scrollable list (double-click scrolls to message in chat) + +### @Mentions +- Client-side only — no server-side handling or special notifications +- GUI `MentionCompleter`: popup `QListWidget` shown when `@` detected at cursor, filters conversation members by prefix, inserts `@username ` on selection +- Rendering: `re.sub(r'@(\w+)', ...)` highlights mentions in blue bold (`#89b4fa`) +- Triggered from `_on_input_changed()` -> `_check_mention_trigger()` + +### Contact Key Verification (Safety Numbers / Fingerprints / QR Codes) +- **Zero server changes** — entirely client-side. Server already stores identity keys in `users.identity_key`. +- **TOFU (Trust On First Use):** `known_identity_keys.bin` stores first-seen identity key per user. Encrypted with `_local_key` (AES-256-GCM). Loaded on login/registration/pairing. +- **Explicit verification:** `verified_contacts.bin` stores verified contacts with method + timestamp. Encrypted with `_local_key`. +- **Fingerprint algorithm:** Iterated SHA-512 (5200 iterations), seed = `version(2B) + identity_key(32B) + user_id(UTF-8)`. Each iteration: `SHA-512(prev + identity_key)`. Output: first 32 bytes → 6 groups of 5 zero-padded digits. +- **Safety number:** Both users' fingerprints concatenated (lower user_id first → deterministic ordering). 64 bytes → 12 groups of 5 digits, displayed as 3 lines of 4 groups. Both sides see the same number. +- **QR code format:** `0x01 + uid_len(1B) + uid(UTF-8) + identity_key(32B)`. Generated via `qrcode` library, decoded via `pyzbar` (optional). +- **Identity key status:** `check_identity_key()` returns `"new"` | `"trusted"` | `"verified"` | `"changed"` | `"changed_verified"`. Called from `_get_user_info()`, result stored in `_user_cache` as `identity_key_status`. +- **Key change callback:** `_key_change_cb(user_id, username, old_key_hex, was_verified)` fires on key change. GUI wires to `key_change_warning` signal → warning dialog. +- **GUI indicators:** Green checkmark badge in conversation list for verified DMs (`ROLE_VERIFIED` data role, painted by `ConversationDelegate`). E2E label in chat header shows "Verified" (green) or "Encrypted" (muted) — clickable → opens `VerificationDialog`. Green checkmark next to verified members in group info. Security section in `UserProfileDialog` showing fingerprint + status. +- **VerificationDialog:** Frameless dialog showing safety number (monospace), QR code, both fingerprints, verification status. Buttons: "Mark as Verified" / "Remove Verification" / "Scan QR Code". +- **CLI:** Option 20 (Verify contact) — show safety number + mark verified. Option 21 (Show my fingerprint). +- **Tamper resistance:** `_load_known_identity_keys()` a `_load_verified_contacts()` nemají plaintext migration fallback (na rozdíl od sessions/sender keys). Soubory nikdy neexistovaly v nešifrované podobě — feature přidána po implementaci lokálního šifrování. Pokud útočník s přístupem k disku nahradí šifrovaný soubor plaintextovým JSONem, `_decrypt_local()` selže a load vrátí `{}` (prázdný stav). Útočník nemůže podvrhnout falešný verified status ani potlačit key-change warning. +- **iOS implementation spec (pro implementaci v ios_client/):** + +| Položka | Specifikace | +|---------|-------------| +| **Fingerprint algoritmus** | SHA-512 iterated 5200×. Seed = `version(2B big-endian, hodnota 0) + identity_key(32B) + user_id(UTF-8)`. Každá iterace: `SHA-512(prev_result + identity_key)`. Výstup: prvních 32 bytes. | +| **Fingerprint display** | 6 skupin × 5 číslic: `UInt64(bytes[i*5..": {"identity_key": "", "first_seen": "ISO8601", "last_seen": "ISO8601"}}}` | +| **Verified storage** | Keychain nebo šifrovaný soubor. JSON schema: `{"version": 1, "contacts": {"": {"identity_key": "", "verified_at": "ISO8601", "method": "safety_number\|qr_code\|manual"}}}` | +| **Šifrování storage** | AES-256-GCM klíčem z HKDF(identity_key, salt="local_storage_key", info="encrypted_chat_local"). **Žádný plaintext fallback** — pokud decrypt selže, vrátit prázdný dict. | +| **Identity key status** | `checkIdentityKey(userId, ikBytes) → "new" \| "trusted" \| "verified" \| "changed" \| "changed_verified"`. Volat při každém `getUserInfo()`. | +| **Self exclusion** | Vlastní user_id nikdy nezobrazovat jako unverified — přeskočit v conv listu i group info. | +| **UI: Conversation list** | Zelená fajfka (SF Symbol `checkmark.seal.fill`) vedle jména pro verified DM kontakty. | +| **UI: Chat header** | "Verified" (zelená) nebo "Encrypted" (šedá) pod jménem. Tap → otevře VerificationView. | +| **UI: VerificationView** | Safety number (monospace), QR kód (CIQRCodeGenerator), oba fingerprints, status. Tlačítka: "Mark as Verified" / "Remove Verification" / "Scan QR Code". | +| **UI: Key change alert** | `.alert()` s textem "Identity key for [name] has changed!" + "Accept" / "View Details". | +| **UI: Group info** | Zelená fajfka vedle verified členů (ne u sebe). | +| **Cross-platform test** | Python klient a iOS klient musí pro stejný pár (user_id_A, ik_A, user_id_B, ik_B) vypočítat identický safety number. QR vygenerovaný na jedné platformě musí být čitelný na druhé. | + +### Rate Limits +- Per-IP+email window (60s): register 3/min, login 10/min, send_message 20/min +- Per-connection: 20 req/s +- Per-IP: max 10 connections, global max 200 +- Pairing: TTL 120s, max 90 poll attempts, pairing_start 10/min, pairing_poll 120/min, client polls every 2s + +### GUI Font Handling (IMPORTANT) +All widget stylesheet `font-size` declarations use `pt` (not `px`). Using `px` in Qt stylesheets sets `pixelSize` and leaves `pointSize=-1`, which causes `QFont::setPointSize: Point size <= 0` warnings on Windows. Conversion: `pt ~= px * 0.75` at 96 DPI. HTML styles inside QTextBrowser (`_render_single_message_html`) still use `px` — that's fine, QTextBrowser uses its own HTML renderer. Bold fonts for list items use `_bold_font()` helper + `item.setData(FontRole)` to avoid the same issue. + +### Phantom Users (Anti User-Enumeration) +- When a user creates a conversation with an unregistered email, the server creates a "phantom" user with `rsa_public_key = 'PHANTOM'` marker +- Phantom users have real crypto keys (Ed25519 IK, X25519 SPK + 5 OPKs) so X3DH works on the client side +- `handle_find_conversation` and `handle_create_conversation` create phantoms instead of returning "User not found" +- `handle_send_message` skips phantom recipients when storing `message_recipients` — only sender's self-encrypted copy is saved +- `phantom_user_ids: set[str]` in-memory cache loaded at startup from DB, updated on create/delete +- On registration (`handle_register_confirm`): if email belongs to a phantom, the phantom is **upgraded in-place** via `db.upgrade_phantom_user()` — preserves user_id and all FK references (invitations, conversation_members). Phantom's server-generated prekeys are deleted (real user uploads own). +- `handle_create_conversation` (groups) and `handle_add_member` create invitations for phantom users too. Push notifications only sent to non-phantom users. When phantom registers and logs in, they see pending invitations. +- Messages sent to phantom users are NOT stored and NOT recoverable after registration — this is by design (prevents user enumeration, sender sees own messages via self-encryption) +- DB functions: `db.create_phantom_user(email)`, `db.is_phantom_user(user_id)`, `db.delete_phantom_user(user_id)`, `db.upgrade_phantom_user(phantom_id, username, rsa_public_key_pem, identity_key)`, `db.get_all_phantom_user_ids()` + +### Logout/Login Fix +- `_is_logout` flag in MainWindow prevents `closeEvent()` from calling `bridge.stop()` which killed the asyncio loop +- On logout: set `_is_logout = True`, call `bridge.logout()`, then `close()` +- `closeEvent()` only calls `bridge.stop()` if `not self._is_logout` +- This allows `main()` to re-create the login/main windows after logout + +### Server Graceful Shutdown +- SIGINT handler force-closes all writers in `connected_clients` before the asyncio server context manager exits +- Without this, `async with server:` waited forever for `handle_client` loops to finish + +### Version Negotiation +- `VERSION = "0.8.4"` constant in `protocol.py` (shared between client and server) +- `MIN_CLIENT_VERSION = "0.8.3"` — server rejects clients older than this +- Client sends `client_version` in `login_finish` request (both `login()` and `reconnect()`) +- Server logs `client_version`, returns `server_version` in `login_finish` response +- Server startup log includes version: `"Encrypted chat server v0.8.4 listening on ..."` +- iOS client (0.8.3) stays compatible — new notification types (`message_reacted`, `message_pinned`, `message_unpinned`) and payload fields (`reactions`, `pinned_at`, `forwarded_from`) are ignored by older clients + +### Privacy Overlay (Lock Screen) +Anti-forensic privacy feature for PyQt GUI client: +- **Immediate overlay:** On `QEvent.Type.ActivationChange` (window loses focus), dark overlay (`rgba(30,30,46,245)`) with lock icon covers entire window. Protects against Alt+Tab thumbnails, screen sharing, shoulder surfing. +- **Timed lock:** After `_LOCK_TIMEOUT_MS` (30s) unfocused, overlay transitions to locked state — requires password to dismiss. +- **Password verification:** `_on_unlock_attempt()` reads `identity_private.bin` from disk and calls `_decrypt_private_key(data, password)` (ECP1 format: PBKDF2-600k + AES-256-GCM). Successful decryption = correct password. +- **Lock capability detection:** `_lock_capable` flag checks if identity key file starts with `b"ECP1"`. If key is not password-encrypted (legacy/no-password), lock timer never fires (overlay still works as visual privacy screen). +- **Toggle:** Ctrl+Shift+P enables/disables the feature (default: enabled). +- **Notification handling during lock:** `_show_tray_notification()` checks `self._privacy_locked` — tray toasts continue while locked. `_on_notification()` increments unread counts and skips `mark_read` while locked. +- **Components:** `_privacy_overlay` (QWidget), `_lock_input` (QLineEdit password), `_lock_error` (QLabel), `_lock_timer` (QTimer single-shot), `_lock_hint` (QLabel status text). + +### Secure Deletion (Anti-Forensic Wipe) +`_secure_delete(path)` helper in both `chat_core.py` and `server.py`: +- Opens file with `r+b`, overwrites entire content with `os.urandom(size)`, calls `f.flush()` + `os.fsync(f.fileno())`, then `p.unlink()`. +- Fallback: if overwrite fails (permissions, etc.), falls back to standard `p.unlink(missing_ok=True)`. +- **Applied to (chat_core.py):** `_delete_opk_private()`, `_delete_session_file()`, session migration cleanup, sender key migration cleanup, message cache migration (plaintext JSON → encrypted). +- **Applied to (server.py):** Conversation delete (all `.enc`+`.tmp`), message delete (`.enc`), oversized upload chunk cleanup (`.tmp`), incomplete/invalid upload end (`.tmp`), stale upload periodic cleanup (`.enc`+`.tmp`). + +### Metadata Privacy +Four measures to minimize metadata leakage: +- **Message Padding:** `pad_plaintext()`/`unpad_plaintext()` in `crypto_utils.py`. Plaintext padded to nearest bucket size (64B..64KB) before encryption. Format: `0x01 + plaintext + random + length(4B)`. Legacy unpadded messages (prefix `{`) auto-detected by `unpad_plaintext()`. Applied on all 6 send paths + 2 decrypt paths in `chat_core.py`. +- **Log Sanitization:** `_who(session)` returns `u=XXXXXXXX d=YYYYYYYY` (truncated user_id + device_id). Group names, recipient counts, emails, and usernames removed from all server log lines (register, login, DM create, invite, rename, send_message). +- **Metadata Retention:** `db.cleanup_old_reads(days)` and `db.cleanup_old_reactions(days)` delete old interaction data in batches. Default `METADATA_RETENTION_DAYS=90`. Runs every ~1 hour (every 30th cycle of `_periodic_cleanup`). `cleanup_old_reads` joins on `messages.created_at` to only delete reads for old messages. `get_unread_counts(max_age_days)` excludes messages older than retention window — prevents phantom unreads after read cleanup. Indexes: `idx_reads_read_at` on `message_reads.read_at`, `idx_reactions_created_at` on `message_reactions.created_at`. +- **Sender Chain Minimization:** For new group messages, `sender_chain_id`/`sender_chain_n` stored in per-recipient `message_recipients.ratchet_header` instead of `messages` table. Removes persistent sender correlation from message-level DB rows. Server verifies group-only context (dummy `dh_pub` all-zeros) before extracting chain data from per-recipient header — prevents DM injection. Self-copy entries (sender's `user_id == session["user_id"]`) are skipped during chain_meta injection. `_validate_header` now accepts `{"self": true}` for per-recipient ratchet_header (fixes self-copy storage in DB). `handle_get_messages` extracts from both locations (backward compat). Push notifications still include chain data for live decrypt. +- **Architectural limitation (known):** Server still stores `messages.sender_id` and `message_recipients.user_id` — the communication graph (who talks to whom, when) remains visible to the server. Full metadata hiding (e.g. Signal's Sealed Sender, onion routing) is a fundamentally different architecture requiring sender anonymity at the protocol level. Current metadata privacy measures reduce *unnecessary* metadata exposure (message length, log PII, interaction history retention, group chain correlation) but do not hide the communication graph itself. + +## Conventions + +- Server handlers: `handle_(msg, session, writer)` — registered in dispatch table in `handle_client()` +- DB functions: one `get_connection()` per call, `cursor(dictionary=True)`, returns dicts +- Binary data: always base64 in protocol (`encode_binary`/`decode_binary`) +- GUI signals: bridge emits `pyqtSignal`, MainWindow connects in `_connect_signals()` +- Error responses: `{"status": "error", "data": {"message": "..."}}` +- Notification decrypt returning `None` = control message, skip silently +- GUI stylesheet font sizes: always `pt`, never `px` (see Font Handling section above) +- File sharing reuses image upload infrastructure with `file_type` parameter +- Avatar files stored in `UPLOAD_DIR/avatars/` — user: `{user_id}.{ext}`, group: `group_{conv_id}.{ext}` + +## Aktuální stav práce + +### ✅ Dokončeno (tato session) +- **iOS client (Swift/SwiftUI)** — Full native iOS port in `ios_client/` (47 files, ~6200 lines). Wire-compatible with Python server. Crypto layer: CryptoKit (AES-GCM, HKDF, Ed25519, X25519), pure Swift GF(2^255-19) for Ed→X conversion, Security.framework RSA-4096, ECP1 key encryption (PBKDF2 600k + AES-GCM). Protocol: Network.framework TCP+TLS, newline-delimited JSON. Core: ChatClient actor (1644 lines) with X3DH, Double Ratchet, Sender Keys, per-device sessions, SPK rotation. UI: SwiftUI views (login/register, conversation list, chat with message bubbles, group info, profile, search). Server-side RSA PSS compatibility fix: `PSS.MAX_LENGTH` → `PSS.AUTO` in `rsa_verify()` to accept both Python (max salt) and iOS (hash-length salt) signatures. +- Logout/login bug fix — `_is_logout` flag prevents bridge.stop() on logout +- Hover text readability — `color: #cdd6f4;` added to `QListWidget::item:hover` +- File card background — `background:transparent; border:1px solid #45475a` +- Delete conversations — full stack (db, server, chat_core, gui), DMs + groups (creator-only), file cleanup from disk +- Group invitation system — full stack (schema, db, server, chat_core, gui), create/accept/decline, real-time notification, invitation list UI +- Circular avatars in conversation list — QPainter circular crop, default letter avatars, online green dot overlay +- Group avatar support — upload/download, display in group info dialog, "Change Avatar" button (creator only) +- Server graceful shutdown — force-close connected clients on SIGINT +- Profile dialog avatar circular crop — QPainter in UserProfileDialog._on_avatar_loaded +- Periodic refresh timer — 2-minute QTimer re-downloads avatars + invitations +- Group invitation notification fix — `group_invitation` added to `_background_listener` notification types +- Delete button in conversation header — trash icon for DMs always, groups creator-only +- File cleanup on conversation delete — `db.get_conversation_file_ids()` + unlink `.enc` files +- Removed right-click "Delete conversation" from conversation list context menu +- README.md updated +- **H4 Race conditions fix** — 4 asyncio.Lock guards (`_clients_lock`, `_conn_lock`, `_pairing_lock`, `_uploads_lock`) pro všechny sdílené mutable struktury v server.py. `_notify_users()` + `_notify_users_individual()` helpery. Rate limit memory cleanup v periodic task. Všechny I/O operace mimo kritické sekce. +- **Unread counts pro offline uživatele** — `db.get_unread_counts()` dotaz přes `message_reads` + `message_recipients`, server vrací `unread_count` v `list_conversations`, GUI populuje `_unread_counts` ze serverových dat (max z server vs local). Opravuje bug kdy offline uživatel po přihlášení neviděl nepřečtené zprávy. +- **C6 Path traversal fix** — `_UUID_RE` regex + `_valid_file_id()`, `_safe_upload_path()`, `_safe_avatar_path()` helpery v server.py. UUID validace v `handle_upload_image_start`, `handle_download_image`. `is_relative_to()` guard ve všech path konstrukcích: upload start/end, download, delete_message file cleanup, delete_conversation file cleanup, _cleanup_uploads, get/update avatar, get/update group avatar. Celkem 10 guardovaných míst. +- **C3+H1+M13 Lokální šifrování + permissions** — `derive_local_storage_key()` v crypto_utils.py (HKDF z identity key, odlišný salt/info od self-encryption key). `_encrypt_local()`/`_decrypt_local()` helpery v chat_core.py (AES-256-GCM, formát: nonce(12)+tag(16)+ct). `_save_session`/`_load_session`, `_save_sender_key_state`/`_load_sender_key_state`, `_save_recv_sender_key`/`_load_recv_sender_key` — volitelný `local_key` parametr, při nastavení šifruje/dešifruje, `chmod 0o600` na soubory. `ChatClient._local_key` derivováno při login/registraci/pairingu. Transparentní migrace: pokud dešifrování selže, zkusí plaintext a re-uloží šifrovaně. `os.chmod(d, 0o700)` na všechny `mkdir()` v get_key_dir, opk_private, sessions, sender_keys, sender_keys_recv, message_cache. `os.chmod(p, 0o600)` na plaintext fallback message cache. +- **H7 Avatar path traversal** — `_safe_avatar_path()` guard na handle_get_avatar, handle_get_group_avatar + defense-in-depth na handle_update_avatar, handle_update_group_avatar. +- **Multi-device support (per-device sessions)** — `devices` table, `device_id` columns on prekeys/messages/recipients/sender_keys. Server: device registration at login, `writer_device_map`, per-device key bundles (`device_bundles` array), per-device notification routing (`device_entries`), `list_devices`/`remove_device` handlers. Client: `device_id` persistence, sessions keyed by `"user_id:device_id"`, `_get_device_bundles()` with 5-min TTL cache, per-device encryption in `_send_dm`/`send_image`/`send_file`/`_distribute_sender_key`, `sender_device_id` in decrypt routing, `decrypt_notification()` handles `device_entries` format. Pairing simplified: only RSA + identity key transfer, new device generates own SPK + OPKs. Migration: old session files auto-migrated, backward compat with old clients/servers. H12 OPK race condition fixed (SELECT FOR UPDATE). +- **Connection resilience fixes** — `_background_listener` fails all pending futures with `ConnectionError` on disconnect (prevents hang). `send_and_recv` has 30s timeout + catches `ConnectionError`. Server dispatch has per-message try/except (handler crash no longer kills connection). GUI `_do_send_message`/`_do_find_or_create_and_send` catch exceptions and emit error signal. +- **DB transaction fix** — `db.get_key_bundles_for_user()` had "Transaction already in progress" error because mysql-connector starts implicit transactions. Fixed with `conn.commit()` before `conn.start_transaction()`. +- **H5+H6 Protocol error handling** — `decode_binary()` catches `binascii.Error` → `ValueError`. `parse_message()` catches `JSONDecodeError`/`UnicodeDecodeError` → `ValueError`. Server dispatch already handles `ValueError` from `read_message()` gracefully. +- **H3+H13 Anti-enumeration** — `handle_register_start` returns same "ok" response for existing email (no "Email already in use" leak). `handle_login_start` returns fake challenge for non-existent email. `handle_login_finish` returns generic "Invalid credentials" for all failure cases. `get_user_info` moved behind auth barrier (requires login). +- **H8 Password memory cleanup** — `register()`, `login()`, `pairing_wait()` convert password to `bytearray`, zero out in `finally` block after key derivation. +- **H10 Image validation** — `_safe_load_image()` helper validates size (<10MB) and dimensions (<8192px) before `QImage.fromData()`. Applied to all 6 image loading locations in gui_client.py. +- **H11 Filename sanitization** — `_safe_filename()` helper strips path components via `os.path.basename()`. Applied to save dialogs and image dialog title. +- **C1+C2+C5 DoS hardening** — C1: `LimitOverrunError` now drains buffer and raises `ValueError` (server sends error response instead of silent disconnect; memory already protected by `limit=` on StreamReader). C2: `MAX_SENDER_KEY_SKIP` reduced from 1000 to 256 (matches DoubleRatchet `MAX_SKIP`). C5: `handle_upload_image_end` validates `received_bytes == file_size` before completing upload. M12 (upload end size validation) also fixed by C5. +- **M2+M8+M10+M11 Security hardening batch** — M2: SenderKeyState HKDF salt changed from `b""` to `b"\x00" * 32` (matches X3DH convention). M8: `_valid_file_id()` renamed to `_valid_uuid()`, UUID validation added to all handlers accepting client-provided `conv_id`, `user_id`, `message_id`, `device_id`. M10: `handle_mark_read` caps `message_ids` to 500 (prevents slow SQL DoS). M11: `handle_pairing_start` generates `poll_token` (secrets.token_hex(16)), `handle_pairing_poll` requires and validates it via `secrets.compare_digest()` — prevents unauthorized poll/payload extraction. +- **H2+H14 TLS hardening** — `TLS_INSECURE` a `TLS_AUTOGEN` nyní vyžadují `ENVIRONMENT=dev` (RuntimeError bez toho). Warning log na serveru i klientovi když TLS vypnuté. C4 (OPK file permissions) bylo již opraveno v C3+H1+M13 batchi. +- **Online dot fix + sorting** — Fixed timing issue where `online_users` signal processed before conv list populated. `_rebuild_conv_list()` sorts: favorites first → online DMs → rest alphabetically. +- **Favorites system** — Right-click context menu on conversation list → Add/Remove from favorites. Star indicator (★). Persisted to `favorites.json` in user key directory. +- **Group renaming** — Full stack: `db.update_conversation_name()`, `handle_rename_conversation` (server, creator-only, max 100 chars), `rename_conversation()` (chat_core), "Rename" button in group info dialog (GUI), `conversation_renamed` push notification to all members. +- **M3+M4+M9 Security hardening** — M3: PBKDF2 600k iterations (`_encrypt_private_key`/`_decrypt_private_key` s ECP1 formátem, backward compat pro PEM). M4: SPK rotace každých 7 dní, `spk_created_at` v `get_prekey_count`, grace period s `prev_spk_private.bin`, fallback v `_process_x3dh_header`/`_decrypt_dm`. M9: `_snapshot()`/`_restore()` v `DoubleRatchet.decrypt()`, snapshot/restore v `SenderKeyState.decrypt()`. +- **Phantom invitation fix** — Phantom users now receive group invitations. `handle_create_conversation` and `handle_add_member` create invitations for phantoms (no push notification). `handle_register_confirm` upgrades phantom in-place via `db.upgrade_phantom_user()` (preserves user_id + FK references). `handle_add_member` creates phantom for unregistered emails (same as `create_conversation`). After registration, user sees pending invitations on login. +- **Message Search** — Client-side search through decrypted message cache. `ChatClient.search_messages()` searches local cache. GUI: collapsible search bar (Ctrl+F) with prev/next navigation, match count, yellow/orange highlighting in message HTML. Escape closes search. Search button in chat header. +- **Session Recovery** — `session_reset` protocol message + `handle_session_reset` server handler. `ChatClient.reset_session()` deletes local session + notifies peer. Peer handles `session_reset` notification by deleting their session. Next message auto-creates new session via X3DH. GUI: "Reset session with sender" context menu on undecryptable messages, status bar notification on incoming reset. +- **L8 Phantom user DB inflation fix** — `_valid_email()` helper validates email format before phantom creation in `handle_find_conversation`, `handle_create_conversation`, `handle_add_member`. `db.cleanup_stale_phantoms(30)` deletes phantom users older than 30 days with no active conversations with real users. Runs in `_periodic_cleanup` every 10 minutes, refreshes in-memory `phantom_user_ids` cache. +- **M6 TOCTOU race fix** — `db.remove_conversation_member_atomic()` returns bool (True if row existed). Used in `handle_remove_member` (checks return value, returns error if already removed) and `handle_leave_group`. Defense-in-depth: pre-checks remain for user-friendly errors, atomic operation prevents double-removal. +- **Local-first messages + multi-device received messages fix** — Self-encrypted copies for received messages (not just sent). `batch_reencrypt_messages()` changed to INSERT ON DUPLICATE KEY UPDATE (upsert) — allows creating SELF_DEVICE_ID rows for received messages. Server-side Python dedup in `handle_get_messages` (prefers device-specific over SELF_DEVICE_ID). `after_ts` parameter for incremental sync. `get_deleted_since` protocol + handler for deletion sync. `_pending_self_encrypt` queue + `_flush_self_encrypt()` for background self-encryption of received notifications via `asyncio.ensure_future()`. +- **Detailed server logging** — `_who(session)` helper for consistent log formatting. 25+ tagged log lines: `[CONN]`, `[LOGIN]`, `[REGISTER]`, `[MSG]`, `[FETCH]`, `[READ]`, `[UPLOAD]`, `[DOWNLOAD]`, `[CONV]`, `[INVITE]`, `[LEAVE]`, `[RENAME]`, `[DELETE]`, `[AVATAR]`, `[PREKEYS]`, `[X3DH]`, `[ROTATE]`, `[DEVICE]`, `[REENCRYPT]`, `[SESSION]`, `[MEMBER]`, `[LIST]`, `[ERROR]`. +- **Version bump to 0.8.4** — `VERSION = "0.8.4"`, `MIN_CLIENT_VERSION = "0.8.3"` in protocol.py. Server rejects clients older than 0.8.3. iOS client (0.8.3) still works — new notification types and payload fields are silently ignored. +- **Message Reactions** — Emoji reactions on messages (`thumbsup`, `heart`, `laugh`, `surprised`, `sad`, `thumbsdown`). `message_reactions` DB table. `db.add_reaction()`/`db.remove_reaction()`/`db.get_reactions()`. Server: `handle_react_message` + `message_reacted` push notification. `get_messages` response includes `reactions` array per message. GUI: reaction submenu in context menu, toggle (add/remove), emoji badges below messages (grouped by reaction, highlighted if own), real-time updates via notification. CLI: option 16. +- **Message Forwarding** — Forward messages to other conversations. `ChatClient.forward_message()` sends as normal `send_message` with `forwarded_from` metadata field (`{sender, conversation_id, message_id}`). No new server endpoint needed. GUI: "Forward" in context menu, conversation picker dialog, "Forwarded from" header with blue left border in message rendering. CLI: option 19. +- **Pinned Messages** — Pin/unpin messages in conversations. `messages.pinned_at`/`pinned_by` columns. `db.pin_message()`/`db.unpin_message()`/`db.get_pinned_messages()`. Server: `handle_pin_message` + `handle_get_pinned_messages` + `message_pinned`/`message_unpinned` push notifications. GUI: "Pin"/"Unpin" in context menu, pin emoji indicator in message header, "Pinned" button in header opens dialog with list (double-click scrolls to message), real-time updates. CLI: options 17-18. +- **@Mentions** — `@username` highlighting in messages. Client-side only (no server-side handling). GUI: `MentionCompleter` popup autocomplete activated when typing `@` in message input, filters current conversation members, inserts `@username` on selection. Message rendering: `@word` patterns highlighted in blue bold (`#89b4fa`). CLI: visible as plain `@username` text. +- **Drag & Drop souborů/obrázků** — Přetažení souboru na chat oblast (message input nebo message area) automaticky odešle. Obrázky (`.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.webp`) se posílají přes `send_image`, ostatní přes `send_file`. Vizuální feedback: modrý dashed border při drag-over. Drop je aktivní pouze když je vybraná konverzace (`drop_enabled` flag na `MessageInput`, `current_conv_id` check v event filteru na `message_area`). +- **Registration prekey upload fix** — `_ensure_prekeys()` now detects missing SPK on server (empty `spk_created_at`) and forces upload with `need_new_spk=True`. Previously, registration uploaded prekeys before login (no session → server rejected with "Not logged in"), and the login flow only uploaded OPKs without SPK. Also added warning log in `_generate_and_upload_prekeys()` when upload fails. +- **Message sender identification fix** — `_render_single_message_html()` now uses `sender_id` (user_id UUID) instead of `sender` (username) for `is_me` detection. Fixes incorrect message alignment when two users share the same display name. +- **Privacy Overlay (lock screen)** — Anti-forensic privacy feature (PyQt only). On window deactivation: immediate dark overlay with lock icon hides all chat content (protects against Alt+Tab thumbnails, screen sharing, shoulder surfing). After 30 seconds unfocused: locks and requires login password to unlock (verified by decrypting identity key from disk via ECP1/PBKDF2-600k). Ctrl+Shift+P toggles on/off. Tray notifications continue working while locked. Messages arriving during lock are counted as unread and NOT marked as read until user unlocks. `_lock_capable` flag auto-detects if identity key is password-encrypted (ECP1 format) — lock requires password only when available. +- **Secure Deletion (anti-forensic wipe)** — `_secure_delete(path)` helper overwrites file with `os.urandom()` + `fsync` before `unlink`. Applied to all sensitive file deletions: OPK private keys, session files (reset + migration), received sender key migration, message cache migration (plaintext JSON cleanup) in chat_core.py; `.enc` and `.tmp` files on conversation delete, message delete, oversized upload cleanup, incomplete upload cleanup, stale upload periodic cleanup in server.py. Fallback to standard `unlink` if overwrite fails. +- **UI redesign (Signal/Telegram look)** — `theme.py` theme systém (ThemeColors dataclass, DARK_THEME Catppuccin Mocha, LIGHT_THEME Signal-inspired, ThemeManager singleton s persistence + live switching). Widget-based message bubbles (MessageBubble QFrame s QPainter rounded rect). ConversationDelegate (custom painting: avatar, name, preview, timestamp, unread badge). Redesigned chat header (avatar, name, status, action buttons). Pill-shaped input (auto-resize, 2-line min). Tabbed login (Login/Register/Link Device). Frameless dialogs (`_make_frameless`). Theme toggle (sun/moon) v settings. +- **Reakce/piny persistentní v cache** — `ChatClient.update_message_in_cache()` synchronně aktualizuje pole zprávy v šifrovaném message cache na disku. Volá se při přidání/odebrání reakce, pin/unpin (z kontextového menu i z push notifikací). Opravuje bug kdy reakce a piny mizely po přepnutí konverzace a návratu (incremental sync nenačítal stará data ze serveru). +- **Context menu fallback na zprávách** — `MessageBubble` context menu policy změněna z `CustomContextMenu` na `DefaultContextMenu`. S `CustomContextMenu` Qt emitoval nepřipojený signál místo volání `contextMenuEvent()` override — kontext menu se neukázalo. Event filter zůstává jako primární handler, `contextMenuEvent` je fallback. +- **Forward obrázků a souborů** — `forward_message()` v chat_core.py přeposílá kompletní `image`/`file` metadata (file_id, AES klíč, IV, thumbnail, filename, size). Dříve se posílal jen textový popis `[Forwarded image: ...]`. Šifrovaný soubor je na serveru, stačí přeposlat metadata. +- **Delete message okamžitý** — Smazání zprávy se projeví lokálně ihned (bez čekání na reload). Server posílá `message_deleted` notifikaci jen ostatním, ne odesílateli — opraveno lokálním označením zprávy jako smazané + uložením do cache + re-renderem před odesláním na server. +- **Frameless confirmation dialogy** — `_confirm_dialog()` helper nahrazuje `QMessageBox.question()` — frameless dialog bez systémové lišty, s červeným "Delete" tlačítkem. Aplikováno na Delete Message a Reset Session dialogy. +- **Reakce — explicitní barva textu** — Reaction badge QLabel nyní má explicitní `color: {t.text_primary}` aby byl text viditelný v obou režimech (předtím spoléhal na CSS dědičnost). +- **SPK/OPK encryption + brute-force lockout** — SPK/OPK private keys now encrypted with AES-256-GCM via `_local_key` (derived from identity key via HKDF). Transparent migration from plaintext. Client-side brute-force lockout: exponential backoff `min(2^N, 300)` seconds after N failed password attempts. Lockout state in `login_lockout.json`. Applied to `ChatClient.login()` and GUI privacy overlay unlock (`_on_unlock_attempt`). `_clear_lockout()` on success. +- **Contact Key Verification** — Signal-style safety numbers, fingerprints, QR codes. TOFU key tracking (`known_identity_keys.bin`) + explicit verification (`verified_contacts.bin`), both encrypted with `_local_key`. `crypto_utils.py`: `compute_fingerprint()` (iterated SHA-512, 5200x), `format_fingerprint()` (30 digits), `compute_safety_number()` (60 digits, symmetric), `encode_verification_qr()`/`decode_verification_qr()`. `chat_core.py`: `check_identity_key()` (TOFU with "new"/"trusted"/"verified"/"changed"/"changed_verified"), `verify_contact()`, `unverify_contact()`, `accept_key_change()`, `get_verification_status()`, `get_safety_number()`, `get_my_fingerprint()`, `get_peer_fingerprint()`, `get_verification_qr_data()`, `verify_qr_code()`. GUI: `VerificationDialog` (safety number, QR code, fingerprints, verify/unverify buttons, QR scan), green checkmark in conversation list for verified DMs (`ROLE_VERIFIED`), E2E label clickable with verification status, key change warning dialog, security section in `UserProfileDialog`, verified badge in group info member list. CLI: options 20 (verify contact) and 21 (show fingerprint). Zero server changes. +- **Metadata Privacy (Ochrana metadat)** — Čtyři opatření pro minimalizaci metadat: **(A) Message Padding** — `pad_plaintext()`/`unpad_plaintext()` v crypto_utils.py. Bucketed padding (64/128/256/512/1K/2K/4K/8K/16K/32K/64K). Formát: `0x01 + plaintext + random_padding + pad_length(4B)`. Prefix `0x01` rozliší od legacy JSON (začíná `{`). Aplikováno na všech 6 send cest v chat_core.py (send_message, distribute_sender_key, forward_message, send_image, send_file, reencrypt_history) + unpad na 2 decrypt cestách (_decrypt_dm, _decrypt_group). **(B) Log Sanitizace** — `_who()` vrací `u=XXXXXXXX d=YYYYYYYY` místo `username (email) [device]`. Group names a recipient counts odstraněny z logů. **(C) Metadata Retention** — `db.cleanup_old_reads()`/`db.cleanup_old_reactions()` mažou záznamy starší N dní (default `METADATA_RETENTION_DAYS=90`). Batch delete (10k/iterace). Spouští se v `_periodic_cleanup()` každé 2 minuty. **(D) Sender Chain Přesun** — `sender_chain_id`/`sender_chain_n` se pro nové group zprávy ukládají do `message_recipients.ratchet_header` místo `messages` tabulky. Server `handle_get_messages` extrahuje z obou míst (backward compat). Notifikace stále posílají chain data pro live decrypt. + +### 🐛 Známé bugy a problémy +- **Sender Key Redistribution (High Priority):** New group member can't decrypt old messages. On `add_member`, existing members should re-create and redistribute sender keys. +- ~~**Database Connection Pooling:**~~ ✅ OPRAVENO — `MySQLConnectionPool(pool_size=10)`. +- ~~**Duplicate FETCH after send (GUI):**~~ ✅ OPRAVENO — `send_message` vrací payload lokálně, GUI appenduje bez re-fetch. +- ~~**Group delete confirmation message is generic**~~ ✅ OPRAVENO — frameless dialog s kontextovým textem. +- ~~**Reakce a piny mizely po přepnutí konverzace:**~~ ✅ OPRAVENO — `update_message_in_cache()` ukládá na disk. +- ~~**Forward obrázku/souboru posílal jen text:**~~ ✅ OPRAVENO — přeposílá kompletní image/file metadata. +- ~~**Delete message se neprojevil okamžitě:**~~ ✅ OPRAVENO — lokální smazání + cache update před serverovým voláním. +- ~~**Delete/Reset dialogy měly systémovou lištu:**~~ ✅ OPRAVENO — frameless `_confirm_dialog()`. + +### ⏭️ Další kroky (TODO) + +#### Bezpečnostní opravy (priorita dle auditu) +1. **C6 (CRITICAL): Path traversal přes file_id** — `handle_upload_image_start` vytváří soubor `UPLOAD_DIR / f"{file_id}.tmp"` bez validace. Útočník může `../../...` a zapisovat/mazat mimo UPLOAD_DIR. Řešení: validovat UUID formát, ověřit `path.resolve().is_relative_to(UPLOAD_DIR.resolve())`. +2. ~~**H12 (HIGH): OPK race condition v db.get_key_bundle()**~~ ✅ OPRAVENO (součást multi-device — SELECT FOR UPDATE v consume_one_time_prekey + get_key_bundle) +3. **H3+H13: User enumeration** — `get_user_info` dostupné bez auth, vrací identity_key pro libovolný email. `register_start`/`login_start` vrací jednoznačné chyby. Řešení: auth pro `get_user_info`, generické odpovědi pro register/login. +4. ~~**H2+H14: TLS hardening**~~ ✅ OPRAVENO — `TLS_INSECURE` a `TLS_AUTOGEN` vyžadují `ENVIRONMENT=dev`. Warning log při vypnutém TLS. +5. ~~**C1+C2+C5**~~ ✅ OPRAVENO — DoS vektory (LimitOverrunError → ValueError, MAX_SENDER_KEY_SKIP 256, upload completeness check) +6. **C3+C4+H1** — Šifrování dat na disku (message cache, sessions, OPK permissions, `chmod 0o700` pro adresáře) +7. **H5+H6** — Error handling v protokolu (base64, JSON) +8. **H7** — Path traversal v avatar souborech (`resolved_path.is_relative_to()`) +9. ~~**M11 (MEDIUM): Pairing poll DoS**~~ ✅ OPRAVENO — poll_token binding (secrets.token_hex(16) + secrets.compare_digest) +10. ~~**M12: Upload end bez validace velikosti**~~ ✅ OPRAVENO (součást C5 fixu — `handle_upload_image_end` validuje `received_bytes == file_size`) +11. ~~**L8: Phantom user DB inflation**~~ ✅ OPRAVENO — email validace + periodic cleanup stale phantoms (30 dní) +12. **Version negotiation** — `VERSION = "0.8"` v protocol.py, klient posílá `client_version` při loginu, server loguje a vrací `server_version` + +#### Před nasazením do produkce (checklist) +1. **TLS certifikáty** — Získat certifikát (Let's Encrypt / vlastní CA). Nastavit `TLS_ENABLED=true`, `TLS_CERT_FILE`, `TLS_KEY_FILE` v `.env`. Ověřit že `TLS_INSECURE` a `TLS_AUTOGEN` NEJSOU nastavené (vyžadují `ENVIRONMENT=dev`). Na klientovi nastavit `TLS_ENABLED=true` a případně `TLS_CA_FILE` pokud vlastní CA. +2. **Email validace** — Zapnout `_valid_email()` kontrolu v `handle_find_conversation`, `handle_create_conversation`, `handle_add_member` (kód existuje v server.py, volání zakomentována). Teď vypnuto protože dev prostředí používá emaily bez @. +3. **MySQL TLS** — Přidat SSL parametry do `db.get_connection()` (`ssl_ca`, `ssl_cert`, `ssl_key`) pokud DB běží na jiném stroji. +4. **Connection pooling** — Nahradit `get_connection()` za `mysql.connector.pooling.MySQLConnectionPool(pool_size=10)`. +5. **SMTP** — Nastavit reálný SMTP server pro registrační kódy (`SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM`). +6. **UPLOAD_DIR** — Ověřit že `UPLOAD_DIR` je na persistentním disku s dostatkem místa, správnými právy (0o700). +7. **Rate limity** — Přezkoumat limity pro produkční zátěž (registrace 3/min, login 10/min, send_message 20/min, max 10 spojení/IP). +8. **Packaging** — Zabalit klienta (pyinstaller / cx_Freeze) pro distribuci. Po zabalení zvážit auto-update mechanismus a `get_version` endpoint. +9. **Penetrační testy** — Provést před ostrým nasazením (viz sekce níže). +10. **Backup** — Nastavit pravidelný backup MySQL databáze + `UPLOAD_DIR`. + +#### Penetrační testy +- Naplánovat a provést manuální penetrační testy zaměřené na: + - Path traversal (file_id, avatar_file) + - DoS vektory (readuntil, sender key fast-forward, upload flooding) + - Race conditions (OPK reuse, membership TOCTOU) + - User enumeration (register, login, get_user_info) + - TLS downgrade / MITM bez TLS + - Pairing session hijacking + - Memory exhaustion (rate_limits, phantom users, message_ids) +- Vytvořit testovací skripty pro automatizované security testy +- Zdokumentovat výsledky a opravit nalezené problémy + +#### Ke zvážení +- **Auto-update klientů** — distribuce aktualizovaných souborů klientům před login/registrací. Řešit až po kompilaci/packagingu (pyinstaller apod.). Mechanismus: server verze check → klient stáhne nové soubory → restart. +- **Server version check endpoint** — po packagingu mít jednoduchý endpoint (např. `get_version`), který vrací min/aktuální podporovanou verzi klienta + URL/metadata pro update; klient může před loginem ověřit kompatibilitu a nabídnout update. Vhodné i pro postupné vypínání starých klientů. + +#### Monetizace — plán + +**Princip:** Oddělený platební server (KYC/AML compliant) od chat serveru (anonymní). Platba generuje jednorázový premium kód, chat server zná jen "user_id aktivoval kód". Žádný přímý link platba↔chat identita. + +**Architektura:** +``` +Platební server (Stripe/Paddle): zákazník → platba → vygeneruje premium_code + sdílí jen: premium_code (jednorázový string) +Chat server: user_id → aktivuje premium_code → premium do data X +``` +- Platební server neví jaký user_id kód použil +- Chat server neví kdo kód koupil +- AML splněno na platební straně, privacy zachováno na chat straně + +**Free vs Premium tier:** +| | Free | Premium | +|---|---|---| +| Konverzace | 5 | neomezeno | +| Soubory | 10 MB | 50 MB | +| Zařízení | 1 | 5 | +| Message retention | 30 dní | neomezeno | +| Skupiny | max 10 členů | neomezeno | + +**Implementace (chat server):** +- Tabulka `premium_codes(code VARCHAR(64) PK, plan ENUM('premium_30','premium_365'), created_at, redeemed_by CHAR(36) nullable FK, redeemed_at nullable)` +- Tabulka `user_plans(user_id PK FK, plan ENUM('free','premium'), expires_at DATETIME nullable, message_retention_days INT DEFAULT 30)` +- Sloupec `messages.expires_at` (nullable DATETIME) — NULL = neomezená retence +- Handler `redeem_code` — validuje kód, aktivuje plán, nastaví expires_at +- `handle_send_message` — kontroluje limity (konverzace, velikost) +- Periodic cleanup: `DELETE FROM messages WHERE expires_at IS NOT NULL AND expires_at < NOW()` + CASCADE + `.enc` smazání +- Retence podle konverzace: pokud alespoň jeden člen premium → vyšší retence +- Klient: indikace expirace zpráv, upozornění na limity, "Upgrade" tlačítko + +**Implementace (platební server — oddělený deployment):** +- Jednoduchý web (Flask/FastAPI) s Stripe Checkout +- Generuje premium_code, uloží do sdílené DB nebo pošle přes API +- AML/KYC řeší Stripe (PCI DSS, SCA, reporting) + +**Další revenue streams:** +- **Enterprise/B2B licence** — self-hosted deployment, LDAP/SSO, admin dashboard, SLA. Faktura na IČO. +- **Šifrovaný cloud backup** — export/import historie šifrované uživatelským heslem (nezávisle na retenci) + +#### Funkční vylepšení +1. **Sender Key Redistribution** — on add_member, redistribute sender keys to all members including new one +2. ~~**Device Linking fix**~~ ✅ — replaced with true multi-device (per-device sessions, simplified pairing) +3. ~~**SPK Rotation**~~ ✅ — periodic rotation with grace period (implemented in M4 fix) +4. **Typing Indicators** (budoucí) — `typing_start`/`typing_stop` protocol + GUI indicator (3s timeout, debounce) +5. ~~**CLI support**~~ ✅ — profiles, file sharing, invitations, leave/rename/delete, search, devices in `client.py` +6. ~~**Message search**~~ ✅ — client-side search through decrypted cache, Ctrl+F toggle, highlight + navigation +7. ~~**Session Recovery**~~ ✅ — `session_reset` protocol, auto-recreate via X3DH on next message +8. ~~**Connection Pooling**~~ ✅ — `MySQLConnectionPool(pool_size=10)`, lazy init, `DB_POOL_SIZE` env var. `conn.close()` vrací do poolu. +9. ~~**Version negotiation**~~ ✅ — `VERSION = "0.8.4"` in protocol.py, client sends `client_version` at login, server rejects clients < MIN_CLIENT_VERSION +10. **Delivery Receipts** — `message_delivered` notifikace po přijetí na zařízení (1 fajfka = odesláno, 2 fajfky = doručeno, modré = přečteno). Nová tabulka `message_deliveries` nebo rozšíření `message_reads`. +11. ~~**Reakce na zprávy (Message Reactions)**~~ ✅ — emoji reakce na zprávy. Tabulka `message_reactions`. Push notifikace `message_reacted`. GUI: emoji badges pod zprávou, submenu v kontext menu. +12. ~~**Přeposílání zpráv (Message Forwarding)**~~ ✅ — kontext menu "Forward", výběr konverzace, odeslání s `forwarded_from` metadatem. GUI: "Forwarded from" header. +13. ~~**Připnuté zprávy (Pinned Messages)**~~ ✅ — `messages.pinned_at`/`pinned_by` sloupce. `pin_message`/`unpin_message`/`get_pinned_messages` protokol. GUI: pin ikona + dialog s připnutými zprávami. +14. ~~**Zmínky (@mentions)**~~ ✅ — parsování `@username` v textu, autocomplete při psaní @, zvýraznění zmínek v modré. Klient-side only (bez server notifikací). +15. ~~**Contact Key Verification**~~ ✅ — Signal-style safety numbers (60 digits, symmetric), fingerprints (30 digits), QR codes. TOFU key tracking + explicit verification. GUI: VerificationDialog, green checkmark v conv listu, E2E label status, key change warning. CLI: options 20+21. Zero server changes. + +#### Optimalizace serveru +1. ~~**DB Connection Pooling**~~ ✅ — viz bod 8 výše. +2. ~~**Oprava duplicitních FETCH v GUI**~~ ✅ — `send_message` vrací payload lokálně, GUI appenduje bez re-fetch. Dedup guard v `_on_notification`. +3. ~~**Batch prekey replenishment**~~ ✅ — `ensure_prekeys` handler na serveru (get_prekey_count + upload v jednom roundtripu). `_generate_and_upload_prekeys_batch()` v chat_core. +4. ~~**Server-side message count**~~ ✅ — `get_messages` response obsahuje `total_count`. `db.count_messages()` funkce. +5. **Prepared statements / query cache** — pro často opakované dotazy (get_messages, list_conversations) připravit prepared statements. +6. **WebSocket upgrade** — dlouhodobě nahradit raw TCP za WebSocket pro lepší kompatibilitu s firewally, load balancery, a web klienty. + +#### Mobilní push notifikace (budoucí — iOS + Android) +- Tabulka `push_tokens(user_id, device_id, platform ENUM('ios','android'), token, created_at)` +- Server: při `new_message` pokud cílový uživatel nemá aktivní TCP spojení → odeslat přes APNs (iOS) / FCM (Android) +- Obsah push: jen "Nová zpráva od X" (E2EE = server nezná plaintext) +- iOS: `UserNotifications` framework, registrace tokenu při loginu, `didReceiveRemoteNotification` +- Android: Firebase Cloud Messaging, `FirebaseMessagingService` +- Server-side: `aioapns` (Python APNs library) + `firebase-admin` (FCM SDK) + +## Bezpečnostní audit (Security Audit) + +Kompletní audit provedený přes všechny soubory projektu. Nálezy seřazené podle závažnosti. + +### 🔴 CRITICAL — Okamžitě řešit před nasazením + +#### ~~C1. readuntil() bez limitu → memory exhaustion (protocol.py:62)~~ ✅ OPRAVENO +`ProtocolReader.read_message()` volá `readuntil(b"\n")`, které načte CELOU zprávu do paměti PŘED kontrolou velikosti. Útočník pošle gigabyty dat bez newline → server spadne na out-of-memory. +```python +line = await self._reader.readuntil(b"\n") # buffers everything first! +if len(line) > MAX_MESSAGE_BYTES: # too late +``` +**Řešení:** Implementovat framing s hlavičkou obsahující velikost zprávy, nebo použít `readuntil()` s `limit` parametrem (asyncio StreamReader nemá nativně — nutno obalit vlastním čtením po částech). + +#### ~~C2. SenderKeyState — neomezený fast-forward DoS (crypto_utils.py:642-645)~~ ✅ OPRAVENO +Při dešifrování skupinové zprávy s libovolně vysokým `n` se smyčka `while self.n <= n` provede milionkrát — derivuje milion klíčů, spotřebuje stovky MB RAM. +```python +while self.n <= n: + self.chain_key, mk = kdf_ck(self.chain_key) + self._known_keys[self.n] = mk # unbounded dict growth + self.n += 1 +``` +**Řešení:** Přidat `MAX_FORWARD_SKIP` limit (např. 1000) — stejně jako Double Ratchet má `MAX_SKIP=256`. + +#### C3. Dešifrované zprávy uložené jako plaintext na disku (chat_core.py:222-239) +Message cache v `~/.encrypted_chat/{email}/message_cache/{conv_id}.json` obsahuje plný obsah dešifrovaných zpráv v nešifrovaném JSONu. Bez nastavení `chmod 0o600`. Kdokoliv s přístupem k disku přečte kompletní historii. +**Řešení:** Šifrovat cache klíčem odvozeným z identity key + nastavit `chmod 0o600` na soubory. + +#### ~~C4. OPK private keys bez file permissions (chat_core.py:153-156)~~ ✅ OPRAVENO +OPK privátní klíče se ukládají bez `os.chmod(0o600)`. RSA klíče (řádek 87) a identity key (řádek 121) mají `chmod` — OPK ne. Na sdílených systémech může jiný uživatel přečíst ephemeral klíče. +**Opraveno:** Součást C3+H1+M13 fixu — `_save_opk_private()` nyní volá `os.chmod(path, 0o600)` + `os.chmod(dir, 0o700)`. + +#### ~~C5. Chunked upload nevaliduje celkovou velikost (server.py:1138-1142)~~ ✅ OPRAVENO +`handle_upload_image_chunk` akumuluje `received_bytes` ale nekontroluje limit. Útočník deklaruje `file_size=5MB`, pak posílá chunky donekonečna → disk exhaustion. +```python +upload["received_bytes"] += len(raw) # no check against file_size! +``` +**Řešení:** Přidat `if upload["received_bytes"] > upload["file_size"]: reject`. + +### 🟠 HIGH — Řešit před production nasazením + +#### H1. Session + sender key soubory nešifrované na disku (chat_core.py:176-215) +Double Ratchet session state (DH privátní klíče, root key, chain keys) a sender key state se ukládají jako plaintext hex JSON v `sessions/` a `sender_keys/`. Bez šifrování, bez `chmod 0o600`. Kompromitace disku = dešifrování celé historie. +**Řešení:** Šifrovat state klíčem z identity key, nastavit `chmod 0o600`. + +#### ~~H2. TLS vypnuté ve výchozím stavu (chat_core.py:274-291, server.py)~~ ✅ OPRAVENO (hardening) +`TLS_ENABLED` je defaultně `false`. Bez TLS jdou po síti RSA challenge-response, session tokeny a metadata v plaintextu. `TLS_INSECURE=true` vypíná certificate verification → MITM. +**Opraveno:** `TLS_INSECURE` a `TLS_AUTOGEN` nyní vyžadují `ENVIRONMENT=dev` — v produkci RuntimeError. Warning log při vypnutém TLS na serveru i klientovi. TLS_ENABLED zůstává default false (uživatel nemá certifikát), ale po nasazení Let's Encrypt stačí flip na true. + +#### H3. User enumeration přes registraci (server.py:182-189) +Registrace vrací "Email already in use" pro existující uživatele vs. tiché vytvoření phantoma pro neexistující. Útočník může enumerovat platné emaily. +**Řešení:** Vrátit generickou odpověď "Check your email for verification code" i když email existuje. + +#### ~~H4. Race conditions v in-memory strukturách (server.py: multiple)~~ ✅ OPRAVENO +`connected_clients` dict, `phantom_user_ids` set, `pairing_sessions` dict — čteny a zapisovány z více concurrent koroutin bez synchronizace. Asyncio je single-threaded, ale yieldy uvnitř handlerů (await) mohou způsobit nekonzistentní stav. +**Opraveno:** 4 asyncio.Lock guards: `_clients_lock` (connected_clients, phantom_user_ids), `_conn_lock` (connection_counts, current_connections, rate_limits), `_pairing_lock` (pairing_sessions, pending_registrations), `_uploads_lock` (pending_uploads). Helper funkce `_notify_users()` / `_notify_users_individual()` — snapshot under lock, send outside. Rate limit memory cleanup v periodic task. Žádný handler nedrží dva locky současně → deadlock impossible. + +#### H5. base64 decode bez error handling (protocol.py:14-16, server.py + chat_core.py) +`decode_binary()` volá `base64.b64decode()` bez try-except. Nevalidní base64 od klienta → unhandled `binascii.Error` → handler crash. Mnoho callsites v server.py (řádky 357, 378, 783) nemá catch. +**Řešení:** Obalit `decode_binary()` try-except, nebo validovat base64 vstup před dekódováním. + +#### H6. JSON parsing bez exception handling (protocol.py:48-50) +`parse_message()` volá `json.loads()` bez try-catch. Malformovaný JSON = neošetřený `JSONDecodeError`. Server handler catch (řádek 1399) to odchytí, ale není to explicitní. +**Řešení:** Obalit `json.loads()` v `parse_message()` try-except s explicitní chybovou zprávou. + +#### H7. Path traversal v avatar souborech (server.py:1265, 1318) +`avatar_file` ze serveru (z DB) se přímo joinuje s `UPLOAD_DIR / "avatars"` bez validace. Pokud DB obsahuje `../../etc/passwd`, server přečte libovolný soubor. +**Řešení:** Přidat `resolved_path.resolve().is_relative_to(UPLOAD_DIR)` check. + +#### H8. Heslo v paměti jako Python string (chat_core.py, gui_client.py) +Python stringy jsou immutable — nelze je bezpečně vymazat z paměti. Heslo zůstává v paměti dokud garbage collector neuklidí. Memory dump = plaintext heslo. +**Řešení:** Použít `bytearray` (mutable), po použití přepsat nulami: `pwd[:] = b'\x00' * len(pwd)`. + +#### H9. Self-encryption key je statický a deterministický (chat_core.py:904+, crypto_utils.py:224-233) +`derive_self_encryption_key(identity_private)` produkuje vždy stejný klíč. Kompromitace identity klíče = dešifrování všech vlastních kopií zpráv navždy. Žádná forward secrecy pro self-copies. +**Poznámka:** Toto je by-design (nutné pro cross-device čtení), ale je to architektonické omezení. + +#### H10. Malicious image data → QImage crash (gui_client.py) +`QImage.fromData(data)` zpracovává nevalidované binární data. Speciálně vytvořený obrázek může způsobit crash, memory exhaustion, nebo v extrémním případě RCE přes Qt image codec. +**Řešení:** Validovat velikost dat před parsováním, limit na max rozlišení. + +#### H11. Filename z serveru v save dialogu (gui_client.py:2389, 2460) +Server-controlled `filename` se předává jako default do `QFileDialog.getSaveFileName()`. Pokud server pošle `"../../../.bashrc"`, dialog to navrhne. +**Řešení:** Sanitizovat filename — odstranit `../`, `\`, absolutní cesty. Použít jen `os.path.basename()`. + +### 🟡 MEDIUM — Zvážit pro hardening + +#### M1. Inconsistentní Ed25519 serializace (crypto_utils.py:99-102) +Bez hesla: 32 raw bytes. S heslem: PEM PKCS8 (~302 bytes). Dva různé formáty mohou způsobit problém při migraci nebo obnově klíčů. +**Poznámka:** M3 fix částečně řeší — s heslem je nyní ECP1 formát (ne PEM), ale `load_ed25519_private()` stále detekuje 3 formáty (ECP1, PEM, raw). Legacy PEM soubory se automaticky migrují při dalším uložení. + +#### ~~M2. Prázdný salt v SenderKeyState HKDF (crypto_utils.py:610)~~ ✅ OPRAVENO +`hkdf_derive(sender_key, salt=b"", ...)` — RFC 5869 doporučuje nenulový salt. X3DH správně používá `b"\x00" * 32`. +**Opraveno:** Změněno `salt=b""` → `salt=b"\x00" * 32` aby odpovídalo X3DH konvenci. + +#### ~~M3. PBKDF2 iterace pod doporučeným minimem (crypto_utils.py)~~ ✅ OPRAVENO +`BestAvailableEncryption` používá ~100k iterací PBKDF2. OWASP 2023 doporučuje 480k+. +**Opraveno:** Nahrazeno vlastním `_encrypt_private_key`/`_decrypt_private_key` s PBKDF2-HMAC-SHA256 (600k iterací) + AES-256-GCM. ECP1 formát (magic prefix) s backward compat pro staré PEM soubory. Aplikováno na RSA (`serialize_private_key`/`load_private_key`) i Ed25519 (`serialize_ed25519_private`/`load_ed25519_private`). + +#### ~~M4. SPK bez replay protection a bez rotace (server.py:360-368)~~ ✅ OPRAVENO +Stejný SPK lze nahrát opakovaně. Žádný nonce/timestamp v podpisu. SPK se nikdy nerotuje → kompromitovaný SPK = trvalé dešifrování nových sessions. +**Opraveno:** SPK rotace každých 7 dní (`SPK_ROTATION_DAYS`). Server vrací `spk_created_at` v `handle_get_prekey_count`. `_ensure_prekeys()` kontroluje stáří SPK a rotuje pokud >= 7 dní. Předchozí SPK uložen jako grace period (`prev_spk_private.bin`/`prev_spk_id.txt`) pro in-flight X3DH. `_process_x3dh_header` přijímá `spk_override`, `_decrypt_dm` retry s předchozím SPK při selhání dešifrování. + +#### ~~M5. Rate limit unbounded memory (server.py:73-83)~~ ✅ OPRAVENO (součást H4 fixu) +Staré záznamy se nikdy nečistí pokud klíč přestane být aktivní → útočník vytvoří miliony klíčů → memory leak. +**Opraveno:** `_cleanup_rate_limits()` v periodic cleanup (každých 10 min) maže stale entries z `rate_limits` i `connection_counts`. + +#### ~~M6. TOCTOU race v membership checks (db.py)~~ ✅ OPRAVENO +`is_conversation_member()` → `remove_conversation_member()` — mezi kontrolou a akcí může jiný klient stav změnit. +**Opraveno:** `db.remove_conversation_member_atomic()` vrací bool (True pokud řádek existoval). Použito v `handle_remove_member` a `handle_leave_group`. + +#### M7. MySQL spojení bez TLS (db.py:20-28) +`get_connection()` nepředává SSL parametry. Na vzdáleném serveru jdou credentials v plaintextu. + +#### ~~M8. Chybějící validace UUID formátu (server.py: throughout)~~ ✅ OPRAVENO +`conv_id`, `user_id` — kontrola jen na neprázdnost, ne na formát UUID v4. +**Opraveno:** `_valid_file_id()` přejmenováno na `_valid_uuid()`. UUID validace přidána do všech handlerů přijímajících klientem poskytnuté `conv_id`, `user_id`, `message_id`, `device_id`. + +#### ~~M9. Ratchet state corruption recovery (chat_core.py:1088-1104)~~ ✅ OPRAVENO +Pokud `decrypt()` změní chain keys ale selže na AAD verification, backup/restore mechanismus funguje, ale pokud backup selže (out-of-memory), stav zůstane corrupted. +**Opraveno:** `DoubleRatchet.decrypt()` nyní snapshotuje stav přes `_snapshot()`/`_restore()` a rollbackuje při jakékoliv výjimce (včetně skipped message key restore). `SenderKeyState.decrypt()` stejně snapshotuje `chain_key`, `n`, `_known_keys` před fast-forward a rollbackuje při selhání. + +#### ~~M10. Chybějící validace velikosti message_ids listu (db.py:641-646)~~ ✅ OPRAVENO +Klient může poslat tisíce message_ids v jednom požadavku → pomalý SQL dotaz, DoS. +**Opraveno:** `handle_mark_read` nyní odmítá požadavky s více než 500 message_ids. + +### 🟢 LOW — Dobrá praxe, nízké riziko + +- **L1.** Hex string keys v skipped messages dict — timing side-channel po úspěšné autentikaci (crypto_utils.py:425) +- **L2.** RatchetHeader serializace redundantně konvertuje typy (crypto_utils.py:394-405) +- **L3.** `notif_label.setText()` bezpečné proti XSS (Qt neinterpretuje HTML v setText), ale křehké — přepnutí na `setHtml()` by to rozbilo (gui_client.py:1524, 2259) +- **L4.** SQL column interpolation v `update_user_profile` — whitelist chrání, ale pattern je nebezpečný při kopírování (db.py:818-822) +- **L5.** Chybějící TLS cipher suite hardening — Python defaulty jsou rozumné, ale ne explicitně nastavené (protocol.py) +- **L6.** Temporary pairing key není bezpečně vymazán z paměti (chat_core.py:581) +- **L7.** `_user_cache` ukládá public identity keys indefinitely — memory leak pro hodně kontaktů + +### Druhý bezpečnostní review (zaměření na návrh, DB, komunikaci, lokální tmp/cache) + +#### C6. Path traversal → libovolný zápis/smazání souborů přes file_id (server.py) +`handle_upload_image_start` vytváří soubor `UPLOAD_DIR / f"{file_id}.tmp"` bez validace `file_id`. Útočník může poslat `../../...` a zapisovat mimo UPLOAD_DIR. Následné rename, cleanup, `delete_message` a `delete_conversation` pak mohou mazat libovolné soubory. +**Řešení:** Striktně validovat file_id (UUID hex/kanonický formát), odmítnout cokoliv s `/`, `\`, `..`. Ověřit `path.resolve().is_relative_to(UPLOAD_DIR.resolve())`. Ideálně ukládat do podadresářů podle hash/UUID. + +#### ~~H12. OPK race condition — reuse one-time pre-keys (db.py)~~ ✅ OPRAVENO +V `db.get_key_bundle()` se OPK vybírá SELECT → DELETE bez transakčního zámku. Při souběhu může být stejný OPK vydán vícekrát → porušení bezpečnostních předpokladů X3DH. +**Opraveno:** `consume_one_time_prekey()` a `get_key_bundle()` nyní používají `SELECT ... FOR UPDATE` + DELETE v jedné transakci (součást multi-device implementace). + +#### H13. Neautentizované get_user_info + identity key exfiltrace (server.py) +`get_user_info` je dostupné bez přihlášení a vrací username, email a identity_key pro libovolný email/user_id. Umožňuje enumeraci uživatelů a sběr metadata/klíčů. +**Řešení:** Vyžadovat auth, nebo omezit na "kontakty v konverzaci". + +#### ~~H14. TLS_INSECURE umožňuje MITM i v produkci (chat_core.py, server.py)~~ ✅ OPRAVENO +`TLS_INSECURE=true` vypíná verifikaci certifikátu → útočník může podvrhnout key bundle. Přímo ohrožuje E2EE integritu. +**Opraveno:** `TLS_INSECURE` vyžaduje `ENVIRONMENT=dev`, jinak RuntimeError. Součást H2 fixu. + +#### ~~M11. Pairing poll DoS — neautentizovaný přístup k payload (server.py)~~ ✅ OPRAVENO +Kdokoli s 8-místným kódem může pollovat a "vyzvednout" payload (smazán po vyzvednutí). I když je šifrovaný, jde o snadný DoS (reálnému zařízení pairing selže). +**Opraveno:** `handle_pairing_start` generuje `poll_token` (secrets.token_hex(16)), vrací klientovi. `handle_pairing_poll` vyžaduje `poll_token` a porovnává přes `secrets.compare_digest()`. Klient ukládá token v `pairing_start()` a posílá v `pairing_wait()`. + +#### ~~M12. Upload end bez validace received_bytes == file_size (server.py)~~ ✅ OPRAVENO +`upload_image_end` neověřuje, že `received_bytes == file_size`. Může zůstat nedokončený/nevalidní soubor. +**Řešení:** Kontrola délky před `complete_image_upload`. + +#### M13. Klíčové adresáře bez chmod 700 (chat_core.py) +`get_key_dir` a podadresáře (`sessions`, `sender_keys`, `opk_private`) se vytvářejí bez explicitních práv; spoléhá se na umask. +**Řešení:** Po `mkdir` vždy `chmod 0o700` pro adresář, `0o600` pro soubory. + +#### ~~L8. Phantom users — DB inflation (server.py, db.py)~~ ✅ OPRAVENO +`find_conversation` vytváří phantom usery pro libovolné emaily. I s rate limit lze DB časem nafouknout. +**Opraveno:** `_valid_email()` validace před vytvořením phantomu. `db.cleanup_stale_phantoms(30)` v periodic cleanup — maže phantomy starší 30 dní bez aktivních konverzací s reálnými uživateli. + +### Bezpečnostní matice (souhrn) + +| Soubor | CRITICAL | HIGH | MEDIUM | LOW | +|--------|----------|------|--------|-----| +| `protocol.py` | 1 (C1) | 2 (H5, H6) | 0 | 1 (L5) | +| `crypto_utils.py` | 1 (C2) | 0 | 3 (M1-M3) | 2 (L1, L2) | +| `server.py` | 2 (C5, C6) | 3 (H3, H7, H13) | 4 (M4, M8, M11, M12) | 0 | +| `chat_core.py` | 2 (C3, C4) | 4 (H1, H2/H14, H8, H9) | 2 (M9, M13) | 1 (L6) | +| `gui_client.py` | 0 | 2 (H10, H11) | 0 | 2 (L3, L7) | +| `db.py` | 0 | 1 (H12) | 3 (M6, M7, M10) | 2 (L4, L8) | +| **Celkem** | **6** | **12** | **12** | **8** | +| **Opraveno** | 6 (~~C1~~, ~~C2~~, ~~C3~~, ~~C4~~, ~~C5~~, ~~C6~~) | 11 (~~H1~~, ~~H2~~, ~~H3~~, ~~H4~~, ~~H5~~, ~~H6~~, ~~H7~~, ~~H8~~, ~~H10~~, ~~H11~~, ~~H12~~, ~~H13~~, ~~H14~~) | 11 (~~M2~~, ~~M3~~, ~~M4~~, ~~M5~~, ~~M6~~, ~~M8~~, ~~M9~~, ~~M10~~, ~~M11~~, ~~M12~~, ~~M13~~) | 1 (~~L8~~) | +| **Zbývá** | **0** | **1** | **1** | **7** | + +### Doporučené pořadí oprav (aktualizováno) +1. ~~**C6**~~ ✅ — Path traversal přes file_id — DONE (UUID validace + is_relative_to) +2. ~~**C1 + C2 + C5**~~ ✅ — DoS vektory — DONE (LimitOverrunError → ValueError, MAX_SENDER_KEY_SKIP 256, upload completeness check) +3. ~~**H12**~~ ✅ — OPK race condition — DONE (SELECT FOR UPDATE, součást multi-device) +4. ~~**C3 + H1 + H7 + M13**~~ ✅ — Šifrování dat na disku + file/dir permissions + avatar path traversal — DONE +5. ~~**H2/H14**~~ ✅ — TLS hardening (TLS_INSECURE + TLS_AUTOGEN vyžadují ENVIRONMENT=dev, warning log) — DONE +6. ~~**H5 + H6**~~ ✅ — Error handling v protokolu (base64, JSON) — DONE +7. ~~**H3 + H13**~~ ✅ — User enumeration (generické odpovědi, auth pro get_user_info) — DONE +8. ~~**H4**~~ ✅ — Race conditions (asyncio.Lock) — DONE +9. ~~**H8 + H10 + H11**~~ ✅ — Paměť hesel, image parsing, filename sanitizace — DONE +10. ~~**M2 + M8 + M10 + M11**~~ ✅ — Hardening batch (HKDF salt, UUID validace, message_ids cap, pairing poll token) — DONE +11. ~~**M3, M4, M9**~~ ✅ — PBKDF2 600k iterations, SPK rotace 7 dní s grace period, ratchet state rollback — DONE +12. **M1, ~~M6~~, M7** — Remaining hardening (Ed25519 serialization, ~~TOCTOU~~ ✅, MySQL TLS) +13. **Penetrační testy** — manuální + automatizované security testy + +## Důležitá rozhodnutí a kontext +- **Invitations replace direct add for groups:** `handle_add_member` and `handle_create_conversation` (for groups) now create invitations instead of directly adding members. DMs still auto-add both users. This was a design decision to give users control over joining groups. +- **Group delete = total destruction:** When creator deletes a group, ALL members are removed and the conversation is fully deleted. This is different from "leave group" which only removes the leaving user. +- **DM delete is per-user:** Deleting a DM only removes the deleting user. The other user still sees the conversation until they also delete it. +- **Avatar caching in GUI is pixmap-based:** `_avatar_cache` and `_group_avatar_cache` store QPixmap objects, not raw bytes. The `_on_avatar_for_conv_list` and `_on_group_avatar_for_conv_list` signals convert bytes → QImage → QPixmap on receipt. +- **No context menu on conversation list anymore:** Delete was the only action. Now handled by header buttons. `conv_list.setContextMenuPolicy(DefaultContextMenu)`. + +## Environment Variables +See README.md for full list. Key: `SERVER_HOST`, `SERVER_PORT`, `MYSQL_*`, `TLS_*`, `SMTP_*`, `LOG_LEVEL`, `MAX_INPUT_CHARS`, `UPLOAD_DIR`, `MAX_IMAGE_BYTES`, `MAX_FILE_BYTES`, `MAX_MESSAGE_BYTES`, `METADATA_RETENTION_DAYS` (default 90). + +## Commands & Workflow + +- Start server: `python server.py` +- Start GUI client: `python gui_client.py` +- Start CLI client: `python client.py` +- Environment: `.env` file in project root (loaded by `dotenv`) +- Dependencies: `PyQt6`, `mysql-connector-python`, `cryptography`, `Pillow` (for image sharing), `python-dotenv` +- Check syntax: `python3 -m py_compile .py` +- All files on both server AND client side: `crypto_utils.py`, `protocol.py`, `chat_core.py`, `gui_client.py` (or `client.py`) diff --git a/README.md b/README.md index c21fb9a..c0221cf 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,343 @@ +<<<<<<< HEAD # Kecalek_python +======= +# Encrypted Chat + +End-to-end encrypted chat s forward secrecy (X3DH + Double Ratchet, Signal Protocol). +Server ukládá a přeposílá šifrované bloby — nikdy nevidí plaintext. + +## Soubory + +### Server +| Soubor | Účel | +|--------|------| +| `server.py` | Asyncio TCP server, handler dispatch, rate limiting, notifikace | +| `db.py` | MySQL CRUD, jedna connection na volání | +| `schema.sql` | MySQL schéma (users, conversations, messages, ...) | + +### Klient +| Soubor | Účel | +|--------|------| +| `gui_client.py` | PyQt6 GUI | +| `client.py` | CLI klient | +| `chat_core.py` | Logika klienta — session management, šifrování, lokální klíče | + +### Sdílené (server + klient) +| Soubor | Účel | +|--------|------| +| `crypto_utils.py` | Ed25519, X25519, AES-256-GCM, HKDF, PBKDF2, X3DH, Double Ratchet (state rollback), Sender Keys (state rollback), ECP1 key encryption | +| `protocol.py` | Newline-delimited JSON protokol, base64 encoding | + +## Quick Start + +1. `pip install -r requirements.txt` +2. Spustit `schema.sql` v MySQL (kompletní clean start). Pro migraci existující DB: `migration_multi_device.sql`. +3. `python server.py` +4. Klient: `python client.py` (CLI) nebo `python gui_client.py` (GUI, PyQt6) + +## Jak funguje šifrování + +### Klíče na uživatele +| Klíč | Typ | Účel | +|------|-----|------| +| RSA-4096 | Asymetrický | Pouze login challenge-response. Šifrovaný PBKDF2 (600k iterací) + AES-256-GCM. | +| Identity Key (IK) | Ed25519 | Podpisy, konverze na X25519 pro X3DH. Šifrovaný PBKDF2 (600k iterací) + AES-256-GCM. | +| Signed Pre-Key (SPK) | X25519 | DH v X3DH, podepsaný IK. **Rotuje se každých 7 dní** s grace periodem pro in-flight X3DH. | +| One-Time Pre-Keys (OPK) | X25519 | Jednorázové, spotřebuje se při X3DH, automaticky doplňované (< 20 → +50) | + +### DM (1:1 zprávy) — X3DH + Double Ratchet +1. Alice chce napsat Bobovi poprvé → stáhne jeho key bundle (IK, SPK, OPK) ze serveru. +2. X3DH: 4 DH výpočty → shared secret. +3. Double Ratchet inicializován ze shared secret. +4. Každá zpráva: symmetric ratchet (HMAC chain) → message key → AES-256-GCM. +5. Každá odpověď: DH ratchet (nový X25519 keypair) → nový root key + chain key. +6. Per-recipient ciphertext — každý recipient má vlastní šifrovaný blob. +7. Při selhání dešifrování: automatický rollback stavu ratchetu (snapshot/restore). + +### Skupiny — Sender Keys +1. Každý člen má vlastní sender key chain pro skupinu. +2. Sender key se distribuuje ostatním členům přes pairwise Double Ratchet (jako DM). +3. Skupinové zprávy: symmetric ratchet na sender key → AES-256-GCM. +4. Jeden ciphertext pro celou skupinu (efektivní). + +### Lokální úložiště klíčů +``` +~/.encrypted_chat/{email}/ + private.pem # RSA (login) — ECP1 formát s heslem, PEM bez hesla + public.pem # RSA (login) + identity_private.bin # Ed25519 — ECP1 formát s heslem, 32B raw bez hesla + identity_public.bin # Ed25519 + device_id.txt # UUID tohoto zařízení + spk_private.bin # Aktuální signed prekey (šifrovaný AES-256-GCM) + spk_id.txt + prev_spk_private.bin # Předchozí SPK, grace period (šifrovaný AES-256-GCM) + prev_spk_id.txt + opk_private/ # One-time prekeys (šifrované AES-256-GCM) + {opk_id}.bin + login_lockout.json # Brute-force lockout stav (failed_attempts, locked_until) + sessions/ # Double Ratchet stavy (šifrované AES-256-GCM) + {user_id}_{device_id}.bin + sender_keys/ # Vlastní sender keys pro skupiny + {conv_id}.bin + sender_keys_recv/ # Přijaté sender keys od ostatních + {conv_id}_{sender_id}_{device_id}.bin +``` + +## Bezpečnostní hardening + +### Šifrování privátních klíčů na disku (ECP1 formát) +RSA a Ed25519 privátní klíče šifrované heslem používají vlastní formát ECP1 (Encrypted Chat PBKDF v1): +- **PBKDF2-HMAC-SHA256** s 600 000 iteracemi (OWASP 2023 doporučuje 480k+) +- **AES-256-GCM** pro šifrování, magic bytes "ECP1" jako AAD +- **Formát:** `ECP1(4B) + salt(16B) + nonce(12B) + ciphertext+tag` +- **Zpětná kompatibilita:** Staré PEM soubory (z `BestAvailableEncryption`) se načtou automaticky a při dalším uložení se přešifrují do ECP1. + +### Šifrování SPK/OPK na disku +SPK a OPK privátní klíče jsou šifrované AES-256-GCM klíčem `_local_key` (HKDF z Ed25519 identity key): +- Při save: `_encrypt_local(raw, local_key)` → `nonce(12B) + tag(16B) + ciphertext` +- Při load: `_decrypt_local()` s transparentní migrací — pokud dešifrování selže, načte jako plaintext a uloží šifrovaně +- Aplikováno na `spk_private.bin`, `prev_spk_private.bin`, `opk_private/*.bin` + +### Brute-force ochrana (client-side lockout) +Po chybném zadání hesla se prodlužuje čas do dalšího pokusu: +- **Vzorec:** `min(2^N, 300)` sekund, kde N = počet neúspěšných pokusů (2s, 4s, 8s, ... až 5 min) +- **Stav:** `login_lockout.json` v adresáři klíčů (`failed_attempts`, `locked_until`) +- **Aplikováno na:** `ChatClient.login()` (síťový login) + GUI privacy overlay unlock (`_on_unlock_attempt`) +- **Reset:** Úspěšné přihlášení smaže lockout soubor +- **Defense-in-depth:** Smazání souboru resetuje počítadlo, ale PBKDF2-600k stále zpomaluje každý pokus (~0.5s/pokus) + +### SPK rotace (7 dní) +Signed Pre-Key se rotuje periodicky: +- Po přihlášení `_ensure_prekeys()` zjistí stáří SPK ze serveru (`spk_created_at`) +- Pokud je SPK starší než 7 dní → vygeneruje nový, starý uloží jako grace period +- **Grace period:** `prev_spk_private.bin` — pokud příchozí X3DH selže s aktuálním SPK, zkusí předchozí +- Omezuje dopad kompromitace SPK — útočník může vytvářet nové sessions max 7 dní + +### Odolnost ratchetu (state rollback) +Double Ratchet i Sender Keys automaticky rollbackují stav při selhání dešifrování: +- Před modifikací chain keys/counters se vytvoří snapshot +- Pokud AES-GCM dešifrování selže (corrupted data, wrong key), stav se obnoví +- Session zůstane funkční i po zpracování poškozené zprávy + +## Registrace + +1. `register` → server pošle 6-místný kód na email (nebo vrátí přímo v dev módu bez SMTP). +2. `register_confirm` → potvrzení kódu. +3. Automaticky se vygenerují a uploadnou prekeys (1 SPK + 50 OPKs). +4. Login. + +## Multi-Device Support + +Pravý multi-device (Signal-like) — každé zařízení má nezávislé Double Ratchet sessions. +Při posílání DM se zpráva šifruje zvlášť pro každé zařízení příjemce. +Všechna zařízení uživatele sdílejí Ed25519 identity key (pro self-encryption kompatibilitu). + +### Architektura +- **Devices tabulka** — každé přihlášení registruje device (UUID), server mapuje writer→device +- **Per-device prekeys** — každé zařízení má vlastní SPK + OPKs, server vrací `device_bundles` pole +- **Per-device sessions** — sessions klíčované `"user_id:device_id"`, nezávislé Double Ratchet instance +- **Self-encryption** — odesílatel šifruje vlastní kopii statickým klíčem z identity key (čitelné všemi vlastními zařízeními) +- **Notifikace** — `device_entries` pole, klient vybere záznam odpovídající svému device_id + +### Device Pairing (zjednodušený) + +Nové zařízení získá RSA + Ed25519 identity klíče od existujícího zařízení. +Přenos šifrovaný RSA-OAEP + AES-GCM přes server (server nevidí klíče). +Nové zařízení si po přihlášení automaticky vygeneruje vlastní SPK + OPKs. + +1. Nové zařízení: `Link Device` → dostane 8-místný kód. +2. Existující zařízení: `Authorize Device` → zadá kód → odešle RSA + identity klíče. +3. Nové zařízení importuje klíče, přihlásí se, vygeneruje vlastní prekeys. + +### Migrace +- Existující DB: spustit `migration_multi_device.sql` (nebo `migration_multi_device_resume.sql` pro idempotentní re-run) +- Čistá DB: `schema.sql` již obsahuje všechny multi-device sloupce + +## Device Revocation (Key Rotation) + +Rotuje RSA login klíč. Odpojí ostatní sessions. Forward secrecy zajišťuje, že kompromitace +jednoho session klíče neodhalí historii — není potřeba re-encryption. + +## Konfigurace + +### Server + DB +- `SERVER_HOST` (default `127.0.0.1`), `SERVER_PORT` (default `9999`) +- `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_USER`, `MYSQL_PASSWORD`, `MYSQL_DATABASE` + +### TLS +- `TLS_ENABLED` — zapne TLS (default `false`) +- `TLS_REQUIRED` — vyžaduje TLS_ENABLED, jinak server odmítne start +- `TLS_CERT_FILE`, `TLS_KEY_FILE` — cesty k certifikátu a privátnímu klíči (PEM) +- `TLS_AUTOGEN` — auto-generuje self-signed cert (**jen s `ENVIRONMENT=dev`**) +- `TLS_CA_FILE` (klient) — vlastní CA certifikát pro ověření serveru +- `TLS_INSECURE` (klient) — vypne ověření certifikátu (**jen s `ENVIRONMENT=dev`**) +- `ENVIRONMENT` — `dev`/`development` povolí TLS_INSECURE a TLS_AUTOGEN + +#### Produkční nasazení s Let's Encrypt +```bash +# 1. Nainstalovat certbot +sudo apt install certbot + +# 2. Získat certifikát (port 80 musí být volný pro ověření) +sudo certbot certonly --standalone -d chat.example.com + +# 3. V .env nastavit: +TLS_ENABLED=true +TLS_CERT_FILE=/etc/letsencrypt/live/chat.example.com/fullchain.pem +TLS_KEY_FILE=/etc/letsencrypt/live/chat.example.com/privkey.pem + +# 4. Klient — stačí zapnout TLS (Let's Encrypt je v systémovém trust store): +TLS_ENABLED=true +``` +Certifikát funguje na jakémkoliv portu (9999, 443, ...) — je vázaný na doménu, ne port. Certbot automaticky obnovuje certifikát každých 90 dní. + +#### Dev/testování (self-signed) +```bash +ENVIRONMENT=dev +TLS_ENABLED=true +TLS_AUTOGEN=true # server auto-generuje self-signed cert +TLS_INSECURE=true # klient přeskočí ověření certifikátu +``` + +### SMTP +- `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM` +- Bez SMTP = dev mód (kód se vrací přímo klientovi). + +### Obrázky +- `UPLOAD_DIR` (default `uploads`), `MAX_IMAGE_BYTES` (default 5 MB, `0` = bez limitu) + +### Limity +- `MAX_MESSAGE_BYTES` (default `65536`), `MAX_INPUT_CHARS` (GUI, default `2000`) +- Rate limity: register 3/min, login 10/min, send_message 20/min, pairing_poll 10/min +- Connection: 20 req/s per connection, max 10 per IP, 200 global +- Pairing TTL: 120s, max 5 failed poll pokusů + +### Logging +- `LOG_LEVEL` (default `INFO`) + +## Features + +- Registrace (2-step, SMTP), login (RSA challenge-response), key rotation +- **Multi-device** — per-device sessions (Signal-like), device pairing (RSA + identity key transfer), automatické prekey generování na novém zařízení +- DM s forward secrecy (X3DH + Double Ratchet) — per-device šifrování +- Skupiny se Sender Keys (distribuované přes pairwise ratchet) +- Skupinové pozvánky — přidání do skupiny vyžaduje souhlas (accept/decline) +- Odpovědi na zprávy (reply_to) +- Mazání zpráv (soft-delete pro všechny, real-time notifikace) +- Mazání konverzací (pravý klik → smaže pro uživatele, pokud nezbývají členové smaže celou konverzaci) +- Šifrované obrázky (AES-256-GCM, chunked upload, thumbnail v bublině) +- Šifrované soubory (PDF, ZIP, atd. až 50 MB, chunked upload) +- Read receipts (real-time, client-side resoluce) +- Prekey replenishment (automatické doplňování OPKs po loginu + SPK rotace každých 7 dní) +- Silné šifrování klíčů na disku (PBKDF2 600k iterací + AES-256-GCM, ECP1 formát) +- Odolný ratchet — automatický rollback stavu při selhání dešifrování +- TLS (volitelný, auto-gen self-signed) +- Real-time notifikace konverzací — nové konverzace, přidání/odebrání členů se zobrazí okamžitě bez re-loginu +- Connection state indicator — zelená/červená/oranžová tečka, automatický reconnect s exponential backoff +- Online/offline status — zelená tečka na avataru v seznamu konverzací + v group info +- User profily — telefon, lokace, avatar, nastavení viditelnosti (email, telefon, lokace) +- Phantom users — anti user-enumeration: konverzace s neregistrovaným emailem funguje normálně (odesílatel vidí své zprávy), zprávy pro phantom příjemce se neukládají, phantom se smaže při skutečné registraci +- Clickable links — HTTPS modré, HTTP oranžové s ikonou zámku + potvrzovací dialog + +### GUI (PyQt6) +- Dark theme (Catppuccin Mocha) +- Seznam konverzací s kulatými avatary a online indikátorem (zelená tečka) +- Unread count badge na konverzacích (číselný počet nepřečtených zpráv) +- Message bubliny s barevným left border, timestamp vedle jména +- Read receipts (checkmarks), group info dialog, add/remove member +- Context menu: reply, delete, view image, download file +- Attach button pro obrázky a soubory, thumbnail v bublině, full-size viewer + save +- Pagination ("Load older messages") +- Connection indicator (zelená=online, červená=offline, oranžová=reconnecting) +- Auto-reconnect s exponential backoff (1s → 2s → 4s → ... → max 30s) +- Tlačítko "My Profile" — editace vlastního profilu (telefon, lokace, avatar, viditelnost) +- User profil dialog — klik na info tlačítko v group info → read-only profil uživatele +- Avatar upload/download (JPEG/PNG, max 2 MB, kruhový výřez) +- Leave group (červené tlačítko v group info, přenos creatora) +- Pozvánky do skupin — seznam pending pozvánek nad konverzacemi, pravý klik → accept/decline +- Periodický refresh avatarů a pozvánek (každé 2 minuty) + +### CLI +- Základní funkcionalita (DM, skupiny, šifrování). Profily a soubory pouze přes GUI. + +## Závislosti + +- `cryptography` — Ed25519, X25519, AES-GCM, RSA, HKDF, PBKDF2 +- `mysql-connector-python` — MySQL +- `python-dotenv` — env vars +- `PyQt6` — GUI +- `Pillow` — resize/thumbnail obrázků + +## Known Issues + +- Sender Keys pro skupiny se nedistribuují znovu při přidání nového člena (nový člen neuvidí staré skupinové zprávy). + +## TODO + +### Security — Zbývající +- [ ] **H9: Self-encryption key** — statický/deterministický klíč (by-design pro cross-device, architektonické omezení) +- [ ] M1: Nekonzistentní Ed25519 serializace (částečně vyřešeno M3 — ECP1 formát, ale 3 legacy formáty) +- [ ] M6: TOCTOU race v membership checks +- [ ] M7: MySQL spojení bez TLS +- [ ] L1-L8: Low-priority hardening +- [ ] **Penetrační testy** — manuální + automatizované + +### Features — High Priority +- [ ] Redistribuce sender keys při přidání nového člena do skupiny +- [ ] Typing indicators + +### Features — Medium Priority +- [ ] Hledání zpráv v konverzacích +- [ ] Group admin roles (více adminů) +- [ ] Edit sent messages + +### Features — Low Priority +- [ ] Dark/light theme toggle +- [ ] Desktop notifications (system tray) +- [ ] Database connection pooling +- [ ] Image gallery view +- [ ] Systemd + Docker deployment + +### Monetizace +Oddělený platební server (Stripe, KYC/AML compliant) od chat serveru (anonymní). Platba → premium kód → aktivace na chat serveru. Žádný přímý link platba↔chat identita. + +- **Free tier:** 5 konverzací, 10 MB soubory, 1 zařízení, 30 dní retence, max 10 členů/skupina +- **Premium:** neomezeno — aktivace jednorázovým kódem z platebního serveru +- **Enterprise:** self-hosted, LDAP/SSO, admin dashboard, SLA — faktura na firmu +- Detaily implementace viz `CLAUDE.md` + +### Hotovo — Security +- [x] **C1-C6: Všechny CRITICAL opraveny** — readuntil DoS, sender key fast-forward, OPK permissions, upload size check, path traversal (UUID validace + is_relative_to) +- [x] **H1-H8, H10-H14: Většina HIGH opravena** — lokální šifrování dat (AES-256-GCM), TLS hardening (INSECURE/AUTOGEN jen v dev), anti-enumeration, race conditions (asyncio.Lock), protokol error handling, avatar path traversal, hesla v paměti (bytearray+zero), image validace, filename sanitizace, OPK race condition (SELECT FOR UPDATE) +- [x] **M2-M5+M8-M13: Většina MEDIUM opravena** — HKDF salt, PBKDF2 600k iterací (ECP1 formát), SPK rotace 7 dní s grace periodem, rate limit cleanup, UUID validace, ratchet state rollback, message_ids cap, pairing poll token, upload check, chmod 0o700/0o600 +- [x] **SPK/OPK šifrování + brute-force lockout** — všechny privátní klíče na disku šifrované (ECP1 nebo AES-256-GCM), exponenciální backoff po chybném hesle (2^N s, max 5 min) + +### Hotovo — Features +- [x] **Multi-device support** — per-device sessions (Signal-like), device pairing, automatické prekey generování +- [x] Unread counts pro offline uživatele +- [x] Clickable HTTP links — HTTPS modré, HTTP oranžové s varováním +- [x] User profily (telefon, lokace, avatar, viditelnost) +- [x] Connection state indicator + auto-reconnect +- [x] Encrypted file sharing (až 50 MB) +- [x] Leave group + přenos creatora +- [x] Unread count badge +- [x] User avatars (upload/download, kruhový výřez) +- [x] Online/offline status (zelená tečka na avataru) +- [x] Mazání konverzací +- [x] Skupinové pozvánky (accept/decline) +- [x] Graceful server shutdown + +## Bezpečnostní audit + +Dva bezpečnostní audity provedeny (kód review). Nalezeno 6 CRITICAL, 12 HIGH, 12 MEDIUM, 8 LOW nálezů. + +| Závažnost | Celkem | Opraveno | Zbývá | +|-----------|--------|----------|-------| +| CRITICAL | 6 | **6** | 0 | +| HIGH | 12 | **11** | 1 (H9 — by-design) | +| MEDIUM | 12 | **10** | 2 (M1 částečně, M6, M7) | +| LOW | 8 | 0 | 8 | + +Detaily viz `CLAUDE.md`. +>>>>>>> d506e65 (initial commit) diff --git a/SECURITY_AUDIT.md b/SECURITY_AUDIT.md new file mode 100644 index 0000000..3afb064 --- /dev/null +++ b/SECURITY_AUDIT.md @@ -0,0 +1,363 @@ +# Security Audit (Encrypted Chat) + +Aktualizace: 2026-03-08 +Scope: `server.py`, `db.py`, `chat_core.py`, `gui_client.py`, `client.py`, `protocol.py`, `schema.sql`, `.env`, markdown dokumentace. + +Metodika: statický audit kódu + konfigurace. Nebyl proveden aktivní penetrační test ani fuzzing. + +## Executive Summary + +Nejzávažnější aktuálně otevřené nálezy: + +- Plaintext DB heslo v `.env` souborech (C3). +- Chybějící TLS mezi aplikací a MySQL (H1). +- Slabá oprávnění upload/avatary souborů na disku (H2). +- DoS přes neomezené `pending_registrations` (H6). + +## CRITICAL + +### ~~C1. TOFU / verifikace identity klíče se obchází při běžném X3DH flow~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- TOFU kontrola existuje jen v `_get_user_info()` (`chat_core.py:799-803`). +- Při navazování session (`_get_or_create_session`) se `identity_key` z bundle bere přímo bez TOFU kontroly (`chat_core.py:1497-1501`, `chat_core.py:1534-1538`). +- U příchozího X3DH (`_process_x3dh_header`) se remote IK také uloží bez TOFU kontroly (`chat_core.py:1551-1553`, `chat_core.py:1580-1584`). + +**Dopad** + +- Pokud server nebo MITM podstrčí jiný identity key, klient může navázat session bez varování. +- Prakticky to obchází uživatelskou verifikaci kontaktu ve výchozím messaging flow. + +**Oprava** + +- Nová výjimka `IdentityKeyChanged(user_id, new_key_bytes, status)` v `chat_core.py` — hard-fail při změně identity klíče. +- `_get_or_create_session()`: TOFU check přes `check_identity_key()` před X3DH initiate. Při `changed`/`changed_verified` vyhodí `IdentityKeyChanged` — session se nenaváže. +- `_process_x3dh_header()`: TOFU check před X3DH respond. Stejný hard-fail — příchozí zpráva s podvrženým klíčem je odmítnuta. +- GUI: `IdentityKeyChanged` zachycena v notification loopu (emituje `key_change_warning` signál místo pádu loopu) a v `_do_send_message` (zobrazí error + warning dialog). +- Session je blokována dokud uživatel explicitně neakceptuje key change přes `accept_key_change()`. +- `decrypt_notification()`: explicitní `except IdentityKeyChanged: raise` před generickým `except Exception` — výjimka se propaguje do notification loopu místo tichého spolknutí. +- `key_change_warning` signál rozšířen o 5. parametr `new_key_bytes: bytes` — "Accept New Key" dialog předává nový klíč přímo z výjimky, ne z cache (která mohla obsahovat starý klíč). +- `IdentityKeyChanged` ošetřena ve všech GUI send cestách: `_do_send_image`, `_do_send_file`, `_do_forward_message`, `_do_find_or_create_and_send` — zobrazí warning dialog + error message. +- CLI (`client.py`): `IdentityKeyChanged` ošetřena ve všech 6 send cestách (send_message ×3, send_image, send_file, forward_message). + +--- + +### ~~C2. Perzistentní DoS konverzace přes nevalidní message headers~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- Server přijímá `ratchet_header` / `x3dh_header` i jako raw `str/bytes` bez JSON schema validace (`server.py:1105-1112`, `server.py:1146-1151`). +- Při `get_messages` se hodnoty bez ochrany parsují `json.loads(...)` (`server.py:1266`, `server.py:1274`). + +**Dopad** + +- Útočník v konverzaci může uložit “poisoned” hlavičku a rozbít načtení historie ostatním členům (`Internal server error`). +- Chyba je perzistentní, dokud je vadná zpráva v historii. + +**Oprava** + +- Nový helper `_validate_header(raw, name)` v `server.py` — přijímá pouze `dict`, odmítá `str`/`bytes`, limit 4096 bajtů. +- `handle_send_message`: message-level i per-recipient headers procházejí `_validate_header()`. Nevalidní hlavička → error response, zpráva se neuloží. +- `handle_get_messages`: `json.loads()` obaleno `try/except` (JSONDecodeError, TypeError, UnicodeDecodeError). Corrupted header → prázdný dict `{}` + warning log, ostatní zprávy se načtou normálně. +- `_validate_header()` rozšířena o validaci očekávaných klíčů a typů pro ratchet headers (`dh_pub`: str, `n`: int, `pn`: int) a používá striktní kontrolu typu pro `n/pn` (`type(...) is int`) — `bool` je explicitně odmítnut. +- Realtime push notifikace nyní čtou data z validovaných `db_recipients` (ne z `recipients_raw`). Per-recipient hlavičky se dekódují z validovaných bytes zpět do `dict` pro JSON notifikaci. +- `encrypted_content` a `nonce` v push notifikacích se skládají z validovaných raw bytes a serializují se přes `encode_binary()` — untrusted hodnoty z raw requestu se do push větve nepropíší. + +--- + +### C3. Plaintext tajemství v `.env` a `zaloha/.env` + +**Evidence** + +- `.env` obsahuje `MYSQL_PASSWORD` (`.env:4`). +- `zaloha/.env` obsahuje `MYSQL_PASSWORD` (`zaloha/.env:4`). + +**Dopad** + +- Únik souboru = okamžitý přístup do DB. +- Riziko přes backupy, sdílení projektu, malware, CI artefakty. + +**Doporučení** + +1. Okamžitě rotovat DB heslo. +2. Nahradit repozitářové `.env` šablonou (`.env.example`) bez tajemství. +3. Použít secrets manager / deployment-level secret injection. + +## HIGH + +### H1. Chybí TLS mezi aplikací a MySQL + +**Evidence** + +- `MySQLConnectionPool` je bez `ssl_ca`/`ssl_verify_cert` parametrů (`db.py:35-44`). +- Konfigurace používá síťovou DB (`MYSQL_HOST=192.168.1.112`, `.env:1`). + +**Dopad** + +- Odposlech nebo MITM na trase app<->DB může odhalit credentials i data. + +**Doporučení** + +1. Zapnout MySQL TLS na serveru. +2. Vynutit TLS verifikaci certifikátu v `db.py`. + +--- + +### H2. Upload/avatary na disku mají slabá oprávnění + +**Evidence** + +- Upload soubory jsou vytvářeny bez explicitního `chmod` na file (`server.py:1732`, `server.py:1806`, `server.py:1909`, `server.py:1969`). +- V prostředí auditu: `uploads` a `uploads/avatars` mají `775`, soubory typicky `664`. + +**Dopad** + +- Lokální uživatelé na stejném hostu mohou číst citlivá data (včetně avatarů v plaintextu). + +**Doporučení** + +1. Nastavit adresáře `0700`. +2. Po zápisu každého souboru nastavit `0600`. +3. Upload storage přesunout mimo project tree. + +--- + +### ~~H3. `session_reset` nemá autorizační vazbu na vztah mezi uživateli~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- Handler přijme libovolné validní `peer_user_id` a pošle notifikaci (`server.py:2040-2052`). +- Neověřuje, že uživatelé sdílí konverzaci nebo existuje session. + +**Dopad** + +- Možnost spam/DoS reset notifikací na cílové uživatele. + +**Oprava** + +- Nová DB funkce `db.shares_conversation(user_id_a, user_id_b)` — `SELECT 1 ... LIMIT 1` přes `conversation_members` JOIN. +- `handle_session_reset`: před push notifikací ověřuje `shares_conversation()`. Pokud uživatelé nesdílí žádnou konverzaci → error response. +- Rate limit 5 požadavků/min na `session_reset` per user (`session_reset|{user_id}`) — IP adresa není součást klíče, takže změnou IP nejde limit obejít. +- Pokud je předán `peer_device_id`, reset notifikace se doručí pouze cílovému zařízení (filtr přes `writer_device_map`). Bez `peer_device_id` zůstává broadcast na všechna zařízení peera. + +--- + +### ~~H4. User enumeration přes pairing a user-info endpointy~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- `pairing_start` vrací explicitně `User not found` (`server.py:763-766`). +- `get_user_info` vrací metadata uživatele při lookupu přes email/user_id (`server.py:551-564`). + +**Dopad** + +- Snadné mapování existence účtů. + +**Oprava** + +- `handle_pairing_start`: vždy vrací `ok` s platně vypadajícím kódem a session se vytvoří vždy (i pro neexistující email), takže `pairing_poll` vrací nerozlišitelné `ready: false`. +- Přidán globální cap `PAIRING_MAX_SESSIONS = 100` pro omezení počtu současných pairing sessions (DoS hardening). +- `pairing_start` rate limit je per-IP (bez email komponenty), aby nešel obcházet rotací emailů. +- `pairing_claim` i `pairing_send`: sjednocená chyba `Invalid or expired code` (žádné rozlišení "neexistuje" vs "patří jinému účtu"). +- V pairing flow se síťové I/O (`send_resp`) volá až po uvolnění `_pairing_lock`. +- `handle_get_user_info`: přidán parametr `session` (vyžaduje login). Lookupovat lze jen sebe nebo kontakty (ověřeno přes `shares_conversation()`). Pro neexistující i nepovolené cíle vrací neutrální "User not found". +- Doplňuje dřívější anti-enumeration opravy: `register_start` (generická odpověď), `login_start` (fake challenge), `login_finish` (generická chyba). + +--- + +### ~~H5. Phantom user inflation přes `create_conversation` / `find_conversation` / `add_member` (DoS)~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- `create_conversation` vytváří phantom účty pro neznámé emaily bez dedikovaného rate limitu (`server.py:906-920`). +- `find_conversation` a `add_member` rate limitují přes `_rate_limit_key(..., addr, email)`, takže rotace emailů obchází limit (`server.py:972`, `server.py:1001`, `server.py:209-212`). +- `create_phantom_user()` pro každý nový email generuje IK+SPK+OTP a zapisuje více řádků do DB (`db.py:1470-1507`). + +**Dopad** + +- Útočník může nafukovat DB a CPU náklady (kryptografická generace + zápisy), případně degradovat výkon serveru. + +**Oprava** + +1. `_can_create_phantom(addr, user_id)` helper kontroluje 3 limity před každým `create_phantom_user()`: + - Globální cap: `MAX_PHANTOM_USERS = 500` (počet v `phantom_user_ids` setu) + - Per-user rate: `phantom_create|{user_id}` — 10/min (email-nezávislé, neobejitelné rotací) + - Per-IP rate: `phantom_create_ip|{addr}` — 10/min (email-nezávislé) +2. `create_conversation` nově má per-user rate limit 10/min + phantom check před každým členem. +3. `find_conversation` a `add_member` — existující per-addr+email limit zůstává (brání hammering jednoho emailu), přidán `_can_create_phantom` check před vytvořením phantomu. +4. Stávající `cleanup_stale_phantoms(30)` v periodic cleanup (10 min) zajišťuje garbage collection. + +--- + +### H6. `pending_registrations` nemá hard cap (memory/SMTP abuse) + +**Evidence** + +- `pending_registrations` je globální in-memory dict bez horního limitu (`server.py:56`). +- `register_start` používá rate limit klíč s emailem (`register_start|addr|email`), rotace emailů limit obchází (`server.py:341`, `server.py:209-212`). +- `register_start` ukládá novou pending registraci do dict bez capu (`server.py:373-382`). +- Periodický cleanup nevolá `_cleanup_registrations()`; expirace se spouští jen při `register_*` flow (`server.py:2486-2508`, `server.py:276-281`). + +**Dopad** + +- Riziko růstu paměti a SMTP abuse (masivní register_start s různými emaily). + +**Doporučení** + +1. Přidat `REGISTER_MAX_PENDING` cap a odmítnout nové requesty po dosažení limitu. +2. Změnit rate limit na per-IP (bez emailu) + případně per-subnet. +3. Přidat `_cleanup_registrations()` i do periodického cleanup tasku. + +## MEDIUM + +### ~~M1. `mark_read` a `confirm_delivery` neověřují, že `message_ids` patří do dané konverzace~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- Handler validuje členství jen v `conversation_id` (`server.py:1464-1479`, `server.py:1516-1531`). +- DB insert metody pro receipts neváží `message_id` na konverzaci (`db.py:1102-1113`, `db.py:1188-1200`). + +**Dopad** + +- Možná manipulace read/delivery stavu cizích zpráv (integrita metadat). + +**Oprava** + +- `db.mark_messages_read()` a `db.mark_messages_delivered()` nahrazeny z per-row `INSERT IGNORE` na batch `INSERT IGNORE ... SELECT m.id, %s FROM messages m WHERE m.id IN (...) AND m.conversation_id = %s`. +- Message IDs, které nepatří do dané konverzace, jsou tiše přeskočeny (SELECT je nevrátí). + +--- + +### M2. SMTP STARTTLS bez explicitního TLS contextu + +**Evidence** + +- `server.starttls()` je voláno bez `ssl.create_default_context()` (`server.py:290`). + +**Dopad** + +- Slabší kontrola TLS parametrů/verifikace dle runtime prostředí. + +**Doporučení** + +1. Použít `server.starttls(context=ssl.create_default_context())`. +2. Přidat `EHLO` před/po STARTTLS. + +--- + +### ~~M3. CLI klient: několik lokálních hardening mezer~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- Heslo se zadává přes `input()` (echo on) (`client.py:730`, `client.py:749`, `client.py:754`). +- Zprávy se tisknou bez sanitace escape sekvencí (`client.py:491`). +- Default save path při downloadu je převzat z remote `filename` (`client.py:523-530`). + +**Dopad** + +- Shoulder-surfing hesla, terminal escape spoofing, riskantní defaultní save path. + +**Oprava** + +- Všechny password prompty (register, login, pairing, authorize device, rotate keys) nahrazeny `prompt_password()` wrapping `getpass.getpass()` — heslo se nezobrazuje na terminálu. +- `_sanitize_text()` helper stripuje control znaky (`\x00-\x1f` kromě `\t`/`\n`/`\r`) a ANSI escape sekvence. Aplikováno na `sender`, `text`, `filename` při výpisu zpráv v `_print_messages()`. +- Follow-up: `_sanitize_text()` nyní bezpečně přijímá i non-string vstupy (`None -> ""`, jinak `str(...)`), čímž se eliminuje `TypeError` při neočekávaném typu z payloadu (`client.py:32-36`). +- Follow-up: sanitace rozšířena na zbývající user-controlled CLI výpisy — seznam konverzací (`client.py:63-67`), search výsledky (`client.py:293-300`), seznam pozvánek (`client.py:612-614`), profil (`client.py:637-644`), seznam zařízení (`client.py:709-713`), verify view (`client.py:435`, `client.py:446`) a notifikace včetně reaction hodnoty (`client.py:752-774`). +- `_safe_filename()` helper: `os.path.basename()` + odstranění NUL + fallback na `"download"` pro prázdné/tečkové názvy. Aplikováno na default save path při downloadu. + +--- + +### ~~M4. `get_key_bundle` umožňuje OPK depletion (availability)~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- `handle_get_key_bundle` nemá rate limit ani authorizační vazbu na vztah mezi uživateli (`server.py:648-660`). +- DB vrstva při každém volání spotřebovává one-time prekeys (`get_key_bundles_for_user` — „Consumes one OPK per device atomically”, `db.py:394-450`). +- `target_user_id` lze získat přes `find_conversation` lookup (`server.py:966-987`). + +**Dopad** + +- Útočník může opakovanými dotazy vyčerpat OPK oběti, zhoršit doručitelnost a vynutit časté doplňování prekeys. + +**Oprava** + +1. Per-caller rate limit: `get_key_bundle|{user_id}` — 10/min (omezuje celkový počet fetchů jednoho uživatele). +2. Per-target rate limit: `get_key_bundle_target|{target_user_id}` — 20/min (omezuje rychlost vyčerpávání OPK konkrétní oběti). Autorizace probíhá před per-target RL (neautorizovaný request nespálí bucket cíle). +3. Autorizace: `shares_conversation()` — caller musí sdílet konverzaci s cílem (self-fetch povolen vždy). +4. Chybová zpráva pro neautorizovaný přístup je neutrální (`”Key bundle not available”`) — shodná s neexistujícím uživatelem. +5. **Doplňující per-user rate limity** na všechny zbývající výpočetně/DB náročné handlery (celkem 29 RL checks): + - Crypto+DB: `upload_prekeys` 5/min, `ensure_prekeys` 5/min, `rotate_keys` 3/min, `reencrypt` 10/min + - DB-heavy: `get_messages` 30/min, `delete_conv` 5/min, `delete_msg` 20/min, `react` 20/min, `remove_member` 10/min, `rename_conv` 5/min + - File I/O: `update_avatar` 5/min (sdílený bucket pro user i group avatar) + +--- + +### ~~M5. `upload_image_start` nemá anti-DoS cap na in-flight uploady~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- `upload_image_start` nevynucuje request rate limit ani limit počtu aktivních uploadů na user/IP (`server.py:1786-1823`). +- In-memory `pending_uploads` je bez explicitního capu (`server.py:58`, `server.py:1812-1819`). +- Cleanup stale uploadů běží periodicky (600s) a DB stale threshold je 3600s (`server.py:2488-2490`, `db.py:1626-1633`). + +**Dopad** + +- Útočník může zahájit mnoho uploadů a vytvářet dočasné soubory/záznamy, což zvyšuje memory/disk tlak. + +**Oprava** + +1. Per-user rate limit: `upload_start|{user_id}` — 10/min. +2. Globální cap: `MAX_UPLOADS_GLOBAL = 200` (kontrola `len(pending_uploads)` pod `_uploads_lock`). +3. Per-user cap: `MAX_UPLOADS_PER_USER = 5` (počet záznamů s `uploader_id == user_id`). +4. Stale threshold snížen z 3600s na `UPLOAD_STALE_SECONDS = 600` (10 min). +5. Periodic cleanup interval snížen z 600s na 120s (2 min). + +## LOW + +### ~~L1. `decode_binary` není strict base64~~ ✅ OPRAVENO (2026-03-08) + +**Evidence** + +- `base64.b64decode(data)` bez `validate=True` (`protocol.py:18`). + +**Dopad** + +- Méně striktní input parsing (robustnost), ne přímý průnik. + +**Oprava** + +- `decode_binary()` nyní volá `base64.b64decode(data, validate=True)` — odmítá neplatné base64 znaky (whitespace, non-alphabet). + +## Positive Findings + +- Dev-only guardy: `TLS_INSECURE` a `TLS_AUTOGEN` jsou blokovány mimo `ENVIRONMENT=dev`. +- Server používá UUID validace v řadě handlerů. +- Upload/download ověřuje členství v konverzaci. +- Klientské private keys/storage používají PBKDF2 + AES-GCM a restriktivní perms (`0700`/`0600`) v key storage. +- Přítomný client-side lockout na opakované chybné login pokusy. + +## Prioritní plán oprav + +### 0-48 hodin + +1. Rotace DB hesla + odstranění tajemství z `.env`. +2. ~~Oprava TOFU bypassu v obou X3DH cestách.~~ ✅ DONE +3. ~~Zablokování nevalidních message headers na vstupu.~~ ✅ DONE +4. Přepnutí upload storage perms na `0700/0600`. +5. ~~Omezit phantom creation (rate limit bez emailu + cap).~~ ✅ DONE +6. Zavést cap pro `pending_registrations` a čistit je i v periodickém cleanupu. +7. ~~Přidat cap/rate limit na in-flight uploady.~~ ✅ DONE + +### 7 dní + +1. Zapnout TLS mezi app a MySQL (mTLS nebo minimálně server cert verify). +2. ~~Opravit autorizaci `session_reset`.~~ ✅ DONE +3. ~~Opravit vazbu `message_ids` na `conversation_id` pro receipts.~~ ✅ DONE +4. Omezit `get_key_bundle` (rate limit + policy sdílené konverzace). + +### 30 dní + +1. ~~Anti-enumeration sjednotit napříč endpointy.~~ ✅ DONE +2. ~~CLI hardening (`getpass`, output sanitace, filename sanitace).~~ ✅ DONE +3. Doplnit integrační testy pro bezpečnostní regresi (TOFU, poisoned headers, receipt authz, session_reset device targeting, anti-enumeration, DoS caps). diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..302a5f5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,22 @@ +# TODO + +## Distributed global cap for phantom users (multi-process safe) + +1. Add DB-backed quota as source of truth (`system_quotas` table, row `phantom_users` with `used` and `limit`). +2. Move cap enforcement into one DB transaction: + - lock quota row with `SELECT ... FOR UPDATE` + - check `used < limit` + - create phantom user + - increment `used` + - commit (or rollback on failure). +3. Handle same-email races using `UNIQUE(email)`: + - on duplicate key, do not increment quota + - return existing user (or unified error response). +4. Add periodic reconciliation job: + - recalculate phantom count from `users` + - repair `system_quotas.used` if drift is detected. +5. Move phantom creation rate-limits to shared backend (Redis or DB atomic counters), so all server processes enforce the same limits. +6. Add concurrency tests: + - multi-process create storm near cap boundary (499/500) + - duplicate-email storm + - assert `used <= limit` always holds. diff --git a/certs/README.md b/certs/README.md new file mode 100644 index 0000000..7c074d6 --- /dev/null +++ b/certs/README.md @@ -0,0 +1,101 @@ +# TLS Setup — Let's Encrypt + Cloudflare DNS + +TLS certifikát přes Let's Encrypt bez nutnosti otevírat port 80. +Ověření domény probíhá přes DNS TXT záznam (Cloudflare API). + +## Předpoklady + +- Doména s DNS na Cloudflare (free tier stačí) +- Cloudflare API token s oprávněním "Edit zone DNS" +- Root přístup na serveru (certbot potřebuje `/etc/letsencrypt/`) + +## Postup + +### 1. Cloudflare API token + +1. Jdi na https://dash.cloudflare.com/profile/api-tokens +2. **Create Token** → Use template **"Edit zone DNS"** +3. Zone Resources → vybrat svou doménu +4. Zkopíruj vygenerovaný token + +### 2. Credentials soubor + +```bash +cp cloudflare.ini.example cloudflare.ini +nano cloudflare.ini # vlož API token +chmod 600 cloudflare.ini +``` + +### 3. Získání certifikátu + +```bash +sudo ./setup-tls.sh chat.example.com +``` + +Skript nainstaluje certbot + Cloudflare plugin, získá certifikát a vytvoří symlinky v tomto adresáři. + +### 4. Konfigurace serveru + +Přidej do `.env` v kořenovém adresáři projektu: + +```env +TLS_ENABLED=true +TLS_CERT_FILE=/etc/letsencrypt/live/chat.example.com/fullchain.pem +TLS_KEY_FILE=/etc/letsencrypt/live/chat.example.com/privkey.pem +``` + +### 5. Konfigurace klienta + +Na klientovi stačí: + +```env +TLS_ENABLED=true +``` + +Let's Encrypt je v systémovém trust store — klient ověří certifikát automaticky. + +## Obnova certifikátu + +Certbot obnovuje certifikát automaticky přes systemd timer (každých ~60 dní, cert platí 90). + +```bash +# Ověřit že timer běží +systemctl status certbot.timer + +# Ruční obnova (test) +sudo certbot renew --dry-run +``` + +Po úspěšné obnově se spustí `reload-server.sh` (deploy hook) — restartuje chat server aby načetl nový certifikát. + +## Soubory + +| Soubor | Účel | +|--------|------| +| `setup-tls.sh` | Instalace certbot + získání certifikátu | +| `reload-server.sh` | Deploy hook — restartuje server po renew | +| `cloudflare.ini.example` | Šablona pro Cloudflare API token | +| `cloudflare.ini` | Tvůj API token (gitignored) | + +## FAQ + +**Funguje certifikát na nestandardním portu (např. 9999)?** +Ano. Certifikát je vázaný na doménu, ne na port. `chat.example.com:9999` funguje. + +**Musím otevírat port 80?** +Ne. DNS challenge ověřuje doménu přes DNS TXT záznam, žádný HTTP požadavek na server. + +**Co když nemám Cloudflare?** +Můžeš použít ruční DNS challenge (bez automatického renew): +```bash +sudo certbot certonly --manual --preferred-challenges dns -d chat.example.com +``` +Certbot ti řekne jaký TXT záznam přidat. Při renew to musíš opakovat ručně. + +**Dev/testování bez certifikátu?** +```env +ENVIRONMENT=dev +TLS_ENABLED=true +TLS_AUTOGEN=true # server vygeneruje self-signed cert +TLS_INSECURE=true # klient přeskočí ověření +``` diff --git a/certs/cloudflare.ini.example b/certs/cloudflare.ini.example new file mode 100644 index 0000000..2fd8c67 --- /dev/null +++ b/certs/cloudflare.ini.example @@ -0,0 +1,11 @@ +# Cloudflare API token pro certbot DNS challenge +# 1. Jdi na https://dash.cloudflare.com/profile/api-tokens +# 2. Create Token -> Edit zone DNS (template) +# 3. Zone Resources: vybrat svou doménu +# 4. Zkopírovat token sem +# +# Po vyplnění přejmenuj na cloudflare.ini a nastav práva: +# cp cloudflare.ini.example cloudflare.ini +# chmod 600 cloudflare.ini + +dns_cloudflare_api_token = VLOZ_SVUJ_CLOUDFLARE_API_TOKEN diff --git a/certs/reload-server.sh b/certs/reload-server.sh new file mode 100755 index 0000000..abc7449 --- /dev/null +++ b/certs/reload-server.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Deploy hook — spustí se automaticky po úspěšném renew certifikátu +# Certbot volá tento skript s RENEWED_LINEAGE a RENEWED_DOMAINS env vars +# +# Restartuje chat server aby načetl nový certifikát. +# Přizpůsob podle toho jak server spouštíš (systemd / screen / přímý proces). + +set -euo pipefail + +echo "Certifikát obnoven pro: ${RENEWED_DOMAINS:-unknown}" + +# Varianta 1: Systemd service +if systemctl is-active --quiet encrypted-chat 2>/dev/null; then + systemctl restart encrypted-chat + echo "Server restartován (systemd)." + exit 0 +fi + +# Varianta 2: Poslat SIGINT procesu (graceful shutdown + ruční restart) +PID=$(pgrep -f "python.*server.py" || true) +if [ -n "$PID" ]; then + echo "Posílám SIGINT procesu $PID (server.py)" + kill -INT "$PID" + echo "Server zastaven. Spusť ho znovu ručně nebo přes systemd." + exit 0 +fi + +echo "VAROVÁNÍ: Server proces nenalezen. Restartuj server ručně." diff --git a/certs/setup-tls.sh b/certs/setup-tls.sh new file mode 100755 index 0000000..1c1f7c6 --- /dev/null +++ b/certs/setup-tls.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Setup TLS certifikátu přes Let's Encrypt + Cloudflare DNS challenge +# Nevyžaduje otevřený port 80 — ověření přes DNS TXT záznam +# +# Použití: +# 1. Přesuň DNS domény na Cloudflare (free tier stačí) +# 2. Vytvoř API token: https://dash.cloudflare.com/profile/api-tokens +# -> Use template "Edit zone DNS" -> vybrat doménu +# 3. cp cloudflare.ini.example cloudflare.ini +# Vlož token, chmod 600 cloudflare.ini +# 4. sudo ./setup-tls.sh chat.example.com +# +# Po úspěšném získání certifikátu přidej do .env: +# TLS_ENABLED=true +# TLS_CERT_FILE=/etc/letsencrypt/live/DOMENA/fullchain.pem +# TLS_KEY_FILE=/etc/letsencrypt/live/DOMENA/privkey.pem + +set -euo pipefail + +DOMAIN="${1:-}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CREDS="$SCRIPT_DIR/cloudflare.ini" +DEPLOY_HOOK="$SCRIPT_DIR/reload-server.sh" + +if [ -z "$DOMAIN" ]; then + echo "Použití: sudo $0 " + echo "Příklad: sudo $0 chat.example.com" + exit 1 +fi + +if [ "$EUID" -ne 0 ]; then + echo "Spusť jako root: sudo $0 $DOMAIN" + exit 1 +fi + +if [ ! -f "$CREDS" ]; then + echo "Chybí $CREDS" + echo "Zkopíruj cloudflare.ini.example -> cloudflare.ini a vlož API token." + exit 1 +fi + +# Ověř oprávnění credentials souboru +PERMS=$(stat -c %a "$CREDS" 2>/dev/null || stat -f %Lp "$CREDS" 2>/dev/null) +if [ "$PERMS" != "600" ]; then + echo "VAROVÁNÍ: $CREDS má oprávnění $PERMS, nastavuji 600" + chmod 600 "$CREDS" +fi + +echo "=== Instalace certbot + Cloudflare pluginu ===" +if ! command -v certbot &>/dev/null; then + apt-get update + apt-get install -y certbot python3-certbot-dns-cloudflare + echo "Certbot nainstalován." +else + echo "Certbot již nainstalován." + # Doinstaluj plugin pokud chybí + if ! python3 -c "import certbot_dns_cloudflare" 2>/dev/null; then + apt-get install -y python3-certbot-dns-cloudflare + fi +fi + +echo "" +echo "=== Získání certifikátu pro $DOMAIN ===" +DEPLOY_ARGS="" +if [ -f "$DEPLOY_HOOK" ] && [ -x "$DEPLOY_HOOK" ]; then + DEPLOY_ARGS="--deploy-hook $DEPLOY_HOOK" + echo "Deploy hook: $DEPLOY_HOOK" +fi + +certbot certonly \ + --dns-cloudflare \ + --dns-cloudflare-credentials "$CREDS" \ + --dns-cloudflare-propagation-seconds 30 \ + -d "$DOMAIN" \ + --non-interactive \ + --agree-tos \ + --register-unsafely-without-email \ + $DEPLOY_ARGS + +CERT_DIR="/etc/letsencrypt/live/$DOMAIN" +if [ -d "$CERT_DIR" ]; then + echo "" + echo "=== Certifikát úspěšně získán ===" + echo "" + echo "Soubory:" + echo " Certifikát: $CERT_DIR/fullchain.pem" + echo " Klíč: $CERT_DIR/privkey.pem" + echo "" + echo "Přidej do .env:" + echo " TLS_ENABLED=true" + echo " TLS_CERT_FILE=$CERT_DIR/fullchain.pem" + echo " TLS_KEY_FILE=$CERT_DIR/privkey.pem" + echo "" + echo "Na klientovi stačí:" + echo " TLS_ENABLED=true" + echo "" + echo "Automatický renew: certbot timer (systemd) nebo cron" + echo " systemctl status certbot.timer" + echo "" + + # Symlinky pro snadný přístup + ln -sf "$CERT_DIR/fullchain.pem" "$SCRIPT_DIR/fullchain.pem" + ln -sf "$CERT_DIR/privkey.pem" "$SCRIPT_DIR/privkey.pem" + echo "Symlinky vytvořeny v $SCRIPT_DIR/" +else + echo "CHYBA: Certifikát nebyl vytvořen." + exit 1 +fi diff --git a/chat_core.py b/chat_core.py new file mode 100644 index 0000000..7f19d07 --- /dev/null +++ b/chat_core.py @@ -0,0 +1,3481 @@ +"""Shared network layer and ChatClient class for CLI and GUI clients. + +Uses X3DH + Double Ratchet for message encryption, Sender Keys for groups. +RSA retained for login challenge-response only. +""" + +import asyncio +import hashlib +import json +import logging +import os +import ssl +import time +import uuid +from datetime import datetime, timezone +from pathlib import Path + +from dotenv import load_dotenv + +load_dotenv() + +from crypto_utils import ( + # RSA (login only) + generate_rsa_keypair, + serialize_private_key, + serialize_public_key, + load_private_key, + load_public_key, + rsa_sign, + # Ed25519 + generate_identity_keypair, + serialize_ed25519_private, + serialize_ed25519_private_raw, + serialize_ed25519_public, + load_ed25519_private, + load_ed25519_public, + ed25519_sign, + # X25519 + generate_x25519_keypair, + serialize_x25519_private, + serialize_x25519_public, + load_x25519_private, + load_x25519_public, + # X3DH + generate_signed_prekey, + generate_one_time_prekeys, + x3dh_initiate, + x3dh_respond, + # Double Ratchet + DoubleRatchet, + # Sender Keys + SenderKeyState, + # AES + aes_encrypt, + aes_decrypt, + # Self-encryption + derive_self_encryption_key, + # Local storage encryption + derive_local_storage_key, + # Contact verification + compute_fingerprint, + format_fingerprint, + compute_safety_number, + encode_verification_qr, + decode_verification_qr, + # Message padding + pad_plaintext, + unpad_plaintext, +) +from protocol import ( + VERSION, + ProtocolReader, + ProtocolWriter, + encode_binary, + decode_binary, + build_request, + MAX_MESSAGE_BYTES, + IMAGE_CHUNK_SIZE, +) + + +KEY_DIR = Path.home() / ".encrypted_chat" +OPK_REPLENISH_THRESHOLD = 20 +OPK_BATCH_SIZE = 50 +SPK_ROTATION_DAYS = 7 + + +def _encrypt_local(data: bytes, key: bytes) -> bytes: + """Encrypt data with AES-256-GCM for local storage. Format: nonce(12) + tag(16) + ciphertext.""" + _, nonce, ct, tag = aes_encrypt(data, key=key) + return nonce + tag + ct + + +def _decrypt_local(raw: bytes, key: bytes) -> bytes: + """Decrypt data encrypted by _encrypt_local.""" + nonce, tag, ct = raw[:12], raw[12:28], raw[28:] + return aes_decrypt(key, nonce, ct, tag) + + +def get_key_dir(email: str) -> Path: + d = KEY_DIR / email + d.mkdir(parents=True, exist_ok=True) + os.chmod(d, 0o700) + return d + + +# --------------------------------------------------------------------------- +# RSA key storage (login only — unchanged interface) +# --------------------------------------------------------------------------- + +def save_keys(email: str, private_key, public_key, password: bytes | None = None): + d = get_key_dir(email) + (d / "private.pem").write_bytes(serialize_private_key(private_key, password=password)) + (d / "public.pem").write_bytes(serialize_public_key(public_key)) + os.chmod(d / "private.pem", 0o600) + + +def load_keys(email: str, password: bytes | None = None): + d = get_key_dir(email) + priv_path = d / "private.pem" + pub_path = d / "public.pem" + if not priv_path.exists(): + return None, None, "No local keys found." + pem = priv_path.read_bytes() + try: + private_key = load_private_key(pem, password=password) + except Exception: + try: + private_key = load_private_key(pem, password=None) + if password: + save_keys(email, private_key, load_public_key(pub_path.read_bytes()), password=password) + except Exception: + return None, None, "Invalid or missing password." + public_key = load_public_key(pub_path.read_bytes()) + return private_key, public_key, None + + +# --------------------------------------------------------------------------- +# Identity + prekey storage +# --------------------------------------------------------------------------- + +def _save_identity_keys(email: str, ed_priv, ed_pub, password: bytes | None = None): + d = get_key_dir(email) + if password: + (d / "identity_private.bin").write_bytes(serialize_ed25519_private(ed_priv, password=password)) + else: + (d / "identity_private.bin").write_bytes(serialize_ed25519_private_raw(ed_priv)) + (d / "identity_public.bin").write_bytes(serialize_ed25519_public(ed_pub)) + os.chmod(d / "identity_private.bin", 0o600) + + +def _load_identity_keys(email: str, password: bytes | None = None): + d = get_key_dir(email) + priv_path = d / "identity_private.bin" + pub_path = d / "identity_public.bin" + if not priv_path.exists(): + return None, None + priv = load_ed25519_private(priv_path.read_bytes(), password=password) + pub = load_ed25519_public(pub_path.read_bytes()) + return priv, pub + + +def _save_spk(email: str, spk_priv, spk_id: str, local_key: bytes | None = None): + d = get_key_dir(email) + raw = serialize_x25519_private(spk_priv) + data = _encrypt_local(raw, local_key) if local_key else raw + (d / "spk_private.bin").write_bytes(data) + (d / "spk_id.txt").write_text(spk_id) + os.chmod(d / "spk_private.bin", 0o600) + + +def _load_spk(email: str, local_key: bytes | None = None): + d = get_key_dir(email) + priv_path = d / "spk_private.bin" + id_path = d / "spk_id.txt" + if not priv_path.exists(): + return None, None + raw = priv_path.read_bytes() + if local_key: + try: + raw = _decrypt_local(raw, local_key) + except Exception: + # Plaintext fallback (migration) — re-save encrypted + pass + priv = load_x25519_private(raw) + spk_id = id_path.read_text().strip() if id_path.exists() else "" + if local_key: + _save_spk(email, priv, spk_id, local_key) + return priv, spk_id + + +def _save_prev_spk(email: str, spk_priv, spk_id: str, local_key: bytes | None = None): + """Save previous SPK for grace period (in-flight X3DH may reference old SPK).""" + d = get_key_dir(email) + raw = serialize_x25519_private(spk_priv) + data = _encrypt_local(raw, local_key) if local_key else raw + (d / "prev_spk_private.bin").write_bytes(data) + (d / "prev_spk_id.txt").write_text(spk_id) + os.chmod(d / "prev_spk_private.bin", 0o600) + + +def _load_prev_spk(email: str, local_key: bytes | None = None): + """Load previous SPK (grace period). Returns (private_key, spk_id) or (None, None).""" + d = get_key_dir(email) + priv_path = d / "prev_spk_private.bin" + id_path = d / "prev_spk_id.txt" + if not priv_path.exists(): + return None, None + raw = priv_path.read_bytes() + if local_key: + try: + raw = _decrypt_local(raw, local_key) + except Exception: + pass + priv = load_x25519_private(raw) + spk_id = id_path.read_text().strip() if id_path.exists() else "" + if local_key: + _save_prev_spk(email, priv, spk_id, local_key) + return priv, spk_id + + +def _save_opk_private(email: str, opk_id: str, opk_priv, local_key: bytes | None = None): + d = get_key_dir(email) / "opk_private" + d.mkdir(parents=True, exist_ok=True) + os.chmod(d, 0o700) + raw = serialize_x25519_private(opk_priv) + data = _encrypt_local(raw, local_key) if local_key else raw + (d / f"{opk_id}.bin").write_bytes(data) + os.chmod(d / f"{opk_id}.bin", 0o600) + + +def _load_opk_private(email: str, opk_id: str, local_key: bytes | None = None): + d = get_key_dir(email) / "opk_private" + p = d / f"{opk_id}.bin" + if not p.exists(): + return None + raw = p.read_bytes() + if local_key: + try: + raw = _decrypt_local(raw, local_key) + except Exception: + pass + priv = load_x25519_private(raw) + # Migration: re-save encrypted if local_key provided + if local_key: + _save_opk_private(email, opk_id, priv, local_key) + return priv + + +def _secure_delete(p: Path): + """Overwrite file with random data before deletion (anti-forensic wipe).""" + try: + if not p.exists(): + return + size = p.stat().st_size + if size > 0: + with open(p, "r+b") as f: + f.write(os.urandom(size)) + f.flush() + os.fsync(f.fileno()) + p.unlink() + except Exception: + try: + p.unlink(missing_ok=True) + except Exception: + pass + + +def _delete_opk_private(email: str, opk_id: str): + d = get_key_dir(email) / "opk_private" + p = d / f"{opk_id}.bin" + _secure_delete(p) + + +def _save_device_id(email: str, device_id: str): + d = get_key_dir(email) + p = d / "device_id.txt" + p.write_text(device_id) + os.chmod(p, 0o600) + + +def _load_device_id(email: str) -> str | None: + d = get_key_dir(email) + p = d / "device_id.txt" + if not p.exists(): + return None + return p.read_text().strip() or None + + +# ------------------------------------------------------------------ +# Identity key change exception (TOFU hard-fail) +# ------------------------------------------------------------------ + +class IdentityKeyChanged(Exception): + """Raised when a peer's identity key has changed (TOFU violation). + + Session creation is blocked until the user explicitly accepts the new key. + """ + def __init__(self, user_id: str, new_key_bytes: bytes, status: str): + self.user_id = user_id + self.new_key_bytes = new_key_bytes + self.status = status # "changed" or "changed_verified" + super().__init__( + f"Identity key changed for user {user_id} (status={status}). " + f"Accept the new key before communicating." + ) + + +# ------------------------------------------------------------------ +# Client-side brute-force lockout +# ------------------------------------------------------------------ + +_LOCKOUT_BASE_SECONDS = 2 +_LOCKOUT_MAX_SECONDS = 300 # 5 min cap + + +def _get_lockout_path(email: str) -> Path: + return get_key_dir(email) / "login_lockout.json" + + +def _check_lockout(email: str) -> float: + """Return seconds remaining until next attempt allowed. 0 = can try now.""" + p = _get_lockout_path(email) + if not p.exists(): + return 0.0 + try: + data = json.loads(p.read_text()) + locked_until = data.get("locked_until", 0.0) + remaining = locked_until - time.time() + return max(0.0, remaining) + except Exception: + return 0.0 + + +def _record_failed_attempt(email: str): + """Increment failed counter, update locked_until.""" + p = _get_lockout_path(email) + failed = 0 + try: + if p.exists(): + data = json.loads(p.read_text()) + failed = data.get("failed_attempts", 0) + except Exception: + pass + failed += 1 + delay = min(_LOCKOUT_BASE_SECONDS ** failed, _LOCKOUT_MAX_SECONDS) + locked_until = time.time() + delay + p.write_text(json.dumps({"failed_attempts": failed, "locked_until": locked_until})) + os.chmod(p, 0o600) + + +def _clear_lockout(email: str): + """Reset on successful login.""" + p = _get_lockout_path(email) + if p.exists(): + try: + p.unlink() + except Exception: + pass + + +def _save_session(email: str, peer_user_id: str, ratchet: DoubleRatchet, + local_key: bytes | None = None, peer_device_id: str | None = None): + d = get_key_dir(email) / "sessions" + d.mkdir(parents=True, exist_ok=True) + os.chmod(d, 0o700) + if peer_device_id: + filename = f"{peer_user_id}_{peer_device_id}.bin" + else: + filename = f"{peer_user_id}.bin" + p = d / filename + data = ratchet.export_state() + if local_key: + data = _encrypt_local(data, local_key) + p.write_bytes(data) + os.chmod(p, 0o600) + + +def _load_session(email: str, peer_user_id: str, + local_key: bytes | None = None, + peer_device_id: str | None = None) -> DoubleRatchet | None: + d = get_key_dir(email) / "sessions" + if peer_device_id: + p = d / f"{peer_user_id}_{peer_device_id}.bin" + if not p.exists(): + # Fallback: try old format (no device_id) and migrate + p_old = d / f"{peer_user_id}.bin" + if p_old.exists(): + ratchet = _load_session_file(p_old, local_key) + if ratchet: + _save_session(email, peer_user_id, ratchet, local_key, + peer_device_id=peer_device_id) + _secure_delete(p_old) + return ratchet + return None + else: + p = d / f"{peer_user_id}.bin" + if not p.exists(): + return None + return _load_session_file(p, local_key) + + +def _load_session_file(p: Path, local_key: bytes | None = None) -> DoubleRatchet | None: + """Load a session from a specific file path.""" + if not p.exists(): + return None + raw = p.read_bytes() + if local_key: + try: + data = _decrypt_local(raw, local_key) + except Exception: + # Migration: try loading as plaintext (old unencrypted format) + try: + ratchet = DoubleRatchet.import_state(raw) + return ratchet + except Exception: + return None + return DoubleRatchet.import_state(data) + return DoubleRatchet.import_state(raw) + + +def _delete_session_file(email: str, peer_user_id: str, peer_device_id: str | None = None): + """Securely delete a session file from disk (for session reset).""" + d = get_key_dir(email) / "sessions" + if peer_device_id: + p = d / f"{peer_user_id}_{peer_device_id}.bin" + else: + p = d / f"{peer_user_id}.bin" + _secure_delete(p) + + +def _save_sender_key_state(email: str, conv_id: str, state: SenderKeyState, + local_key: bytes | None = None): + d = get_key_dir(email) / "sender_keys" + d.mkdir(parents=True, exist_ok=True) + os.chmod(d, 0o700) + p = d / f"{conv_id}.bin" + data = state.export_state() + if local_key: + data = _encrypt_local(data, local_key) + p.write_bytes(data) + os.chmod(p, 0o600) + + +def _load_sender_key_state(email: str, conv_id: str, + local_key: bytes | None = None) -> SenderKeyState | None: + d = get_key_dir(email) / "sender_keys" + p = d / f"{conv_id}.bin" + if not p.exists(): + return None + raw = p.read_bytes() + if local_key: + try: + data = _decrypt_local(raw, local_key) + except Exception: + try: + sk = SenderKeyState.import_state(raw) + _save_sender_key_state(email, conv_id, sk, local_key) + return sk + except Exception: + return None + return SenderKeyState.import_state(data) + return SenderKeyState.import_state(raw) + + +def _save_recv_sender_key(email: str, conv_id: str, sender_id: str, state: SenderKeyState, + local_key: bytes | None = None, + sender_device_id: str | None = None): + d = get_key_dir(email) / "sender_keys_recv" + d.mkdir(parents=True, exist_ok=True) + os.chmod(d, 0o700) + if sender_device_id: + filename = f"{conv_id}_{sender_id}_{sender_device_id}.bin" + else: + filename = f"{conv_id}_{sender_id}.bin" + p = d / filename + data = state.export_state() + if local_key: + data = _encrypt_local(data, local_key) + p.write_bytes(data) + os.chmod(p, 0o600) + + +def _load_recv_sender_key(email: str, conv_id: str, sender_id: str, + local_key: bytes | None = None, + sender_device_id: str | None = None) -> SenderKeyState | None: + d = get_key_dir(email) / "sender_keys_recv" + if sender_device_id: + p = d / f"{conv_id}_{sender_id}_{sender_device_id}.bin" + if not p.exists(): + # Fallback: try old format and migrate + p_old = d / f"{conv_id}_{sender_id}.bin" + if p_old.exists(): + sk = _load_recv_sender_key_file(p_old, local_key) + if sk: + _save_recv_sender_key(email, conv_id, sender_id, sk, local_key, + sender_device_id=sender_device_id) + _secure_delete(p_old) + return sk + return None + else: + p = d / f"{conv_id}_{sender_id}.bin" + if not p.exists(): + return None + return _load_recv_sender_key_file(p, local_key) + + +def _load_recv_sender_key_file(p: Path, local_key: bytes | None = None) -> SenderKeyState | None: + """Load a recv sender key from a specific file path.""" + if not p.exists(): + return None + raw = p.read_bytes() + if local_key: + try: + data = _decrypt_local(raw, local_key) + except Exception: + try: + sk = SenderKeyState.import_state(raw) + return sk + except Exception: + return None + return SenderKeyState.import_state(data) + return SenderKeyState.import_state(raw) + + +# --------------------------------------------------------------------------- +# Local decrypted message cache (Double Ratchet keys are one-time use) +# --------------------------------------------------------------------------- + +def _load_message_cache(email: str, conv_id: str, cache_key: bytes | None = None) -> dict: + d = get_key_dir(email) / "message_cache" + p_bin = d / f"{conv_id}.bin" + p_json = d / f"{conv_id}.json" + + # Migration: if old plaintext .json exists but encrypted .bin doesn't + if p_json.exists() and not p_bin.exists(): + try: + cache = json.loads(p_json.read_text("utf-8")) + if cache_key: + _save_message_cache_full(d, conv_id, cache, cache_key) + _secure_delete(p_json) + return cache + except Exception: + return {} + + if not p_bin.exists(): + return {} + if not cache_key: + return {} + try: + raw = p_bin.read_bytes() + # Format: nonce (12) + tag (16) + ciphertext + nonce = raw[:12] + tag = raw[12:28] + ct = raw[28:] + plaintext = aes_decrypt(cache_key, nonce, ct, tag) + return json.loads(plaintext.decode("utf-8")) + except Exception: + return {} + + +def _save_message_cache_full(d: Path, conv_id: str, cache: dict, cache_key: bytes): + """Write the full cache dict encrypted to disk.""" + d.mkdir(parents=True, exist_ok=True) + os.chmod(d, 0o700) + p = d / f"{conv_id}.bin" + plaintext = json.dumps(cache, ensure_ascii=False).encode("utf-8") + _key, nonce, ct, tag = aes_encrypt(plaintext, key=cache_key) + p.write_bytes(nonce + tag + ct) + os.chmod(p, 0o600) + + +def _save_message_to_cache(email: str, conv_id: str, message_id: str, payload: dict, + cache_key: bytes | None = None): + d = get_key_dir(email) / "message_cache" + cache = _load_message_cache(email, conv_id, cache_key) + cache[message_id] = payload + if cache_key: + _save_message_cache_full(d, conv_id, cache, cache_key) + else: + # Fallback: plaintext (no identity key available yet) + d.mkdir(parents=True, exist_ok=True) + os.chmod(d, 0o700) + p = d / f"{conv_id}.json" + p.write_text(json.dumps(cache, ensure_ascii=False), "utf-8") + os.chmod(p, 0o600) + + +# --------------------------------------------------------------------------- +# Verification storage (TOFU + explicit verification) +# --------------------------------------------------------------------------- + +def _save_known_identity_keys(email: str, keys: dict, local_key: bytes | None = None): + """Save TOFU identity key registry (encrypted with local_key).""" + p = get_key_dir(email) / "known_identity_keys.bin" + data = json.dumps({"version": 1, "keys": keys}).encode("utf-8") + if local_key: + data = _encrypt_local(data, local_key) + p.write_bytes(data) + os.chmod(p, 0o600) + + +def _load_known_identity_keys(email: str, local_key: bytes | None = None) -> dict: + """Load TOFU identity key registry. Returns empty dict on error. + + No plaintext fallback — these files were never stored unencrypted + (feature introduced after local encryption was implemented). + Accepting plaintext would allow an attacker with disk access to + inject fake identity keys and bypass TOFU warnings. + """ + p = get_key_dir(email) / "known_identity_keys.bin" + if not p.exists(): + return {} + raw = p.read_bytes() + try: + if local_key: + data = _decrypt_local(raw, local_key) + else: + data = raw + obj = json.loads(data) + return obj.get("keys", {}) + except Exception: + return {} + + +def _save_verified_contacts(email: str, contacts: dict, local_key: bytes | None = None): + """Save explicit verification state (encrypted with local_key).""" + p = get_key_dir(email) / "verified_contacts.bin" + data = json.dumps({"version": 1, "contacts": contacts}).encode("utf-8") + if local_key: + data = _encrypt_local(data, local_key) + p.write_bytes(data) + os.chmod(p, 0o600) + + +def _load_verified_contacts(email: str, local_key: bytes | None = None) -> dict: + """Load explicit verification state. Returns empty dict on error. + + No plaintext fallback — these files were never stored unencrypted. + Accepting plaintext would allow an attacker with disk access to + inject fake verification records (mark attacker as "verified"). + """ + p = get_key_dir(email) / "verified_contacts.bin" + if not p.exists(): + return {} + raw = p.read_bytes() + try: + if local_key: + data = _decrypt_local(raw, local_key) + else: + data = raw + obj = json.loads(data) + return obj.get("contacts", {}) + except Exception: + return {} + + +def _solve_pow(challenge: str, difficulty: int) -> str: + """Solve a proof-of-work challenge by finding a nonce with enough leading zero bits.""" + target_bytes = difficulty // 8 + target_bits = difficulty % 8 + mask = (0xFF << (8 - target_bits)) & 0xFF if target_bits else 0 + nonce = 0 + while True: + digest = hashlib.sha256(f"{challenge}{nonce}".encode()).digest() + # Fast path: check full zero bytes first + ok = True + for i in range(target_bytes): + if digest[i] != 0: + ok = False + break + if ok and target_bits: + if digest[target_bytes] & mask: + ok = False + if ok: + return str(nonce) + nonce += 1 + + +class ChatClient: + def __init__(self): + self.reader: ProtocolReader | None = None + self.writer: ProtocolWriter | None = None + self.raw_writer: asyncio.StreamWriter | None = None + self.session: dict | None = None + self.private_key = None # RSA private key (login only) + self.public_key = None # RSA public key (login only) + self.username: str = "" + self.email: str = "" + self._listener_task: asyncio.Task | None = None + self._response_queue: asyncio.Queue = asyncio.Queue() + self._notification_queue: asyncio.Queue = asyncio.Queue() + self._pending: dict[str, asyncio.Future] = {} + self._pairing_temp_private_key = None + self._reencrypt_progress_cb = None + self._logger = logging.getLogger("encrypted_chat.client") + + # Signal Protocol keys + self.identity_private = None # Ed25519PrivateKey + self.identity_public = None # Ed25519PublicKey + self.spk_private = None # X25519PrivateKey (current signed prekey) + self.spk_id: str = "" + self._prev_spk_private = None # Previous SPK for grace period (M4) + self._prev_spk_id: str = "" + self.opk_privates: dict[str, object] = {} # id -> X25519PrivateKey + self.sessions: dict[str, DoubleRatchet] = {} # "user_id:device_id" -> ratchet + self.sender_key_states: dict[str, SenderKeyState] = {} # conv_id -> own sender key + self.recv_sender_keys: dict[str, SenderKeyState] = {} # "conv_id:sender_id:device_id" -> their key + # Cache: user_id -> {identity_key (Ed25519PublicKey), username, email} + self._user_cache: dict[str, dict] = {} + self.connected: bool = False + self.login_rejected: bool = False + self._cache_key: bytes | None = None # AES key for encrypting message cache on disk + self._local_key: bytes | None = None # AES key for encrypting session/sender key files + # Multi-device support + self.device_id: str | None = None # This device's UUID + self._device_bundle_cache: dict[str, tuple[float, list[dict]]] = {} # user_id -> (ts, bundles) + # Queue of received messages to self-encrypt for multi-device access + self._pending_self_encrypt: list[dict] = [] + # Contact key verification (TOFU + explicit) + self._known_identity_keys: dict = {} # user_id -> {identity_key hex, first_seen, last_seen} + self._verified_contacts: dict = {} # user_id -> {identity_key hex, verified_at, method} + self._key_change_cb = None # callback(user_id, username, old_key_hex, was_verified) + + async def connect(self): + host = os.getenv("SERVER_HOST", "127.0.0.1") + port = int(os.getenv("SERVER_PORT", "9999")) + tls_enabled = os.getenv("TLS_ENABLED", "false").lower() in ("1", "true", "yes") + tls_required = os.getenv("TLS_REQUIRED", "false").lower() in ("1", "true", "yes") + ssl_context = None + if tls_required and not tls_enabled: + raise RuntimeError("TLS_REQUIRED is enabled but TLS is not enabled.") + if tls_enabled: + insecure = os.getenv("TLS_INSECURE", "false").lower() in ("1", "true", "yes") + is_dev = os.getenv("ENVIRONMENT", "").lower() in ("dev", "development") + if insecure and not is_dev: + raise RuntimeError("TLS_INSECURE is only allowed when ENVIRONMENT=dev") + ssl_context = ssl.create_default_context() + ca_file = os.getenv("TLS_CA_FILE", "").strip() + if ca_file: + ssl_context.load_verify_locations(cafile=ca_file) + elif insecure: + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + else: + self._logger.warning("TLS is disabled — traffic is unencrypted. Set TLS_ENABLED=true for production.") + r, w = await asyncio.open_connection(host, port, limit=MAX_MESSAGE_BYTES, ssl=ssl_context) + self.reader = ProtocolReader(r) + self.writer = ProtocolWriter(w) + self.raw_writer = w + self.connected = True + + async def _background_listener(self): + """Read messages from server, routing responses vs notifications.""" + while True: + msg = await self.reader.read_message() + if msg is None: + self.connected = False + # Fail all pending futures so send_and_recv doesn't hang + pending = dict(self._pending) + self._pending.clear() + err = ConnectionError("Server connection lost") + for fut in pending.values(): + if not fut.done(): + fut.set_exception(err) + break + if msg.get("type") in ("new_message", "messages_read", "message_deleted", + "conversation_created", "member_added", "member_removed", + "user_online", "user_offline", "online_users", + "group_invitation", "conversation_renamed", + "session_reset", + "message_reacted", "message_pinned", "message_unpinned", + "message_delivered", "username_changed"): + await self._notification_queue.put(msg) + else: + req_id = msg.get("request_id") + if req_id and req_id in self._pending: + fut = self._pending.pop(req_id) + if not fut.done(): + fut.set_result(msg) + else: + await self._response_queue.put(msg) + + async def send_and_recv(self, msg_type: str, timeout: float = 30.0, **kwargs) -> dict: + try: + request_id = str(uuid.uuid4()) + loop = asyncio.get_running_loop() + fut = loop.create_future() + self._pending[request_id] = fut + await self.writer.send_request(msg_type, request_id=request_id, **kwargs) + except (ValueError, ConnectionError, OSError) as e: + self._pending.pop(request_id, None) + return { + "type": msg_type, + "status": "error", + "data": {"message": str(e) or "Connection lost."}, + } + try: + return await asyncio.wait_for(fut, timeout=timeout) + except asyncio.TimeoutError: + self._logger.warning("send_and_recv timeout for '%s' after %.0fs", msg_type, timeout) + return { + "type": msg_type, + "status": "error", + "data": {"message": f"Request timed out ({msg_type})"}, + } + except ConnectionError: + return { + "type": msg_type, + "status": "error", + "data": {"message": "Connection lost."}, + } + finally: + self._pending.pop(request_id, None) + + # ------------------------------------------------------------------ + # User info / identity key cache + # ------------------------------------------------------------------ + + async def _get_user_info(self, user_id: str = "", email: str = "") -> dict | None: + """Get user info from server, cache identity key. Performs TOFU check.""" + cached = self._user_cache.get(user_id) + if cached: + return cached + kwargs = {} + if user_id: + kwargs["user_id"] = user_id + elif email: + kwargs["email"] = email + else: + return None + resp = await self.send_and_recv("get_user_info", **kwargs) + if resp["status"] != "ok": + return None + data = resp["data"] + ik_bytes = decode_binary(data["identity_key"]) if data.get("identity_key") else None + info = { + "user_id": data["user_id"], + "username": data["username"], + "email": data["email"], + "identity_key": load_ed25519_public(ik_bytes) if ik_bytes else None, + "identity_key_bytes": ik_bytes, + } + # TOFU: check identity key against known keys + if ik_bytes: + status = self.check_identity_key(data["user_id"], ik_bytes) + info["identity_key_status"] = status + self._user_cache[data["user_id"]] = info + return info + + # ------------------------------------------------------------------ + # Contact Key Verification + # ------------------------------------------------------------------ + + def _load_verification_stores(self): + """Load TOFU and verification stores from disk.""" + if not self.email: + return + self._known_identity_keys = _load_known_identity_keys(self.email, self._local_key) + self._verified_contacts = _load_verified_contacts(self.email, self._local_key) + + def check_identity_key(self, user_id: str, identity_key_bytes: bytes) -> str: + """Check a user's identity key against TOFU registry. + + Returns: + "new" — first contact, key recorded (TOFU) + "trusted" — key matches previously seen, not explicitly verified + "verified" — key matches and explicitly verified + "changed" — key differs from recorded (WARNING) + "changed_verified" — key changed AND was previously verified (CRITICAL) + """ + ik_hex = identity_key_bytes.hex() + now = datetime.now(timezone.utc).isoformat() + known = self._known_identity_keys.get(user_id) + + if known is None: + # First time seeing this user — TOFU: trust on first use + self._known_identity_keys[user_id] = { + "identity_key": ik_hex, + "first_seen": now, + "last_seen": now, + } + if self.email: + _save_known_identity_keys(self.email, self._known_identity_keys, self._local_key) + return "new" + + if known["identity_key"] == ik_hex: + # Key matches — update last_seen + known["last_seen"] = now + if self.email: + _save_known_identity_keys(self.email, self._known_identity_keys, self._local_key) + # Check if explicitly verified + verified = self._verified_contacts.get(user_id) + if verified and verified.get("identity_key") == ik_hex: + return "verified" + return "trusted" + + # Key has CHANGED + was_verified = user_id in self._verified_contacts + old_key_hex = known["identity_key"] + + # Invoke callback for GUI/CLI warning + if self._key_change_cb: + username = "" + cached = self._user_cache.get(user_id) + if cached: + username = cached.get("username", "") + try: + self._key_change_cb(user_id, username, old_key_hex, was_verified, identity_key_bytes) + except Exception: + pass + + return "changed_verified" if was_verified else "changed" + + def verify_contact(self, user_id: str, identity_key_bytes: bytes, method: str = "manual"): + """Mark a contact's identity key as explicitly verified.""" + ik_hex = identity_key_bytes.hex() + now = datetime.now(timezone.utc).isoformat() + self._verified_contacts[user_id] = { + "identity_key": ik_hex, + "verified_at": now, + "method": method, + } + # Also ensure TOFU registry is up to date + if user_id not in self._known_identity_keys: + self._known_identity_keys[user_id] = { + "identity_key": ik_hex, + "first_seen": now, + "last_seen": now, + } + else: + self._known_identity_keys[user_id]["last_seen"] = now + if self.email: + _save_verified_contacts(self.email, self._verified_contacts, self._local_key) + _save_known_identity_keys(self.email, self._known_identity_keys, self._local_key) + # Update user cache status + cached = self._user_cache.get(user_id) + if cached: + cached["identity_key_status"] = "verified" + + def unverify_contact(self, user_id: str): + """Remove explicit verification for a contact.""" + self._verified_contacts.pop(user_id, None) + if self.email: + _save_verified_contacts(self.email, self._verified_contacts, self._local_key) + cached = self._user_cache.get(user_id) + if cached and cached.get("identity_key_status") == "verified": + cached["identity_key_status"] = "trusted" + + def accept_key_change(self, user_id: str, new_ik_bytes: bytes): + """Accept a changed identity key — update TOFU, remove old verification.""" + ik_hex = new_ik_bytes.hex() + now = datetime.now(timezone.utc).isoformat() + self._known_identity_keys[user_id] = { + "identity_key": ik_hex, + "first_seen": now, + "last_seen": now, + } + # Remove old verification — user must re-verify + self._verified_contacts.pop(user_id, None) + if self.email: + _save_known_identity_keys(self.email, self._known_identity_keys, self._local_key) + _save_verified_contacts(self.email, self._verified_contacts, self._local_key) + # Update cache + cached = self._user_cache.get(user_id) + if cached: + cached["identity_key_status"] = "trusted" + + def get_verification_status(self, user_id: str) -> str: + """Get verification status for a user. + + Returns: "verified", "trusted", or "unverified". + """ + verified = self._verified_contacts.get(user_id) + if verified: + # Check key still matches + known = self._known_identity_keys.get(user_id) + if known and known.get("identity_key") == verified.get("identity_key"): + return "verified" + if user_id in self._known_identity_keys: + return "trusted" + return "unverified" + + def get_safety_number(self, peer_user_id: str) -> str | None: + """Get formatted safety number for a peer (requires both identity keys).""" + if not self.identity_public or not self.session: + return None + my_uid = self.session.get("user_id", "") + my_ik_bytes = serialize_ed25519_public(self.identity_public) + cached = self._user_cache.get(peer_user_id) + if not cached or not cached.get("identity_key_bytes"): + return None + return compute_safety_number(my_uid, my_ik_bytes, + peer_user_id, cached["identity_key_bytes"]) + + def get_my_fingerprint(self) -> str | None: + """Get formatted fingerprint for own identity key.""" + if not self.identity_public or not self.session: + return None + my_uid = self.session.get("user_id", "") + my_ik_bytes = serialize_ed25519_public(self.identity_public) + fp = compute_fingerprint(my_uid, my_ik_bytes) + return format_fingerprint(fp) + + def get_peer_fingerprint(self, peer_user_id: str) -> str | None: + """Get formatted fingerprint for a peer's identity key.""" + cached = self._user_cache.get(peer_user_id) + if not cached or not cached.get("identity_key_bytes"): + return None + fp = compute_fingerprint(peer_user_id, cached["identity_key_bytes"]) + return format_fingerprint(fp) + + def get_verification_qr_data(self) -> bytes | None: + """Get QR code payload bytes for own identity (for peer to scan).""" + if not self.identity_public or not self.session: + return None + my_uid = self.session.get("user_id", "") + my_ik_bytes = serialize_ed25519_public(self.identity_public) + return encode_verification_qr(my_uid, my_ik_bytes) + + def verify_qr_code(self, qr_data: bytes) -> tuple[bool, str, str]: + """Verify a scanned QR code against known identity keys. + + Returns (success, user_id, message). + """ + try: + user_id, ik_bytes = decode_verification_qr(qr_data) + except ValueError as e: + return False, "", f"Invalid QR code: {e}" + cached = self._user_cache.get(user_id) + if not cached: + return False, user_id, "Unknown user — not in your contacts." + if not cached.get("identity_key_bytes"): + return False, user_id, "No identity key on record for this user." + if cached["identity_key_bytes"] != ik_bytes: + return False, user_id, "Identity key MISMATCH — verification failed!" + # Keys match — mark as verified + self.verify_contact(user_id, ik_bytes, method="qr_code") + username = cached.get("username", user_id[:8]) + return True, user_id, f"Verified {username} via QR code." + + # ------------------------------------------------------------------ + # Registration + # ------------------------------------------------------------------ + + async def register(self, username: str, password: str, email: str) -> tuple[bool, str]: + """Register user. Generates RSA + Ed25519 + prekeys.""" + self.username = username + self.email = email + pwd_bytes = bytearray(password.encode("utf-8")) if password else None + + try: + # RSA keys for login + priv, pub, err = load_keys(email, password=bytes(pwd_bytes) if pwd_bytes else None) + if priv is None: + priv, pub = generate_rsa_keypair() + save_keys(email, priv, pub, password=bytes(pwd_bytes) if pwd_bytes else None) + self.private_key = priv + self.public_key = pub + + # Ed25519 identity keys + ed_priv, ed_pub = _load_identity_keys(email, password=bytes(pwd_bytes) if pwd_bytes else None) + if ed_priv is None: + ed_priv, ed_pub = generate_identity_keypair() + _save_identity_keys(email, ed_priv, ed_pub, password=bytes(pwd_bytes) if pwd_bytes else None) + self.identity_private = ed_priv + self.identity_public = ed_pub + self._cache_key = derive_self_encryption_key(ed_priv) + self._local_key = derive_local_storage_key(ed_priv) + self._load_verification_stores() + finally: + if pwd_bytes: + pwd_bytes[:] = b'\x00' * len(pwd_bytes) + + pub_pem = serialize_public_key(pub).decode("utf-8") + ik_b64 = encode_binary(serialize_ed25519_public(ed_pub)) + + extra_fields: dict = {} + start = await self.send_and_recv( + "register", + username=username, + public_key=pub_pem, + email=email, + identity_key=ik_b64, + ) + # Handle PoW challenge (server under pressure) + if start.get("status") == "pow_required": + challenge = start["data"]["challenge"] + mac = start["data"]["mac"] + difficulty = start["data"]["difficulty"] + logger.info("Server requires proof-of-work (difficulty %d), solving...", difficulty) + nonce = _solve_pow(challenge, difficulty) + extra_fields = {"pow_challenge": challenge, "pow_mac": mac, "pow_nonce": nonce} + start = await self.send_and_recv( + "register", + username=username, + public_key=pub_pem, + email=email, + identity_key=ik_b64, + **extra_fields, + ) + if start["status"] != "ok": + return False, start["data"]["message"] + code = start["data"].get("code") + if code: + return True, code + return True, start["data"].get("message", "Check your email for the code.") + + async def confirm_registration(self, email: str, username: str, code: str) -> tuple[bool, str]: + confirm = await self.send_and_recv("register_confirm", email=email, code=code) + if confirm["status"] == "ok": + # Upload prekeys immediately after registration + await self._generate_and_upload_prekeys() + return True, f"Registered as '{username}' (ID: {confirm['data']['user_id']})" + return False, confirm["data"]["message"] + + async def _generate_and_upload_prekeys(self, keep_spk: bool = False): + """Generate SPK + OPKs and upload to server. + + If keep_spk=True, re-sign the existing SPK instead of generating a new + one. This is used after device pairing so both devices share the same + SPK and either can respond to X3DH. + """ + if not self.identity_private: + return + + if keep_spk and self.spk_private and self.spk_id: + # Re-sign existing SPK (both devices share the identity key) + spk_pub_bytes = serialize_x25519_public(self.spk_private.public_key()) + spk_sig = ed25519_sign(self.identity_private, spk_pub_bytes) + spk_data = { + "id": self.spk_id, + "public_key": encode_binary(spk_pub_bytes), + "signature": encode_binary(spk_sig), + } + else: + # Save current SPK as previous for grace period (M4: in-flight X3DH) + if self.spk_private and self.spk_id: + self._prev_spk_private = self.spk_private + self._prev_spk_id = self.spk_id + _save_prev_spk(self.email, self.spk_private, self.spk_id, self._local_key) + # Generate a brand-new signed prekey + spk = generate_signed_prekey(self.identity_private) + self.spk_private = spk["private"] + self.spk_id = spk["id"] + _save_spk(self.email, spk["private"], spk["id"], self._local_key) + spk_data = { + "id": spk["id"], + "public_key": encode_binary(serialize_x25519_public(spk["public"])), + "signature": encode_binary(spk["signature"]), + } + + # Generate one-time prekeys + opks = generate_one_time_prekeys(OPK_BATCH_SIZE) + for opk in opks: + self.opk_privates[opk["id"]] = opk["private"] + _save_opk_private(self.email, opk["id"], opk["private"], self._local_key) + + # Upload to server + otp_data = [ + {"id": opk["id"], "public_key": encode_binary(serialize_x25519_public(opk["public"]))} + for opk in opks + ] + resp = await self.send_and_recv( + "upload_prekeys", + signed_prekey=spk_data, + one_time_prekeys=otp_data, + ) + if resp.get("status") != "ok": + self._logger.warning("upload_prekeys failed: %s (will retry on login)", + resp.get("data", {}).get("message", "unknown")) + + async def _ensure_prekeys(self): + """Check OPK count and SPK age, replenish/rotate if needed. + + Uses single-roundtrip `ensure_prekeys` handler when available, + falls back to legacy two-step flow (get_prekey_count + upload_prekeys). + """ + resp = await self.send_and_recv("get_prekey_count") + if resp["status"] != "ok": + return + count = resp["data"].get("count", 0) + spk_created_at = resp["data"].get("spk_created_at", "") + + need_new_spk = False + if spk_created_at: + try: + created = datetime.fromisoformat(spk_created_at) + if created.tzinfo is None: + created = created.replace(tzinfo=timezone.utc) + age_days = (datetime.now(timezone.utc) - created).days + if age_days >= SPK_ROTATION_DAYS: + need_new_spk = True + self._logger.info("SPK is %d days old, rotating...", age_days) + except Exception: + need_new_spk = True + else: + # No SPK on server for this device — must upload one + need_new_spk = True + self._logger.info("No SPK on server for this device, uploading...") + + if count < OPK_REPLENISH_THRESHOLD or need_new_spk: + if count >= OPK_REPLENISH_THRESHOLD: + self._logger.info("SPK rotation triggered (OPK count OK: %d)", count) + else: + self._logger.info("OPK count low (%d), replenishing...", count) + await self._generate_and_upload_prekeys_batch(need_new_spk) + + async def _generate_and_upload_prekeys_batch(self, need_new_spk: bool = False): + """Generate and upload prekeys in a single round-trip via ensure_prekeys.""" + if not self.identity_private: + return + + kwargs: dict = {} + + # SPK + if need_new_spk: + if self.spk_private and self.spk_id: + self._prev_spk_private = self.spk_private + self._prev_spk_id = self.spk_id + _save_prev_spk(self.email, self.spk_private, self.spk_id, self._local_key) + spk = generate_signed_prekey(self.identity_private) + self.spk_private = spk["private"] + self.spk_id = spk["id"] + _save_spk(self.email, spk["private"], spk["id"], self._local_key) + kwargs["signed_prekey"] = { + "id": spk["id"], + "public_key": encode_binary(serialize_x25519_public(spk["public"])), + "signature": encode_binary(spk["signature"]), + } + + # OPKs + opks = generate_one_time_prekeys(OPK_BATCH_SIZE) + for opk in opks: + self.opk_privates[opk["id"]] = opk["private"] + _save_opk_private(self.email, opk["id"], opk["private"], self._local_key) + kwargs["one_time_prekeys"] = [ + {"id": opk["id"], "public_key": encode_binary(serialize_x25519_public(opk["public"]))} + for opk in opks + ] + + resp = await self.send_and_recv("ensure_prekeys", **kwargs) + if resp["status"] == "ok": + data = resp.get("data", {}) + self._logger.info("ensure_prekeys: count=%d, spk_uploaded=%s, otps_uploaded=%d", + data.get("count", 0), data.get("uploaded_spk", False), + data.get("uploaded_otps", 0)) + + # ------------------------------------------------------------------ + # Login + # ------------------------------------------------------------------ + + async def login(self, email: str, password: str) -> tuple[bool, str]: + """Login user. Returns (success, message).""" + self.email = email + + # Brute-force lockout check + remaining = _check_lockout(email) + if remaining > 0: + return False, f"Too many failed attempts. Try again in {remaining:.0f}s." + + pwd_bytes = bytearray(password.encode("utf-8")) if password else None + + try: + # Load RSA keys + priv, pub, err = load_keys(email, password=bytes(pwd_bytes) if pwd_bytes else None) + if priv is None: + if err and "password" in err.lower(): + _record_failed_attempt(email) + return False, err or "No local keys found. Register first." + self.private_key = priv + self.public_key = pub + + # Load identity keys + ed_priv, ed_pub = _load_identity_keys(email, password=bytes(pwd_bytes) if pwd_bytes else None) + finally: + if pwd_bytes: + pwd_bytes[:] = b'\x00' * len(pwd_bytes) + + if ed_priv is not None: + self.identity_private = ed_priv + self.identity_public = ed_pub + self._cache_key = derive_self_encryption_key(ed_priv) + self._local_key = derive_local_storage_key(ed_priv) + self._load_verification_stores() + + # Load SPK + spk_priv, spk_id = _load_spk(email, self._local_key) + if spk_priv: + self.spk_private = spk_priv + self.spk_id = spk_id + + # Load previous SPK for grace period (M4) + prev_spk_priv, prev_spk_id = _load_prev_spk(email, self._local_key) + if prev_spk_priv: + self._prev_spk_private = prev_spk_priv + self._prev_spk_id = prev_spk_id + + # Load device_id from disk + self.device_id = _load_device_id(email) + + # RSA challenge-response login + start = await self.send_and_recv("login_start", email=email) + if start["status"] != "ok": + return False, start["data"]["message"] + + challenge = decode_binary(start["data"]["challenge"]) + signature = rsa_sign(self.private_key, challenge) + login_kwargs = {"email": email, "signature": encode_binary(signature), + "client_version": VERSION} + if self.device_id: + login_kwargs["device_id"] = self.device_id + finish = await self.send_and_recv("login_finish", **login_kwargs) + if finish["status"] == "ok": + self.session = finish["data"] + self.username = self.session.get("username", "") + # Store device_id from server + self.device_id = finish["data"].get("device_id") + if self.device_id: + _save_device_id(email, self.device_id) + # Replenish prekeys in background — after pairing, the new device + # has no local OPK private keys so we must generate fresh ones + # (server-side OPKs have no matching private keys on this device). + # Use keep_spk=True to preserve the shared SPK so both devices + # can respond to X3DH. + opk_dir = get_key_dir(self.email) / "opk_private" + has_local_opks = opk_dir.exists() and any(opk_dir.iterdir()) + if has_local_opks: + asyncio.create_task(self._ensure_prekeys()) + else: + self._logger.info("No local OPKs (likely new device). Generating fresh OPKs, keeping SPK.") + asyncio.create_task(self._generate_and_upload_prekeys(keep_spk=True)) + _clear_lockout(email) + return True, f"Logged in as '{self.username}' (ID: {self.session['user_id']})" + return False, finish["data"]["message"] + + # ------------------------------------------------------------------ + # Pairing (device pairing — transfers RSA + identity keys) + # ------------------------------------------------------------------ + + async def pairing_start(self, email: str) -> tuple[bool, str]: + """Start device pairing. Returns (success, code/message).""" + temp_priv, temp_pub = generate_rsa_keypair(2048) + self._pairing_temp_private_key = temp_priv + temp_pub_pem = serialize_public_key(temp_pub).decode("utf-8") + resp = await self.send_and_recv("pairing_start", email=email, temp_public_key=temp_pub_pem) + if resp["status"] == "ok": + self._pairing_poll_token = resp["data"].get("poll_token", "") + return True, resp["data"]["code"] + return False, resp["data"]["message"] + + async def pairing_wait(self, code: str, email: str, password: str, timeout: int = 300) -> tuple[bool, str]: + """Wait for pairing payload and import keys. Returns (success, message).""" + if not self._pairing_temp_private_key: + return False, "Pairing not started." + from crypto_utils import aes_decrypt as _aes_decrypt + poll_token = getattr(self, "_pairing_poll_token", "") + deadline = asyncio.get_event_loop().time() + timeout + while asyncio.get_event_loop().time() < deadline: + resp = await self.send_and_recv("pairing_poll", code=code, poll_token=poll_token) + if resp["status"] != "ok": + return False, resp["data"]["message"] + if not resp["data"].get("ready"): + await asyncio.sleep(2.0) + continue + payload = resp["data"]["payload"] + try: + # Decrypt AES key with temp RSA key + from cryptography.hazmat.primitives.asymmetric import padding as rsa_padding + from cryptography.hazmat.primitives import hashes as rsa_hashes + enc_aes_key = decode_binary(payload["encrypted_key"]) + aes_key = self._pairing_temp_private_key.decrypt( + enc_aes_key, + rsa_padding.OAEP( + mgf=rsa_padding.MGF1(algorithm=rsa_hashes.SHA256()), + algorithm=rsa_hashes.SHA256(), + label=None, + ), + ) + nonce = decode_binary(payload["iv"]) + ct = decode_binary(payload["ciphertext"]) + tag = decode_binary(payload["tag"]) + keys_json = _aes_decrypt(aes_key, nonce, ct, tag) + keys_data = json.loads(keys_json) + + pwd_bytes = bytearray(password.encode("utf-8")) if password else None + + try: + # Import RSA key + rsa_priv = load_private_key(keys_data["rsa_private"].encode(), password=None) + rsa_pub = rsa_priv.public_key() + save_keys(email, rsa_priv, rsa_pub, password=bytes(pwd_bytes) if pwd_bytes else None) + + # Import identity keys + ed_priv = load_ed25519_private(bytes.fromhex(keys_data["identity_private"])) + ed_pub = ed_priv.public_key() + _save_identity_keys(email, ed_priv, ed_pub, password=bytes(pwd_bytes) if pwd_bytes else None) + finally: + if pwd_bytes: + pwd_bytes[:] = b'\x00' * len(pwd_bytes) + + self.email = email + self.private_key = rsa_priv + self.public_key = rsa_pub + self.identity_private = ed_priv + self.identity_public = ed_pub + self._cache_key = derive_self_encryption_key(ed_priv) + self._local_key = derive_local_storage_key(ed_priv) + self._load_verification_stores() + self._pairing_temp_private_key = None + + # Multi-device: new device generates own SPK + OPKs on first + # login. No session/sender key import needed — each device + # has independent Double Ratchet sessions. + + return True, "Pairing complete." + except Exception as e: + return False, f"Failed to import keys: {e}" + return False, "Pairing timed out." + + async def authorize_device(self, code: str) -> tuple[bool, str]: + """Authorize a new device by sending all keys to it.""" + if not self.private_key or not self.identity_private: + return False, "Not logged in." + claim = await self.send_and_recv("pairing_claim", code=code) + if claim["status"] != "ok": + return False, claim["data"]["message"] + + temp_pub_pem = claim["data"]["temp_public_key"].encode("utf-8") + temp_pub = load_public_key(temp_pub_pem) + + # Phase 1: Re-encrypt message history so new device can read old + # messages via self-encryption key. This also advances ratchet states + # for any previously-unfetched messages. + try: + await self.reencrypt_history() + except Exception as e: + self._logger.warning("Re-encryption failed: %s", e) + + # Phase 2: Build keys payload — only RSA + identity key. + # Multi-device: new device generates own SPK + OPKs, creates independent + # sessions. No session/sender key transfer needed. + keys_data = { + "rsa_private": serialize_private_key(self.private_key, password=None).decode(), + "identity_private": serialize_ed25519_private_raw(self.identity_private).hex(), + } + + # Phase 3: Encrypt and send keys to new device + from cryptography.hazmat.primitives.asymmetric import padding as rsa_padding + from cryptography.hazmat.primitives import hashes as rsa_hashes + plaintext = json.dumps(keys_data).encode() + aes_key, nonce, ct, tag = aes_encrypt(plaintext) + enc_aes_key = temp_pub.encrypt( + aes_key, + rsa_padding.OAEP( + mgf=rsa_padding.MGF1(algorithm=rsa_hashes.SHA256()), + algorithm=rsa_hashes.SHA256(), + label=None, + ), + ) + payload = { + "encrypted_key": encode_binary(enc_aes_key), + "iv": encode_binary(nonce), + "ciphertext": encode_binary(ct), + "tag": encode_binary(tag), + } + resp = await self.send_and_recv("pairing_send", code=code, payload=payload) + if resp["status"] == "ok": + return True, "Device authorized." + return False, resp["data"]["message"] + + # ------------------------------------------------------------------ + # Password change (local key re-encryption only) + # ------------------------------------------------------------------ + + def change_password(self, old_password: str, new_password: str) -> tuple[bool, str]: + """Change password for local key encryption (RSA + identity key). + + Returns (success, message). + """ + if not self.email: + return False, "Not logged in." + + old_pwd = bytearray(old_password.encode("utf-8")) + new_pwd = bytearray(new_password.encode("utf-8")) + try: + # 1. Verify old password by loading keys + priv, pub, err = load_keys(self.email, password=bytes(old_pwd)) + if priv is None: + return False, "Wrong current password." + + ed_priv, ed_pub = _load_identity_keys(self.email, password=bytes(old_pwd)) + if ed_priv is None: + return False, "Failed to load identity key." + + # 2. Re-save with new password + save_keys(self.email, priv, pub, password=bytes(new_pwd)) + _save_identity_keys(self.email, ed_priv, ed_pub, password=bytes(new_pwd)) + + return True, "Password changed successfully." + finally: + old_pwd[:] = b'\x00' * len(old_pwd) + new_pwd[:] = b'\x00' * len(new_pwd) + + async def change_username(self, new_username: str) -> tuple[bool, str]: + """Change display name on server.""" + if not self.session: + return False, "Not logged in." + new_username = new_username.strip() + if not new_username or len(new_username) > 100: + return False, "Username must be 1-100 characters." + resp = await self.send_and_recv("change_username", username=new_username) + if resp["status"] == "ok": + self.username = resp["data"]["username"] + if self.session: + self.session["username"] = self.username + return True, "Username changed." + return False, resp["data"].get("message", "Unknown error") + + # ------------------------------------------------------------------ + # Key rotation (RSA login key only) + # ------------------------------------------------------------------ + + async def rotate_keys(self, username: str, password: str) -> tuple[bool, str]: + """Rotate RSA keypair to revoke other devices.""" + if not self.session or self.session.get("username") != username: + return False, "Not logged in." + pwd_bytes = password.encode("utf-8") if password else None + priv, pub = generate_rsa_keypair() + save_keys(self.email, priv, pub, password=pwd_bytes) + self.private_key = priv + self.public_key = pub + pub_pem = serialize_public_key(pub).decode("utf-8") + resp = await self.send_and_recv("rotate_keys", public_key=pub_pem) + if resp["status"] == "ok": + return True, "RSA login keys rotated." + return False, resp["data"]["message"] + + # ------------------------------------------------------------------ + # Session management (X3DH + Double Ratchet) + # ------------------------------------------------------------------ + + async def _get_device_bundles(self, peer_user_id: str) -> list[dict]: + """Get per-device key bundles for a peer. Caches for 5 minutes.""" + import time + cached = self._device_bundle_cache.get(peer_user_id) + if cached: + ts, bundles = cached + if time.time() - ts < 300: + return bundles + + resp = await self.send_and_recv("get_key_bundle", user_id=peer_user_id) + if resp["status"] != "ok": + raise RuntimeError(f"Cannot get key bundle for {peer_user_id}: {resp['data']['message']}") + + data = resp["data"] + ik_b64 = data.get("identity_key", "") + + device_bundles = data.get("device_bundles") + if device_bundles: + # Attach identity_key to each bundle + for b in device_bundles: + b["identity_key"] = ik_b64 + else: + # Old server: wrap flat response as single-entry list + device_bundles = [{ + "device_id": None, + "identity_key": ik_b64, + "signed_prekey_id": data.get("signed_prekey_id", ""), + "signed_prekey": data.get("signed_prekey", ""), + "spk_signature": data.get("spk_signature", ""), + "one_time_prekey_id": data.get("one_time_prekey_id"), + "one_time_prekey": data.get("one_time_prekey"), + }] + + self._device_bundle_cache[peer_user_id] = (time.time(), device_bundles) + return device_bundles + + async def _get_or_create_session(self, peer_user_id: str, + peer_device_id: str | None = None, + bundle: dict | None = None) -> DoubleRatchet: + """Load existing session or create one via X3DH. + + If peer_device_id is set, sessions are keyed by "user_id:device_id". + If bundle is provided, it's used instead of fetching from server. + """ + session_key = f"{peer_user_id}:{peer_device_id}" if peer_device_id else peer_user_id + + # Check in-memory cache + if session_key in self.sessions: + return self.sessions[session_key] + + # Check on disk + ratchet = _load_session(self.email, peer_user_id, self._local_key, + peer_device_id=peer_device_id) + if ratchet: + self.sessions[session_key] = ratchet + return ratchet + + # Create new session via X3DH + if not bundle: + resp = await self.send_and_recv("get_key_bundle", user_id=peer_user_id) + if resp["status"] != "ok": + raise RuntimeError(f"Cannot get key bundle for {peer_user_id}: {resp['data']['message']}") + bundle = resp["data"] + + ik_remote_bytes = decode_binary(bundle["identity_key"]) + ik_remote = load_ed25519_public(ik_remote_bytes) + + # TOFU: verify identity key before using it in X3DH + ik_status = self.check_identity_key(peer_user_id, ik_remote_bytes) + if ik_status in ("changed", "changed_verified"): + raise IdentityKeyChanged(peer_user_id, ik_remote_bytes, ik_status) + + spk_remote = load_x25519_public(decode_binary(bundle["signed_prekey"])) + spk_sig = decode_binary(bundle["spk_signature"]) + + opk_remote = None + opk_id = bundle.get("one_time_prekey_id") + if bundle.get("one_time_prekey"): + opk_remote = load_x25519_public(decode_binary(bundle["one_time_prekey"])) + + # Perform X3DH + shared_secret, ek_priv, ek_pub = x3dh_initiate( + self.identity_private, + ik_remote, + spk_remote, + spk_sig, + opk_remote, + ) + + # Initialize Double Ratchet as Alice + ratchet = DoubleRatchet.init_alice(shared_secret, spk_remote) + self.sessions[session_key] = ratchet + _save_session(self.email, peer_user_id, ratchet, self._local_key, + peer_device_id=peer_device_id) + + # Build X3DH header for first message + x3dh_header = { + "ik": encode_binary(serialize_ed25519_public(self.identity_public)), + "ek": encode_binary(serialize_x25519_public(ek_pub)), + } + if opk_id: + x3dh_header["opk_id"] = opk_id + + # Cache the x3dh header for the next send_message call + ratchet._x3dh_header = x3dh_header + + # Cache remote user info + self._user_cache[peer_user_id] = { + "user_id": peer_user_id, + "identity_key": ik_remote, + "identity_key_bytes": ik_remote_bytes, + "identity_key_status": ik_status, + } + + return ratchet + + def _process_x3dh_header(self, sender_id: str, x3dh_header: dict, + sender_device_id: str | None = None, + spk_override=None) -> DoubleRatchet: + """Process an incoming X3DH header to establish session as Bob. + + Args: + spk_override: If provided, use this SPK private key instead of self.spk_private. + Used for grace period fallback (M4). + """ + ik_remote_bytes = decode_binary(x3dh_header["ik"]) + ik_remote = load_ed25519_public(ik_remote_bytes) + + # TOFU: verify identity key before using it in X3DH + ik_status = self.check_identity_key(sender_id, ik_remote_bytes) + if ik_status in ("changed", "changed_verified"): + raise IdentityKeyChanged(sender_id, ik_remote_bytes, ik_status) + + ek_remote = load_x25519_public(decode_binary(x3dh_header["ek"])) + + opk_id = x3dh_header.get("opk_id") + opk_priv = None + if opk_id: + opk_priv = _load_opk_private(self.email, opk_id, self._local_key) + if opk_priv: + _delete_opk_private(self.email, opk_id) + + spk_priv = spk_override if spk_override else self.spk_private + + shared_secret = x3dh_respond( + self.identity_private, + spk_priv, + ik_remote, + ek_remote, + opk_priv, + ) + + spk_pub = spk_priv.public_key() if hasattr(spk_priv, 'public_key') else None + ratchet = DoubleRatchet.init_bob(shared_secret, (spk_priv, spk_pub)) + + session_key = f"{sender_id}:{sender_device_id}" if sender_device_id else sender_id + self.sessions[session_key] = ratchet + _save_session(self.email, sender_id, ratchet, self._local_key, + peer_device_id=sender_device_id) + + self._user_cache[sender_id] = { + "user_id": sender_id, + "identity_key": ik_remote, + "identity_key_bytes": ik_remote_bytes, + "identity_key_status": ik_status, + } + + return ratchet + + # ------------------------------------------------------------------ + # Conversations + # ------------------------------------------------------------------ + + async def create_conversation(self, member_emails: list[str], name: str | None = None) -> tuple[str | None, str]: + kwargs = {"members": member_emails} + if name: + kwargs["name"] = name + resp = await self.send_and_recv("create_conversation", **kwargs) + if resp["status"] == "ok": + return resp["data"]["conversation_id"], "OK" + return None, resp["data"]["message"] + + async def remove_member(self, conv_id: str, user_id: str) -> tuple[bool, str]: + resp = await self.send_and_recv("remove_member", conversation_id=conv_id, user_id=user_id) + if resp["status"] == "ok": + return True, "OK" + return False, resp["data"]["message"] + + async def leave_group(self, conv_id: str) -> tuple[bool, str]: + """Leave a group conversation.""" + resp = await self.send_and_recv("leave_group", conversation_id=conv_id) + if resp["status"] == "ok": + # Clean up local sender key state for this group + self.sender_key_states.pop(conv_id, None) + # Remove received sender keys for this conversation + to_remove = [k for k in self.recv_sender_keys if k.startswith(f"{conv_id}:")] + for k in to_remove: + self.recv_sender_keys.pop(k, None) + return True, "OK" + return False, resp["data"]["message"] + + async def rename_conversation(self, conv_id: str, name: str) -> tuple[bool, str]: + """Rename a group conversation (creator only).""" + resp = await self.send_and_recv("rename_conversation", conversation_id=conv_id, name=name) + if resp["status"] == "ok": + return True, "OK" + return False, resp["data"]["message"] + + async def delete_conversation(self, conv_id: str) -> tuple[bool, str]: + """Delete a conversation (leave + server cleans up if empty).""" + resp = await self.send_and_recv("delete_conversation", conversation_id=conv_id) + if resp["status"] == "ok": + # Clean up local sender key state + self.sender_key_states.pop(conv_id, None) + to_remove = [k for k in self.recv_sender_keys if k.startswith(f"{conv_id}:")] + for k in to_remove: + self.recv_sender_keys.pop(k, None) + return True, "OK" + return False, resp["data"]["message"] + + async def add_member(self, conv_id: str, email: str) -> tuple[bool, str]: + resp = await self.send_and_recv("add_member", conversation_id=conv_id, email=email) + if resp["status"] == "ok": + return True, "OK" + return False, resp["data"]["message"] + + async def accept_invitation(self, conv_id: str) -> tuple[bool, str]: + """Accept a group invitation.""" + resp = await self.send_and_recv("accept_invitation", conversation_id=conv_id) + if resp["status"] == "ok": + return True, "OK" + return False, resp["data"]["message"] + + async def decline_invitation(self, conv_id: str) -> tuple[bool, str]: + """Decline a group invitation.""" + resp = await self.send_and_recv("decline_invitation", conversation_id=conv_id) + if resp["status"] == "ok": + return True, "OK" + return False, resp["data"]["message"] + + async def list_invitations(self) -> list[dict]: + """List pending group invitations.""" + resp = await self.send_and_recv("list_invitations") + if resp["status"] == "ok": + return resp["data"]["invitations"] + return [] + + async def list_conversations(self) -> list[dict]: + resp = await self.send_and_recv("list_conversations") + if resp["status"] == "ok": + return resp["data"]["conversations"] + return [] + + async def find_or_create_conversation(self, email: str) -> tuple[str | None, str]: + resp = await self.send_and_recv("find_conversation", email=email) + if resp["status"] != "ok": + return None, resp["data"]["message"] + conv_id = resp["data"]["conversation_id"] + if conv_id: + return conv_id, "OK" + return await self.create_conversation([email]) + + # ------------------------------------------------------------------ + # Send message + # ------------------------------------------------------------------ + + def _is_group(self, members: list[dict]) -> bool: + return len(members) > 2 + + async def send_message(self, conv_id: str, text: str, members: list[dict], + reply_to: str | None = None) -> tuple[bool, str | dict]: + """Encrypt and send a message. DM: per-recipient Double Ratchet. Group: Sender Keys. + + Returns (True, msg_dict) on success or (False, error_string) on failure. + msg_dict contains the full decrypted payload ready for display. + """ + my_user_id = self.session["user_id"] + + # Build plaintext payload + payload = { + "sender": self.username, + "text": text, + "reply_to": reply_to, + "timestamp": datetime.now(timezone.utc).isoformat(), + } + plaintext = pad_plaintext(json.dumps(payload, ensure_ascii=False).encode("utf-8")) + + if self._is_group(members): + return await self._send_group_message(conv_id, plaintext, members, payload) + else: + return await self._send_dm(conv_id, plaintext, members, payload) + + async def _send_dm(self, conv_id: str, plaintext: bytes, members: list[dict], + payload: dict | None = None) -> tuple[bool, str | dict]: + """Encrypt DM with per-device Double Ratchet.""" + my_user_id = self.session["user_id"] + recipients = [] + first_ratchet_header = None + + for member in members: + uid = member.get("user_id") + if not uid or uid == my_user_id: + continue + + # Get all device bundles for this user + try: + device_bundles = await self._get_device_bundles(uid) + self._logger.debug("Got %d device bundles for %s", len(device_bundles), uid) + except Exception as e: + self._logger.warning("Failed to get device bundles for %s: %s", uid, e) + device_bundles = [] + + if not device_bundles: + # Fallback: try single session (legacy peer) + ratchet = await self._get_or_create_session(uid) + result = ratchet.encrypt(plaintext) + x3dh_hdr = getattr(ratchet, "_x3dh_header", None) + if x3dh_hdr: + delattr(ratchet, "_x3dh_header") + entry = { + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + "ratchet_header": result["header"], + } + if x3dh_hdr: + entry["x3dh_header"] = x3dh_hdr + recipients.append(entry) + if first_ratchet_header is None: + first_ratchet_header = result["header"] + _save_session(self.email, uid, ratchet, self._local_key) + continue + + for bundle in device_bundles: + dev_id = bundle.get("device_id") + ratchet = await self._get_or_create_session(uid, peer_device_id=dev_id, + bundle=bundle) + result = ratchet.encrypt(plaintext) + x3dh_hdr = getattr(ratchet, "_x3dh_header", None) + if x3dh_hdr: + delattr(ratchet, "_x3dh_header") + + entry = { + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + "ratchet_header": result["header"], + } + if dev_id: + entry["device_id"] = dev_id + if x3dh_hdr: + entry["x3dh_header"] = x3dh_hdr + recipients.append(entry) + + if first_ratchet_header is None: + first_ratchet_header = result["header"] + + _save_session(self.email, uid, ratchet, self._local_key, + peer_device_id=dev_id) + + # Encrypt self-copy with static key derived from identity (not ratchet) + # Uses SELF_DEVICE_ID so all own devices can read it + self_key = derive_self_encryption_key(self.identity_private) + _, self_nonce, self_ct, self_tag = aes_encrypt(plaintext, key=self_key) + recipients.append({ + "user_id": my_user_id, + "encrypted_content": encode_binary(self_ct + self_tag), + "nonce": encode_binary(self_nonce), + "ratchet_header": {"self": True}, + }) + + if not recipients: + return False, "No recipients." + + kwargs = { + "conversation_id": conv_id, + "ratchet_header": first_ratchet_header, + "recipients": recipients, + } + + resp = await self.send_and_recv("send_message", **kwargs) + if resp["status"] == "ok": + msg_data = resp.get("data", {}) + if payload is not None: + result = { + **payload, + "message_id": msg_data.get("message_id", ""), + "created_at": msg_data.get("created_at", ""), + "sender_id": self.session["user_id"], + "conversation_id": conv_id, + "read_by": [], + } + _save_message_to_cache(self.email, conv_id, result["message_id"], result, self._cache_key) + return True, result + return True, "Message sent." + return False, resp["data"]["message"] + + async def _send_group_message(self, conv_id: str, plaintext: bytes, + members: list[dict], + payload: dict | None = None) -> tuple[bool, str | dict]: + """Encrypt group message with Sender Keys.""" + my_user_id = self.session["user_id"] + + # Get or create sender key for this group + sk = self.sender_key_states.get(conv_id) + if not sk: + sk = _load_sender_key_state(self.email, conv_id, self._local_key) + if not sk: + sk = SenderKeyState() + self.sender_key_states[conv_id] = sk + _save_sender_key_state(self.email, conv_id, sk, self._local_key) + # Distribute sender key to all members via pairwise ratchet + await self._distribute_sender_key(conv_id, members, sk) + + self.sender_key_states[conv_id] = sk + + # Encrypt with sender key + result = sk.encrypt(plaintext) + _save_sender_key_state(self.email, conv_id, sk, self._local_key) + + # Build per-recipient entries (same ciphertext for all except self) + recipients = [] + for member in members: + uid = member.get("user_id") + if not uid or uid == my_user_id: + continue + recipients.append({ + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + }) + + # Self-encrypted copy (so other devices + history fetch can decrypt) + self_key = derive_self_encryption_key(self.identity_private) + _, self_nonce, self_ct, self_tag = aes_encrypt(plaintext, key=self_key) + recipients.append({ + "user_id": my_user_id, + "encrypted_content": encode_binary(self_ct + self_tag), + "nonce": encode_binary(self_nonce), + "ratchet_header": {"self": True}, + }) + + ratchet_header = {"dh_pub": "00" * 32, "n": 0, "pn": 0} # Dummy for groups + + kwargs = { + "conversation_id": conv_id, + "ratchet_header": ratchet_header, + "recipients": recipients, + "sender_chain_id": encode_binary(bytes.fromhex(result["chain_id"])), + "sender_chain_n": result["n"], + } + + resp = await self.send_and_recv("send_message", **kwargs) + if resp["status"] == "ok": + msg_data = resp.get("data", {}) + if payload is not None: + result_msg = { + **payload, + "message_id": msg_data.get("message_id", ""), + "created_at": msg_data.get("created_at", ""), + "sender_id": self.session["user_id"], + "conversation_id": conv_id, + "read_by": [], + } + _save_message_to_cache(self.email, conv_id, result_msg["message_id"], result_msg, self._cache_key) + return True, result_msg + return True, "Message sent." + return False, resp["data"]["message"] + + async def _distribute_sender_key(self, conv_id: str, members: list[dict], + sk: SenderKeyState): + """Send own sender key to all group members via pairwise Double Ratchet (per-device).""" + my_user_id = self.session["user_id"] + exported_key = sk.export_key() + + # Build a special "sender_key_distribution" payload + payload = { + "sender": self.username, + "text": "", + "reply_to": None, + "timestamp": datetime.now(timezone.utc).isoformat(), + "_sender_key": { + "conv_id": conv_id, + "key": encode_binary(exported_key), + "sender_device_id": self.device_id, + }, + } + plaintext = pad_plaintext(json.dumps(payload, ensure_ascii=False).encode("utf-8")) + + # Send as DM to each member's devices (per-device encryption) + for member in members: + uid = member.get("user_id") + if not uid or uid == my_user_id: + continue + + try: + # Get all device bundles for this user + try: + device_bundles = await self._get_device_bundles(uid) + except Exception: + device_bundles = [] + + if not device_bundles: + # Fallback: legacy single-device + ratchet = await self._get_or_create_session(uid) + result = ratchet.encrypt(plaintext) + x3dh_header = getattr(ratchet, "_x3dh_header", None) + if x3dh_header: + delattr(ratchet, "_x3dh_header") + + recipient_entry = { + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + "ratchet_header": result["header"], + } + if x3dh_header: + recipient_entry["x3dh_header"] = x3dh_header + kwargs = { + "conversation_id": conv_id, + "ratchet_header": result["header"], + "recipients": [recipient_entry], + } + await self.send_and_recv("send_message", **kwargs) + _save_session(self.email, uid, ratchet, self._local_key) + else: + # Per-device encryption + recipients = [] + first_rh = None + for bundle in device_bundles: + dev_id = bundle.get("device_id") + ratchet = await self._get_or_create_session(uid, peer_device_id=dev_id, + bundle=bundle) + result = ratchet.encrypt(plaintext) + x3dh_header = getattr(ratchet, "_x3dh_header", None) + if x3dh_header: + delattr(ratchet, "_x3dh_header") + + entry = { + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + "ratchet_header": result["header"], + } + if dev_id: + entry["device_id"] = dev_id + if x3dh_header: + entry["x3dh_header"] = x3dh_header + recipients.append(entry) + if first_rh is None: + first_rh = result["header"] + _save_session(self.email, uid, ratchet, self._local_key, + peer_device_id=dev_id) + + kwargs = { + "conversation_id": conv_id, + "ratchet_header": first_rh, + "recipients": recipients, + } + await self.send_and_recv("send_message", **kwargs) + except Exception as e: + self._logger.warning("Failed to distribute sender key to %s: %s", uid, e) + + # ------------------------------------------------------------------ + # Decrypt messages + # ------------------------------------------------------------------ + + def _decrypt_message(self, msg_data: dict) -> dict: + """Decrypt a single message (DM or group).""" + # Check for self-encrypted marker FIRST — after re-encryption, + # group messages will have {"self": true} ratchet_header but still + # have sender_chain_id at message level. + rh = msg_data.get("ratchet_header", {}) + if isinstance(rh, dict) and rh.get("self"): + return self._decrypt_dm(msg_data) + + if msg_data.get("sender_chain_id"): + return self._decrypt_group(msg_data) + else: + return self._decrypt_dm(msg_data) + + def _decrypt_dm(self, msg_data: dict) -> dict: + """Decrypt DM using Double Ratchet with sender, or static key for self-copies.""" + sender_id = msg_data.get("sender_id", "") + sender_device_id = msg_data.get("sender_device_id") + ratchet_header = msg_data.get("ratchet_header", {}) + ct_b64 = msg_data.get("encrypted_content", "") + nonce_b64 = msg_data.get("nonce", "") + + if not ct_b64 or not nonce_b64: + raise ValueError("Missing ciphertext or nonce") + + ciphertext = decode_binary(ct_b64) + nonce = decode_binary(nonce_b64) + + # Self-encrypted message (own sent message copy) + if isinstance(ratchet_header, dict) and ratchet_header.get("self"): + self_key = derive_self_encryption_key(self.identity_private) + ct = ciphertext[:-16] + tag = ciphertext[-16:] + plaintext = aes_decrypt(self_key, nonce, ct, tag) + else: + x3dh_header = msg_data.get("x3dh_header") + + # Session key: "sender_id:sender_device_id" or just "sender_id" for legacy + session_key = f"{sender_id}:{sender_device_id}" if sender_device_id else sender_id + + # Try to load existing session + ratchet = self.sessions.get(session_key) + if not ratchet: + ratchet = _load_session(self.email, sender_id, self._local_key, + peer_device_id=sender_device_id) + if ratchet: + self.sessions[session_key] = ratchet + + if ratchet and not x3dh_header: + # Normal case: existing session, no X3DH header + plaintext = ratchet.decrypt(ratchet_header, ciphertext, nonce) + _save_session(self.email, sender_id, ratchet, self._local_key, + peer_device_id=sender_device_id) + elif x3dh_header: + if ratchet: + # Existing session + X3DH header: sender may have reset. + backup = ratchet.export_state() + try: + plaintext = ratchet.decrypt(ratchet_header, ciphertext, nonce) + _save_session(self.email, sender_id, ratchet, self._local_key, + peer_device_id=sender_device_id) + except Exception: + restored = DoubleRatchet.import_state(backup) + self.sessions[session_key] = restored + _save_session(self.email, sender_id, restored, self._local_key, + peer_device_id=sender_device_id) + ratchet = self._process_x3dh_header(sender_id, x3dh_header, + sender_device_id=sender_device_id) + try: + plaintext = ratchet.decrypt(ratchet_header, ciphertext, nonce) + except Exception: + if self._prev_spk_private: + ratchet = self._process_x3dh_header( + sender_id, x3dh_header, + sender_device_id=sender_device_id, + spk_override=self._prev_spk_private) + plaintext = ratchet.decrypt(ratchet_header, ciphertext, nonce) + else: + raise + _save_session(self.email, sender_id, ratchet, self._local_key, + peer_device_id=sender_device_id) + else: + ratchet = self._process_x3dh_header(sender_id, x3dh_header, + sender_device_id=sender_device_id) + try: + plaintext = ratchet.decrypt(ratchet_header, ciphertext, nonce) + except Exception: + if self._prev_spk_private: + ratchet = self._process_x3dh_header( + sender_id, x3dh_header, + sender_device_id=sender_device_id, + spk_override=self._prev_spk_private) + plaintext = ratchet.decrypt(ratchet_header, ciphertext, nonce) + else: + raise + _save_session(self.email, sender_id, ratchet, self._local_key, + peer_device_id=sender_device_id) + else: + raise ValueError(f"No session for sender {sender_id}") + + plaintext = unpad_plaintext(plaintext) + payload = json.loads(plaintext) + + # Handle sender key distribution messages + if "_sender_key" in payload: + sk_data = payload["_sender_key"] + sk_conv_id = sk_data["conv_id"] + sk_key = decode_binary(sk_data["key"]) + sk_sender_device_id = sk_data.get("sender_device_id") + recv_sk = SenderKeyState.from_key(sk_key) + if sk_sender_device_id: + cache_key = f"{sk_conv_id}:{sender_id}:{sk_sender_device_id}" + else: + cache_key = f"{sk_conv_id}:{sender_id}" + self.recv_sender_keys[cache_key] = recv_sk + _save_recv_sender_key(self.email, sk_conv_id, sender_id, recv_sk, self._local_key, + sender_device_id=sk_sender_device_id) + # Return empty — this is a control message, not user-visible + return None + + return payload + + def _decrypt_group(self, msg_data: dict) -> dict: + """Decrypt group message using sender's Sender Key.""" + sender_id = msg_data.get("sender_id", "") + sender_device_id = msg_data.get("sender_device_id") + conv_id = msg_data.get("conversation_id", "") + chain_id_b64 = msg_data.get("sender_chain_id", "") + chain_n = msg_data.get("sender_chain_n", 0) + ct_b64 = msg_data.get("encrypted_content", "") + nonce_b64 = msg_data.get("nonce", "") + + if not ct_b64 or not nonce_b64 or not chain_id_b64: + raise ValueError("Missing group message fields") + + ciphertext = decode_binary(ct_b64) + nonce = decode_binary(nonce_b64) + chain_id = decode_binary(chain_id_b64) + + my_user_id = self.session["user_id"] + + # If we sent this message, use our own sender key + if sender_id == my_user_id: + sk = self.sender_key_states.get(conv_id) + if not sk: + sk = _load_sender_key_state(self.email, conv_id, self._local_key) + if sk: + self.sender_key_states[conv_id] = sk + if not sk: + raise ValueError("Own sender key not found") + # For our own messages, we can't decrypt from sender key (it's already advanced) + # Return a placeholder — the server echoed our ciphertext + raise ValueError("Cannot decrypt own group message from sender key") + + # Use received sender key — try with sender_device_id first, fall back to without + sk = None + if sender_device_id: + cache_key = f"{conv_id}:{sender_id}:{sender_device_id}" + sk = self.recv_sender_keys.get(cache_key) + if not sk: + sk = _load_recv_sender_key(self.email, conv_id, sender_id, self._local_key, + sender_device_id=sender_device_id) + if sk: + self.recv_sender_keys[cache_key] = sk + + if not sk: + # Fallback: try without device_id (legacy or same-device) + cache_key = f"{conv_id}:{sender_id}" + sk = self.recv_sender_keys.get(cache_key) + if not sk: + sk = _load_recv_sender_key(self.email, conv_id, sender_id, self._local_key) + if sk: + self.recv_sender_keys[cache_key] = sk + + if not sk: + raise ValueError(f"No sender key for {sender_id} in conversation {conv_id}") + + plaintext = unpad_plaintext(sk.decrypt(chain_id.hex(), chain_n, ciphertext, nonce)) + _save_recv_sender_key(self.email, conv_id, sender_id, sk, self._local_key, + sender_device_id=sender_device_id) + + return json.loads(plaintext) + + # ------------------------------------------------------------------ + # Get/decrypt messages (batch) + # ------------------------------------------------------------------ + + async def get_messages(self, conv_id: str, limit: int = 50, offset: int = 0) -> list[dict]: + cache = _load_message_cache(self.email, conv_id, self._cache_key) + my_user_id = self.session["user_id"] if self.session else "" + + # Incremental sync: use stored server timestamp from last successful fetch. + after_ts = None + if cache and offset == 0: + after_ts = cache.get("__last_server_ts", {}).get("ts") + + req_params = {"conversation_id": conv_id, "limit": limit, "offset": offset} + if after_ts: + req_params["after_ts"] = after_ts + resp = await self.send_and_recv("get_messages", **req_params) + + if resp["status"] != "ok": + # Offline fallback: return from cache if available + if cache and offset == 0: + return self._build_from_cache(cache) + return [] + + raw_messages = resp["data"]["messages"] + raw_messages.reverse() # Server returns DESC, reverse to ASC + + # Save latest server timestamp for next incremental sync + if raw_messages: + # raw_messages are now ASC; last one is newest + newest_ts = raw_messages[-1].get("created_at", "") + if newest_ts: + cache["__last_server_ts"] = {"ts": newest_ts} + _save_message_to_cache(self.email, conv_id, "__last_server_ts", + {"ts": newest_ts}, cache_key=self._cache_key) + + # Decrypt new messages from server + new_decrypted = self._decrypt_raw_messages(raw_messages, cache, conv_id, my_user_id) + + # Confirm delivery for messages from others (fire-and-forget) + deliver_ids = [m["message_id"] for m in new_decrypted + if m.get("sender_id") and m["sender_id"] != my_user_id + and not m.get("deleted")] + if deliver_ids: + asyncio.ensure_future(self.confirm_delivery(conv_id, deliver_ids)) + + # Mark entire conversation as read (bulk — server handles filtering) + await self.mark_conversation_read(conv_id) + + # Flush self-encryption queue in background + if self._pending_self_encrypt: + asyncio.ensure_future(self._flush_self_encrypt()) + + if after_ts: + # Incremental: sync deletions, then build from cache + try: + del_resp = await self.send_and_recv("get_deleted_since", + conversation_id=conv_id, since=after_ts) + if del_resp.get("status") == "ok": + for del_id in del_resp.get("data", {}).get("message_ids", []): + cache.pop(del_id, None) + _save_message_to_cache(self.email, conv_id, del_id, {"deleted": True}, + cache_key=self._cache_key) + except Exception: + pass + return self._build_from_cache(cache) + + return new_decrypted + + def _build_from_cache(self, cache: dict) -> list[dict]: + """Build sorted message list from local cache (all messages).""" + messages = [] + for msg_id, p in cache.items(): + if p.get("_control") or msg_id.startswith("__"): + continue + entry = dict(p) + entry.setdefault("message_id", msg_id) + entry.setdefault("read_by", []) + entry.setdefault("delivered_to", []) + messages.append(entry) + messages.sort(key=lambda m: m.get("created_at", "")) + return messages + + def _decrypt_raw_messages(self, raw_messages: list, cache: dict, + conv_id: str, my_user_id: str) -> list[dict]: + """Decrypt server messages, update cache. Returns list of decrypted dicts.""" + decrypted = [] + for m in raw_messages: + msg_id = m["message_id"] + + if m.get("deleted_at"): + decrypted.append({ + "message_id": msg_id, + "sender": "", + "text": "", + "created_at": m["created_at"], + "read_by": [], + "sender_id": m.get("sender_id", ""), + "deleted": True, + }) + cache[msg_id] = {"deleted": True, "created_at": m["created_at"]} + continue + + # Check local cache first (ratchet keys are one-time use) + cached = cache.get(msg_id) + if cached and not cached.get("_control"): + cached["read_by"] = m.get("read_by", []) + cached["delivered_to"] = m.get("delivered_to", []) + cached["created_at"] = m["created_at"] + if m.get("reactions"): + cached["reactions"] = m["reactions"] + if m.get("pinned_at"): + cached["pinned_at"] = m["pinned_at"] + cached["pinned_by"] = m.get("pinned_by", "") + else: + cached.pop("pinned_at", None) + cached.pop("pinned_by", None) + decrypted.append(cached) + continue + if cached and cached.get("_control"): + continue + + try: + msg_data = { + "sender_id": m.get("sender_id", ""), + "sender_device_id": m.get("sender_device_id"), + "conversation_id": conv_id, + "ratchet_header": m.get("ratchet_header", {}), + "encrypted_content": m.get("encrypted_content", ""), + "nonce": m.get("nonce", ""), + "x3dh_header": m.get("x3dh_header"), + "sender_chain_id": m.get("sender_chain_id"), + "sender_chain_n": m.get("sender_chain_n"), + } + payload = self._decrypt_message(msg_data) + if payload is None: + _save_message_to_cache(self.email, conv_id, msg_id, {"_control": True}, + cache_key=self._cache_key) + cache[msg_id] = {"_control": True} + continue + payload["message_id"] = msg_id + payload["created_at"] = m["created_at"] + payload["read_by"] = m.get("read_by", []) + payload["delivered_to"] = m.get("delivered_to", []) + payload["sender_id"] = m.get("sender_id", "") + if m.get("reactions"): + payload["reactions"] = m["reactions"] + if m.get("pinned_at"): + payload["pinned_at"] = m["pinned_at"] + payload["pinned_by"] = m.get("pinned_by", "") + decrypted.append(payload) + _save_message_to_cache(self.email, conv_id, msg_id, payload, + cache_key=self._cache_key) + cache[msg_id] = payload + if m.get("sender_id", "") != my_user_id: + self._pending_self_encrypt.append({ + "message_id": msg_id, + "payload": {k: v for k, v in payload.items() + if k not in ("message_id", "created_at", "read_by", + "delivered_to", "sender_id", "deleted")}, + }) + except Exception as e: + decrypted.append({ + "message_id": msg_id, + "sender": "???", + "text": f"[Decryption failed: {e}]", + "created_at": m["created_at"], + "read_by": [], + }) + return decrypted + + async def _flush_self_encrypt(self): + """Upload self-encrypted copies of received messages for multi-device access.""" + if not self._pending_self_encrypt or not self.identity_private: + return + self_key = derive_self_encryption_key(self.identity_private) + updates = [] + for item in list(self._pending_self_encrypt): + try: + plaintext = json.dumps(item["payload"], ensure_ascii=False).encode("utf-8") + _, nonce, ct, tag = aes_encrypt(plaintext, key=self_key) + updates.append({ + "message_id": item["message_id"], + "encrypted_content": encode_binary(ct + tag), + "nonce": encode_binary(nonce), + }) + except Exception: + pass + self._pending_self_encrypt.clear() + if updates: + try: + for i in range(0, len(updates), 500): + batch = updates[i:i + 500] + await self.send_and_recv("reencrypt_messages", updates=batch) + except Exception as e: + self._logger.warning("Failed to self-encrypt received messages: %s", e) + + async def mark_read(self, conv_id: str, message_ids: list[str]): + if not message_ids: + return + await self.send_and_recv("mark_read", conversation_id=conv_id, message_ids=message_ids) + + async def mark_conversation_read(self, conv_id: str): + """Mark ALL unread messages in a conversation as read (server-side bulk).""" + try: + await self.send_and_recv("mark_conversation_read", conversation_id=conv_id) + except Exception: + pass # non-critical — don't fail message loading + + async def confirm_delivery(self, conv_id: str, message_ids: list[str]): + """Confirm delivery of messages (fire-and-forget, non-critical).""" + if not message_ids: + return + try: + await self.send_and_recv("confirm_delivery", + conversation_id=conv_id, message_ids=message_ids) + except Exception: + pass # non-critical + + def search_messages(self, conv_id: str, query: str) -> list[dict]: + """Search cached messages in a conversation. Returns matching messages.""" + cache = _load_message_cache(self.email, conv_id, self._cache_key) + query_lower = query.lower() + results = [] + for msg_id, payload in cache.items(): + if payload.get("deleted") or payload.get("_control") or payload.get("_sender_key"): + continue + text = payload.get("text", "") + if query_lower in text.lower(): + entry = dict(payload) + entry["message_id"] = msg_id + results.append(entry) + results.sort(key=lambda m: m.get("created_at", "")) + return results + + async def reset_session(self, peer_user_id: str, peer_device_id: str | None = None): + """Delete local session and notify peer to do the same.""" + if peer_device_id: + session_key = f"{peer_user_id}:{peer_device_id}" + else: + session_key = peer_user_id + self.sessions.pop(session_key, None) + _delete_session_file(self.email, peer_user_id, peer_device_id) + await self.send_and_recv("session_reset", + peer_user_id=peer_user_id, + peer_device_id=peer_device_id or "") + + def handle_session_reset_notification(self, from_user_id: str, from_device_id: str | None = None): + """Handle incoming session reset notification — delete the matching session.""" + if from_device_id: + session_key = f"{from_user_id}:{from_device_id}" + else: + session_key = from_user_id + self.sessions.pop(session_key, None) + _delete_session_file(self.email, from_user_id, from_device_id) + + # ------------------------------------------------------------------ + # Local message cache updates + # ------------------------------------------------------------------ + + def load_message_cache(self, conv_id: str) -> dict: + """Load cached messages for a conversation. Returns {msg_id: payload}.""" + if not self.email: + return {} + return _load_message_cache(self.email, conv_id, self._cache_key) + + def update_message_in_cache(self, conv_id: str, message_id: str, updates: dict): + """Update fields of a cached message on disk (synchronous).""" + if not self.email: + return + cache = _load_message_cache(self.email, conv_id, self._cache_key) + if message_id not in cache or cache[message_id].get("_control"): + return + for key, value in updates.items(): + if value is None: + cache[message_id].pop(key, None) + else: + cache[message_id][key] = value + d = get_key_dir(self.email) / "message_cache" + if self._cache_key: + _save_message_cache_full(d, conv_id, cache, self._cache_key) + + # ------------------------------------------------------------------ + # Reactions, Pins, Forwarding + # ------------------------------------------------------------------ + + async def react_message(self, message_id: str, reaction: str, action: str = "add") -> tuple[bool, str]: + """Add or remove a reaction on a message.""" + resp = await self.send_and_recv("react_message", + message_id=message_id, reaction=reaction, action=action) + if resp["status"] == "ok": + return True, "OK" + return False, resp.get("data", {}).get("message", "Failed") + + async def pin_message(self, message_id: str, conversation_id: str, action: str = "pin") -> tuple[bool, str]: + """Pin or unpin a message.""" + resp = await self.send_and_recv("pin_message", + message_id=message_id, conversation_id=conversation_id, action=action) + if resp["status"] == "ok": + return True, "OK" + return False, resp.get("data", {}).get("message", "Failed") + + async def get_pinned_messages(self, conversation_id: str) -> list[dict]: + """Get list of pinned messages for a conversation.""" + resp = await self.send_and_recv("get_pinned_messages", conversation_id=conversation_id) + if resp["status"] == "ok": + return resp["data"].get("messages", []) + return [] + + async def forward_message(self, target_conv_id: str, original_msg: dict, + target_members: list[dict]) -> tuple[bool, str | dict]: + """Forward a message to another conversation.""" + text = original_msg.get("text", "") + + payload = { + "sender": self.username, + "text": text, + "forwarded_from": { + "sender": original_msg.get("sender", ""), + "conversation_id": original_msg.get("conversation_id", ""), + "message_id": original_msg.get("message_id", ""), + }, + "timestamp": datetime.now(timezone.utc).isoformat(), + } + # Forward image/file metadata (the encrypted blob is already on the server) + if original_msg.get("image"): + payload["image"] = original_msg["image"] + if not text: + payload["text"] = "" + if original_msg.get("file"): + payload["file"] = original_msg["file"] + if not text: + payload["text"] = "" + plaintext = pad_plaintext(json.dumps(payload, ensure_ascii=False).encode("utf-8")) + + if self._is_group(target_members): + return await self._send_group_message(target_conv_id, plaintext, target_members, payload) + else: + return await self._send_dm(target_conv_id, plaintext, target_members, payload) + + # ------------------------------------------------------------------ + # Decrypt notification + # ------------------------------------------------------------------ + + def decrypt_notification(self, notif_data: dict) -> dict | None: + """Decrypt a new_message notification. Returns parsed payload or None. + + Supports new multi-device format (device_entries array) and legacy flat format. + """ + try: + conv_id = notif_data.get("conversation_id", "") + msg_id = notif_data.get("message_id", "") + sender_id = notif_data.get("sender_id", "") + sender_device_id = notif_data.get("sender_device_id") + my_user_id = self.session["user_id"] if self.session else "" + + # Extract per-device encrypted content from device_entries or flat fields + encrypted_content = "" + nonce = "" + ratchet_header = {} + x3dh_header = None + + device_entries = notif_data.get("device_entries") + if device_entries: + # Multi-device format: pick entry matching our device_id or SELF_DEVICE_ID + chosen = None + self_entry = None + for entry in device_entries: + eid = entry.get("device_id", "") + if eid == self.device_id: + chosen = entry + break + if eid == "00000000-0000-0000-0000-000000000000": + self_entry = entry + + # If sender is us, prefer self-encrypted entry + if sender_id == my_user_id: + chosen = self_entry or chosen + elif not chosen: + chosen = self_entry + + if not chosen: + self._logger.warning("No matching device_entry for device %s", self.device_id) + return None + + encrypted_content = chosen.get("encrypted_content", "") + nonce = chosen.get("nonce", "") + ratchet_header = chosen.get("ratchet_header") or notif_data.get("ratchet_header", {}) + x3dh_header = chosen.get("x3dh_header") or notif_data.get("x3dh_header") + else: + # Legacy flat format + encrypted_content = notif_data.get("encrypted_content", "") + nonce = notif_data.get("nonce", "") + ratchet_header = notif_data.get("ratchet_header", {}) + x3dh_header = notif_data.get("x3dh_header") + + msg_data = { + "sender_id": sender_id, + "sender_device_id": sender_device_id, + "conversation_id": conv_id, + "ratchet_header": ratchet_header, + "encrypted_content": encrypted_content, + "nonce": nonce, + "x3dh_header": x3dh_header, + "sender_chain_id": notif_data.get("sender_chain_id"), + "sender_chain_n": notif_data.get("sender_chain_n"), + } + payload = self._decrypt_message(msg_data) + if payload is None: + # Cache control message so get_messages skips it + if msg_id and conv_id: + _save_message_to_cache(self.email, conv_id, msg_id, {"_control": True}, + cache_key=self._cache_key) + return None + payload["conversation_id"] = conv_id + payload["message_id"] = msg_id + payload["sender_id"] = sender_id + # Use server-compatible timestamp (no timezone suffix) for cache consistency + _ts = payload.get("timestamp", "") + if _ts: + # Strip timezone suffix (+00:00 or Z) to match server DATETIME format + _ts = _ts.replace("+00:00", "").replace("Z", "") + # Strip microseconds if present + if "." in _ts: + _ts = _ts[:_ts.index(".")] + payload["created_at"] = _ts + payload["read_by"] = [] + payload["delivered_to"] = [] + # Cache so get_messages doesn't re-decrypt (ratchet keys are one-time) + if msg_id and conv_id: + _save_message_to_cache(self.email, conv_id, msg_id, payload, + cache_key=self._cache_key) + # Queue self-encryption for received messages (multi-device access) + if sender_id != my_user_id and msg_id: + self._pending_self_encrypt.append({ + "message_id": msg_id, + "payload": {k: v for k, v in payload.items() + if k not in ("conversation_id", "message_id", "created_at", + "read_by", "delivered_to", "sender_id", "deleted")}, + }) + return payload + except IdentityKeyChanged: + raise # Must propagate to caller for key-change UI + except Exception as e: + self._logger.warning("Failed to decrypt notification: %s", e) + return None + + # ------------------------------------------------------------------ + # Delete message + # ------------------------------------------------------------------ + + async def delete_message(self, message_id: str) -> tuple[bool, str]: + resp = await self.send_and_recv("delete_message", message_id=message_id) + if resp["status"] == "ok": + return True, "Message deleted." + return False, resp["data"]["message"] + + # ------------------------------------------------------------------ + # Image sharing + # ------------------------------------------------------------------ + + async def send_image(self, conv_id: str, image_path: str, members: list[dict], + reply_to: str | None = None) -> tuple[bool, str]: + """Encrypt and upload an image, then send as a message.""" + try: + from PIL import Image + import io + except ImportError: + return False, "Pillow is required for image sharing. Install with: pip install Pillow" + + path = Path(image_path) + if not path.exists(): + return False, "File not found." + + try: + img = Image.open(path) + img.load() + except Exception as e: + return False, f"Cannot open image: {e}" + + # Try sending in original format/quality first + original_format = img.format or "JPEG" + if original_format.upper() not in ("JPEG", "PNG", "WEBP", "GIF", "BMP"): + original_format = "JPEG" + + # Read raw file bytes for original quality + image_bytes = path.read_bytes() + + # If encrypted size exceeds limit, progressively downscale + if MAX_IMAGE_BYTES > 0: + img_aes_key_test, _, ct_test, tag_test = aes_encrypt(image_bytes) + if len(ct_test) + len(tag_test) > MAX_IMAGE_BYTES: + # Convert to RGB for JPEG compression + if img.mode not in ("RGB", "L"): + img = img.convert("RGB") + # Try JPEG at high quality first, then reduce quality/dimensions + for quality in (92, 85, 75, 60): + buf = io.BytesIO() + img.save(buf, format="JPEG", quality=quality) + image_bytes = buf.getvalue() + _, _, ct_test, tag_test = aes_encrypt(image_bytes) + if len(ct_test) + len(tag_test) <= MAX_IMAGE_BYTES: + break + else: + # Still too large — downscale dimensions + for max_dim in (3840, 2560, 1920, 1280): + if max(img.size) > max_dim: + img.thumbnail((max_dim, max_dim), Image.Resampling.LANCZOS) + buf = io.BytesIO() + img.save(buf, format="JPEG", quality=75) + image_bytes = buf.getvalue() + _, _, ct_test, tag_test = aes_encrypt(image_bytes) + if len(ct_test) + len(tag_test) <= MAX_IMAGE_BYTES: + break + + # Generate thumbnail + thumb = img.copy() + thumb.thumbnail((200, 200), Image.Resampling.LANCZOS) + if thumb.mode not in ("RGB", "L"): + thumb = thumb.convert("RGB") + thumb_buf = io.BytesIO() + thumb.save(thumb_buf, format="JPEG", quality=60) + thumbnail_b64 = encode_binary(thumb_buf.getvalue()) + + # Encrypt image with AES-256-GCM + img_aes_key, img_iv, img_ct, img_tag = aes_encrypt(image_bytes) + encrypted_image = img_ct + img_tag + + file_id = str(uuid.uuid4()) + file_size = len(encrypted_image) + + # Chunked upload + resp = await self.send_and_recv( + "upload_image_start", + conversation_id=conv_id, + file_id=file_id, + file_size=file_size, + ) + if resp["status"] != "ok": + return False, resp["data"]["message"] + + upload_offset = 0 + while upload_offset < file_size: + chunk = encrypted_image[upload_offset:upload_offset + IMAGE_CHUNK_SIZE] + resp = await self.send_and_recv( + "upload_image_chunk", + file_id=file_id, + data=encode_binary(chunk), + ) + if resp["status"] != "ok": + return False, resp["data"]["message"] + upload_offset += len(chunk) + + resp = await self.send_and_recv("upload_image_end", file_id=file_id) + if resp["status"] != "ok": + return False, resp["data"]["message"] + + # Build message payload with image info + image_info = { + "file_id": file_id, + "aes_key": encode_binary(img_aes_key), + "iv": encode_binary(img_iv), + "thumbnail": thumbnail_b64, + "filename": path.name, + "size": len(image_bytes), + } + + payload = { + "sender": self.username, + "text": "", + "reply_to": reply_to, + "timestamp": datetime.now(timezone.utc).isoformat(), + "image": image_info, + } + plaintext = pad_plaintext(json.dumps(payload, ensure_ascii=False).encode("utf-8")) + + my_user_id = self.session["user_id"] + + if self._is_group(members): + # Group image: use sender key + sk = self.sender_key_states.get(conv_id) + if not sk: + sk = _load_sender_key_state(self.email, conv_id, self._local_key) + if not sk: + sk = SenderKeyState() + self.sender_key_states[conv_id] = sk + _save_sender_key_state(self.email, conv_id, sk, self._local_key) + await self._distribute_sender_key(conv_id, members, sk) + + result = sk.encrypt(plaintext) + _save_sender_key_state(self.email, conv_id, sk, self._local_key) + + recipients = [] + for member in members: + uid = member.get("user_id") + if not uid or uid == my_user_id: + continue + recipients.append({ + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + }) + + # Self-encrypted copy for sender + self_key = derive_self_encryption_key(self.identity_private) + _, self_nonce, self_ct, self_tag = aes_encrypt(plaintext, key=self_key) + recipients.append({ + "user_id": my_user_id, + "encrypted_content": encode_binary(self_ct + self_tag), + "nonce": encode_binary(self_nonce), + "ratchet_header": {"self": True}, + }) + + resp = await self.send_and_recv( + "send_message", + conversation_id=conv_id, + ratchet_header={"dh_pub": "00" * 32, "n": 0, "pn": 0}, + recipients=recipients, + sender_chain_id=encode_binary(bytes.fromhex(result["chain_id"])), + sender_chain_n=result["n"], + image_file_id=file_id, + ) + else: + # DM image: per-device ratchet (same pattern as _send_dm) + recipients = [] + first_rh = None + for member in members: + uid = member.get("user_id") + if not uid or uid == my_user_id: + continue + + try: + device_bundles = await self._get_device_bundles(uid) + except Exception: + device_bundles = [] + + if not device_bundles: + # Fallback: legacy single-device + ratchet = await self._get_or_create_session(uid) + result = ratchet.encrypt(plaintext) + x3dh_h = getattr(ratchet, "_x3dh_header", None) + if x3dh_h: + delattr(ratchet, "_x3dh_header") + entry = { + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + "ratchet_header": result["header"], + } + if x3dh_h: + entry["x3dh_header"] = x3dh_h + recipients.append(entry) + if first_rh is None: + first_rh = result["header"] + _save_session(self.email, uid, ratchet, self._local_key) + else: + for bundle in device_bundles: + dev_id = bundle.get("device_id") + ratchet = await self._get_or_create_session(uid, peer_device_id=dev_id, + bundle=bundle) + result = ratchet.encrypt(plaintext) + x3dh_h = getattr(ratchet, "_x3dh_header", None) + if x3dh_h: + delattr(ratchet, "_x3dh_header") + entry = { + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + "ratchet_header": result["header"], + } + if dev_id: + entry["device_id"] = dev_id + if x3dh_h: + entry["x3dh_header"] = x3dh_h + recipients.append(entry) + if first_rh is None: + first_rh = result["header"] + _save_session(self.email, uid, ratchet, self._local_key, + peer_device_id=dev_id) + + # Encrypt self-copy with static key + self_key = derive_self_encryption_key(self.identity_private) + _, self_nonce, self_ct, self_tag = aes_encrypt(plaintext, key=self_key) + recipients.append({ + "user_id": my_user_id, + "encrypted_content": encode_binary(self_ct + self_tag), + "nonce": encode_binary(self_nonce), + "ratchet_header": {"self": True}, + }) + + resp = await self.send_and_recv( + "send_message", + conversation_id=conv_id, + ratchet_header=first_rh, + recipients=recipients, + image_file_id=file_id, + ) + + if resp["status"] == "ok": + msg_data = resp.get("data", {}) + result_msg = { + **payload, + "message_id": msg_data.get("message_id", ""), + "created_at": msg_data.get("created_at", ""), + "sender_id": self.session["user_id"], + "conversation_id": conv_id, + "read_by": [], + } + _save_message_to_cache(self.email, conv_id, result_msg["message_id"], result_msg, self._cache_key) + return True, result_msg + return False, resp["data"]["message"] + + async def send_file(self, conv_id: str, file_path: str, members: list[dict], + reply_to: str | None = None) -> tuple[bool, str | dict]: + """Encrypt and upload a file, then send as a message.""" + import mimetypes + + path = Path(file_path) + if not path.exists(): + return False, "File not found." + + try: + file_bytes = path.read_bytes() + except Exception as e: + return False, f"Cannot read file: {e}" + + mime_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream" + + # Encrypt file with AES-256-GCM + file_aes_key, file_iv, file_ct, file_tag = aes_encrypt(file_bytes) + encrypted_file = file_ct + file_tag + + file_id = str(uuid.uuid4()) + file_size = len(encrypted_file) + + # Chunked upload (reuse image upload infrastructure with file_type="file") + resp = await self.send_and_recv( + "upload_image_start", + conversation_id=conv_id, + file_id=file_id, + file_size=file_size, + file_type="file", + ) + if resp["status"] != "ok": + return False, resp["data"]["message"] + + upload_offset = 0 + while upload_offset < file_size: + chunk = encrypted_file[upload_offset:upload_offset + IMAGE_CHUNK_SIZE] + resp = await self.send_and_recv( + "upload_image_chunk", + file_id=file_id, + data=encode_binary(chunk), + ) + if resp["status"] != "ok": + return False, resp["data"]["message"] + upload_offset += len(chunk) + + resp = await self.send_and_recv("upload_image_end", file_id=file_id) + if resp["status"] != "ok": + return False, resp["data"]["message"] + + # Build message payload with file info + file_info = { + "file_id": file_id, + "aes_key": encode_binary(file_aes_key), + "iv": encode_binary(file_iv), + "filename": path.name, + "size": len(file_bytes), + "mime_type": mime_type, + } + + payload = { + "sender": self.username, + "text": "", + "reply_to": reply_to, + "timestamp": datetime.now(timezone.utc).isoformat(), + "file": file_info, + } + plaintext = pad_plaintext(json.dumps(payload, ensure_ascii=False).encode("utf-8")) + + my_user_id = self.session["user_id"] + + if self._is_group(members): + sk = self.sender_key_states.get(conv_id) + if not sk: + sk = _load_sender_key_state(self.email, conv_id, self._local_key) + if not sk: + sk = SenderKeyState() + self.sender_key_states[conv_id] = sk + _save_sender_key_state(self.email, conv_id, sk, self._local_key) + await self._distribute_sender_key(conv_id, members, sk) + + result = sk.encrypt(plaintext) + _save_sender_key_state(self.email, conv_id, sk, self._local_key) + + recipients = [] + for member in members: + uid = member.get("user_id") + if not uid or uid == my_user_id: + continue + recipients.append({ + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + }) + + # Self-encrypted copy for sender + self_key = derive_self_encryption_key(self.identity_private) + _, self_nonce, self_ct, self_tag = aes_encrypt(plaintext, key=self_key) + recipients.append({ + "user_id": my_user_id, + "encrypted_content": encode_binary(self_ct + self_tag), + "nonce": encode_binary(self_nonce), + "ratchet_header": {"self": True}, + }) + + resp = await self.send_and_recv( + "send_message", + conversation_id=conv_id, + ratchet_header={"dh_pub": "00" * 32, "n": 0, "pn": 0}, + recipients=recipients, + sender_chain_id=encode_binary(bytes.fromhex(result["chain_id"])), + sender_chain_n=result["n"], + image_file_id=file_id, + ) + else: + # DM file: per-device ratchet (same pattern as _send_dm) + recipients = [] + first_rh = None + for member in members: + uid = member.get("user_id") + if not uid or uid == my_user_id: + continue + + try: + device_bundles = await self._get_device_bundles(uid) + except Exception: + device_bundles = [] + + if not device_bundles: + # Fallback: legacy single-device + ratchet = await self._get_or_create_session(uid) + result = ratchet.encrypt(plaintext) + x3dh_h = getattr(ratchet, "_x3dh_header", None) + if x3dh_h: + delattr(ratchet, "_x3dh_header") + entry = { + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + "ratchet_header": result["header"], + } + if x3dh_h: + entry["x3dh_header"] = x3dh_h + recipients.append(entry) + if first_rh is None: + first_rh = result["header"] + _save_session(self.email, uid, ratchet, self._local_key) + else: + for bundle in device_bundles: + dev_id = bundle.get("device_id") + ratchet = await self._get_or_create_session(uid, peer_device_id=dev_id, + bundle=bundle) + result = ratchet.encrypt(plaintext) + x3dh_h = getattr(ratchet, "_x3dh_header", None) + if x3dh_h: + delattr(ratchet, "_x3dh_header") + entry = { + "user_id": uid, + "encrypted_content": encode_binary(result["ciphertext"]), + "nonce": encode_binary(result["nonce"]), + "ratchet_header": result["header"], + } + if dev_id: + entry["device_id"] = dev_id + if x3dh_h: + entry["x3dh_header"] = x3dh_h + recipients.append(entry) + if first_rh is None: + first_rh = result["header"] + _save_session(self.email, uid, ratchet, self._local_key, + peer_device_id=dev_id) + + # Encrypt self-copy with static key + self_key = derive_self_encryption_key(self.identity_private) + _, self_nonce, self_ct, self_tag = aes_encrypt(plaintext, key=self_key) + recipients.append({ + "user_id": my_user_id, + "encrypted_content": encode_binary(self_ct + self_tag), + "nonce": encode_binary(self_nonce), + "ratchet_header": {"self": True}, + }) + + resp = await self.send_and_recv( + "send_message", + conversation_id=conv_id, + ratchet_header=first_rh, + recipients=recipients, + image_file_id=file_id, + ) + + if resp["status"] == "ok": + msg_data = resp.get("data", {}) + result_msg = { + **payload, + "message_id": msg_data.get("message_id", ""), + "created_at": msg_data.get("created_at", ""), + "sender_id": self.session["user_id"], + "conversation_id": conv_id, + "read_by": [], + } + _save_message_to_cache(self.email, conv_id, result_msg["message_id"], result_msg, self._cache_key) + return True, result_msg + return False, resp["data"]["message"] + + async def download_file(self, file_id: str, file_info: dict) -> bytes | None: + """Download and decrypt a file. Returns decrypted file bytes or None.""" + chunks = [] + offset = 0 + while True: + resp = await self.send_and_recv( + "download_image", + file_id=file_id, + offset=offset, + ) + if resp["status"] != "ok": + return None + data = resp["data"] + chunk = decode_binary(data["data"]) + chunks.append(chunk) + offset += len(chunk) + if data.get("done"): + break + + encrypted_data = b"".join(chunks) + if len(encrypted_data) < 16: + return None + ciphertext = encrypted_data[:-16] + tag = encrypted_data[-16:] + + try: + file_aes_key = decode_binary(file_info["aes_key"]) + iv = decode_binary(file_info["iv"]) + return aes_decrypt(file_aes_key, iv, ciphertext, tag) + except Exception: + return None + + async def download_image(self, file_id: str, image_info: dict) -> bytes | None: + """Download and decrypt an image. Returns decrypted image bytes or None.""" + chunks = [] + offset = 0 + while True: + resp = await self.send_and_recv( + "download_image", + file_id=file_id, + offset=offset, + ) + if resp["status"] != "ok": + return None + data = resp["data"] + chunk = decode_binary(data["data"]) + chunks.append(chunk) + offset += len(chunk) + if data.get("done"): + break + + encrypted_data = b"".join(chunks) + if len(encrypted_data) < 16: + return None + ciphertext = encrypted_data[:-16] + tag = encrypted_data[-16:] + + try: + img_aes_key = decode_binary(image_info["aes_key"]) + iv = decode_binary(image_info["iv"]) + return aes_decrypt(img_aes_key, iv, ciphertext, tag) + except Exception: + return None + + # ------------------------------------------------------------------ + # Re-encrypt history (for device pairing) + # ------------------------------------------------------------------ + + async def reencrypt_history(self): + """Re-encrypt all cached messages with self-encryption key. + + After device pairing, the new device shares the same identity key + but cannot decrypt old messages (Double Ratchet keys are one-time use). + This re-encrypts all cached messages so they can be read using the + self-encryption key derived from the shared identity key. + """ + if not self.identity_private or not self.session: + return + + self_key = derive_self_encryption_key(self.identity_private) + + # Phase 1: Fetch & decrypt all messages to populate cache + # (messages the old device never opened won't be in cache yet) + try: + convs = await self.list_conversations() + total_convs = len(convs) + for ci, conv in enumerate(convs): + cid = conv.get("id") or conv.get("conversation_id") + if not cid: + continue + if self._reencrypt_progress_cb: + self._reencrypt_progress_cb( + f"Fetching messages: {ci + 1}/{total_convs} conversations..." + ) + offset = 0 + while True: + msgs = await self.get_messages(cid, limit=200, offset=offset) + if not msgs or len(msgs) < 200: + break + offset += len(msgs) + except Exception as e: + self._logger.warning("Failed to fetch messages for re-encryption: %s", e) + + # Phase 2: Read cache and re-encrypt + cache_dir = get_key_dir(self.email) / "message_cache" + if not cache_dir.exists(): + self._logger.info("No message cache to re-encrypt.") + return + + all_updates = [] + conv_ids = set() + for f in cache_dir.iterdir(): + if f.suffix in (".json", ".bin"): + conv_ids.add(f.stem) + + total_files = len(conv_ids) + for i, conv_id in enumerate(sorted(conv_ids)): + cache = _load_message_cache(self.email, conv_id, self._cache_key) + if not cache: + continue + + for msg_id, entry in cache.items(): + # Skip control messages (sender key distribution) + if entry.get("_control"): + continue + # Skip entries with no useful content + text = entry.get("text", "") + if not text and not entry.get("image") and not entry.get("file"): + continue + + # Rebuild plaintext from cached payload + payload = {k: v for k, v in entry.items() + if k not in ("message_id", "created_at", "read_by", "sender_id", "deleted")} + plaintext = pad_plaintext(json.dumps(payload, ensure_ascii=False).encode("utf-8")) + + # Re-encrypt with self-encryption key + _, nonce, ct, tag = aes_encrypt(plaintext, key=self_key) + all_updates.append({ + "message_id": msg_id, + "encrypted_content": encode_binary(ct + tag), + "nonce": encode_binary(nonce), + }) + + if self._reencrypt_progress_cb: + self._reencrypt_progress_cb(f"Re-encrypting history: {i + 1}/{total_files} conversations...") + + if not all_updates: + self._logger.info("No messages to re-encrypt.") + return + + # Send in batches of 500 + batch_size = 500 + total = len(all_updates) + for start in range(0, total, batch_size): + batch = all_updates[start:start + batch_size] + resp = await self.send_and_recv("reencrypt_messages", updates=batch) + if resp["status"] != "ok": + self._logger.warning("Re-encrypt batch failed: %s", resp.get("data", {}).get("message", "")) + else: + self._logger.info("Re-encrypted %d/%d messages.", min(start + batch_size, total), total) + + if self._reencrypt_progress_cb: + self._reencrypt_progress_cb(f"Re-encryption complete: {total} messages uploaded.") + + # ------------------------------------------------------------------ + # User Profiles + # ------------------------------------------------------------------ + + async def get_profile(self, user_id: str | None = None) -> dict | None: + """Get user profile. If user_id is None, returns own profile.""" + kwargs = {} + if user_id: + kwargs["user_id"] = user_id + resp = await self.send_and_recv("get_profile", **kwargs) + if resp["status"] == "ok": + return resp["data"] + return None + + async def update_profile(self, **fields) -> tuple[bool, str]: + """Update own profile (phone, location, *_visible).""" + resp = await self.send_and_recv("update_profile", **fields) + if resp["status"] == "ok": + return True, "OK" + return False, resp["data"]["message"] + + async def update_avatar(self, image_data: bytes) -> tuple[bool, str]: + """Upload avatar image.""" + resp = await self.send_and_recv("update_avatar", data=encode_binary(image_data)) + if resp["status"] == "ok": + return True, resp["data"].get("avatar_file", "") + return False, resp["data"]["message"] + + async def get_avatar(self, user_id: str) -> bytes | None: + """Download avatar for a user.""" + resp = await self.send_and_recv("get_avatar", user_id=user_id) + if resp["status"] == "ok": + return decode_binary(resp["data"]["data"]) + return None + + async def update_group_avatar(self, conv_id: str, image_data: bytes) -> tuple[bool, str]: + """Upload avatar for a group conversation.""" + resp = await self.send_and_recv("update_group_avatar", + conversation_id=conv_id, data=encode_binary(image_data)) + if resp["status"] == "ok": + return True, resp["data"].get("avatar_file", "") + return False, resp["data"]["message"] + + async def get_group_avatar(self, conv_id: str) -> bytes | None: + """Download avatar for a group conversation.""" + resp = await self.send_and_recv("get_group_avatar", conversation_id=conv_id) + if resp["status"] == "ok": + return decode_binary(resp["data"]["data"]) + return None + + # ------------------------------------------------------------------ + # Cleanup + # ------------------------------------------------------------------ + + async def close(self): + self.connected = False + if self._listener_task: + self._listener_task.cancel() + if self.raw_writer: + self.raw_writer.close() + + async def reconnect(self): + """Close existing connection and re-establish: connect + re-login using in-memory keys.""" + try: + await self.close() + except Exception: + pass + # Reset reader/writer but keep keys and sessions + self.reader = None + self.writer = None + self.raw_writer = None + self._listener_task = None + self._pending.clear() + self.login_rejected = False + # Drain queues + while not self._response_queue.empty(): + try: + self._response_queue.get_nowait() + except Exception: + break + while not self._notification_queue.empty(): + try: + self._notification_queue.get_nowait() + except Exception: + break + await self.connect() + self._listener_task = asyncio.create_task(self._background_listener()) + if self.email and self.private_key: + # RSA challenge-response login (keys already in memory) + start = await self.send_and_recv("login_start", email=self.email) + if start["status"] == "ok": + challenge = decode_binary(start["data"]["challenge"]) + signature = rsa_sign(self.private_key, challenge) + login_kwargs = { + "email": self.email, + "signature": encode_binary(signature), + "client_version": VERSION, + } + if self.device_id: + login_kwargs["device_id"] = self.device_id + finish = await self.send_and_recv("login_finish", **login_kwargs) + if finish["status"] == "ok": + self.session = finish["data"] + asyncio.create_task(self._ensure_prekeys()) + else: + # Login rejected — keys were likely rotated on another device + self.session = None + self.connected = False + self.login_rejected = True diff --git a/client.py b/client.py new file mode 100644 index 0000000..23ba082 --- /dev/null +++ b/client.py @@ -0,0 +1,899 @@ +"""Interactive CLI client for encrypted chat (X3DH + Double Ratchet).""" + +import asyncio +import getpass +import logging +import os +import re + +from chat_core import ChatClient, IdentityKeyChanged + + +def setup_logging(): + level_name = os.getenv("LOG_LEVEL", "WARNING").upper() + level = getattr(logging, level_name, logging.WARNING) + logging.basicConfig(level=level, format="%(levelname)s: %(message)s") + + +async def prompt(text: str) -> str: + """Non-blocking terminal input.""" + return await asyncio.get_event_loop().run_in_executor(None, lambda: input(text).strip()) + + +async def prompt_password(text: str = "Password: ") -> str: + """Non-blocking hidden password input (M3 fix).""" + return await asyncio.get_event_loop().run_in_executor(None, lambda: getpass.getpass(text)) + + +# M3 fix: strip terminal control/escape sequences from untrusted text +_CONTROL_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]|\x1b\[[0-9;]*[A-Za-z]") + + +def _sanitize_text(s) -> str: + """Remove control characters and ANSI escape sequences.""" + if not isinstance(s, str): + s = str(s) if s is not None else "" + return _CONTROL_RE.sub("", s) + + +def _safe_filename(name: str) -> str: + """Sanitize remote filename: basename only, no path traversal, no NUL.""" + name = os.path.basename(name) + name = name.replace("\x00", "") + if not name or name.startswith("."): + name = "download" + return name + + +def _human_size(n: int) -> str: + if n >= 1024 * 1024: + return f"{n / (1024*1024):.1f} MB" + if n >= 1024: + return f"{n / 1024:.0f} KB" + return f"{n} B" + + +async def _select_conversation(client: ChatClient, label: str = "Select conversation") -> tuple[dict | None, list[dict]]: + """List conversations and let user pick one. Returns (conv, convs) or (None, []).""" + convs = await client.list_conversations() + if not convs: + print("[*] No conversations.") + return None, [] + + def conv_label(c): + if c.get("name"): + return _sanitize_text(c["name"]) + others = [_sanitize_text(m.get("username") or m.get("email") or "?") for m in c["members"] if m.get("email") != client.email] + return ", ".join(others) if others else _sanitize_text(client.username) + + print() + for i, c in enumerate(convs): + print(f" {i+1}) {conv_label(c)}") + choice = await prompt(f"{label}: ") + try: + idx = int(choice) - 1 + if not (0 <= idx < len(convs)): + print("[!] Invalid selection.") + return None, convs + except ValueError: + print("[!] Invalid selection.") + return None, convs + return convs[idx], convs + + +async def interactive_menu(client: ChatClient): + """Interactive terminal menu.""" + while True: + print("\n--- Encrypted Chat ---") + print("1) Send direct message") + print("2) Send to conversation") + print("3) Read messages") + print("4) Create group conversation") + print("5) Add member to group") + print("6) Send image") + print("7) Send file") + print("8) Invitations") + print("9) Leave group") + print("10) Rename group") + print("11) Delete conversation") + print("12) Search messages") + print("13) My profile") + print("14) View user profile") + print("15) Manage devices") + print("16) React to message") + print("17) Pin/Unpin message") + print("18) View pinned messages") + print("19) Forward message") + print("20) Verify contact") + print("21) Show my fingerprint") + print("22) Change password") + print("23) Change username") + print("q) Quit") + + choice = await prompt("> ") + + if choice == "1": + email = await prompt("To (email): ") + if not email: + continue + text = await prompt("Message: ") + if not text: + continue + conv_id, msg = await client.find_or_create_conversation(email) + if not conv_id: + print(f"[!] {msg}") + continue + convs = await client.list_conversations() + members = [] + for c in convs: + if c["conversation_id"] == conv_id: + members = c["members"] + break + try: + ok, result = await client.send_message(conv_id, text, members) + except IdentityKeyChanged as ikc: + print(f"[!] Identity key changed for {ikc.user_id[:8]}. Accept the new key before sending.") + continue + print(f"[{'+'if ok else '!'}] {'Message sent.' if ok else result}") + + elif choice == "2": + conv, _ = await _select_conversation(client) + if not conv: + continue + text = await prompt("Message: ") + if not text: + continue + try: + ok, result = await client.send_message(conv["conversation_id"], text, conv["members"]) + except IdentityKeyChanged as ikc: + print(f"[!] Identity key changed for {ikc.user_id[:8]}. Accept the new key before sending.") + continue + print(f"[{'+'if ok else '!'}] {'Message sent.' if ok else result}") + + elif choice == "3": + conv, _ = await _select_conversation(client) + if not conv: + continue + messages = await client.get_messages(conv["conversation_id"]) + if not messages: + print("[*] No messages.") + continue + _print_messages(messages, client, conv) + + action = await prompt("\nAction (r=reply, d=delete, dl=download file, empty=back): ") + if not action: + continue + if action.lower().startswith("dl"): + await _download_file_action(client, messages) + continue + if action.lower().startswith("d"): + await _delete_message_action(client, messages) + continue + if action.lower().startswith("r"): + reply_choice = await prompt("Reply to message #: ") + else: + reply_choice = action + try: + reply_idx = int(reply_choice) - 1 + if not (0 <= reply_idx < len(messages)): + print("[!] Invalid message number.") + continue + except ValueError: + print("[!] Invalid number.") + continue + reply_to_id = messages[reply_idx]["message_id"] + text = await prompt("Message: ") + if not text: + continue + try: + ok, result = await client.send_message(conv["conversation_id"], text, conv["members"], reply_to=reply_to_id) + except IdentityKeyChanged as ikc: + print(f"[!] Identity key changed for {ikc.user_id[:8]}. Accept the new key before sending.") + continue + print(f"[{'+'if ok else '!'}] {'Message sent.' if ok else result}") + + elif choice == "4": + name = await prompt("Group name (empty for none): ") + members_input = await prompt("Member emails (comma-separated): ") + members = [m.strip() for m in members_input.split(",") if m.strip()] + if not members: + continue + conv_id, msg = await client.create_conversation(members, name=name.strip() or None) + if conv_id: + print(f"[+] Group created with: {', '.join(members)}") + else: + print(f"[!] {msg}") + + elif choice == "5": + conv, _ = await _select_conversation(client) + if not conv: + continue + email = await prompt("Email to add: ") + ok, msg = await client.add_member(conv["conversation_id"], email) + print(f"[{'+'if ok else '!'}] {msg or 'Invitation sent.'}") + + elif choice == "6": + conv, _ = await _select_conversation(client) + if not conv: + continue + image_path = await prompt("Image path: ") + if not image_path: + continue + try: + ok, msg = await client.send_image(conv["conversation_id"], image_path, conv["members"]) + except IdentityKeyChanged as ikc: + print(f"[!] Identity key changed for {ikc.user_id[:8]}. Accept the new key before sending.") + continue + print(f"[{'+'if ok else '!'}] {msg}") + + elif choice == "7": + conv, _ = await _select_conversation(client) + if not conv: + continue + file_path = await prompt("File path: ") + if not file_path: + continue + if not os.path.isfile(file_path): + print("[!] File not found.") + continue + try: + ok, msg = await client.send_file(conv["conversation_id"], file_path, conv["members"]) + except IdentityKeyChanged as ikc: + print(f"[!] Identity key changed for {ikc.user_id[:8]}. Accept the new key before sending.") + continue + print(f"[{'+'if ok else '!'}] {msg}") + + elif choice == "8": + await _invitations_menu(client) + + elif choice == "9": + conv, _ = await _select_conversation(client, "Select group to leave") + if not conv: + continue + confirm = await prompt(f"Leave '{conv.get('name', 'this conversation')}'? (y/n): ") + if confirm.lower() != "y": + continue + ok, msg = await client.leave_group(conv["conversation_id"]) + print(f"[{'+'if ok else '!'}] {msg}") + + elif choice == "10": + conv, _ = await _select_conversation(client, "Select group to rename") + if not conv: + continue + name = await prompt("New name: ") + if not name: + continue + ok, msg = await client.rename_conversation(conv["conversation_id"], name.strip()) + print(f"[{'+'if ok else '!'}] {msg}") + + elif choice == "11": + conv, _ = await _select_conversation(client, "Select conversation to delete") + if not conv: + continue + confirm = await prompt("Delete this conversation? This cannot be undone. (y/n): ") + if confirm.lower() != "y": + continue + ok, msg = await client.delete_conversation(conv["conversation_id"]) + print(f"[{'+'if ok else '!'}] {msg}") + + elif choice == "12": + conv, _ = await _select_conversation(client, "Select conversation to search") + if not conv: + continue + query = await prompt("Search query: ") + if not query: + continue + # First ensure we have messages cached by fetching them + await client.get_messages(conv["conversation_id"]) + results = client.search_messages(conv["conversation_id"], query) + if not results: + print("[*] No matches found.") + continue + print(f"\n[*] {len(results)} match(es):") + for r in results: + sender = _sanitize_text(r.get("sender", "???")) + text = _sanitize_text(r.get("text", "")) + ts = r.get("created_at", "")[:16] + # Highlight match in text + idx = text.lower().find(query.lower()) + if idx >= 0: + text = text[:idx] + "\033[33m" + text[idx:idx+len(query)] + "\033[0m" + text[idx+len(query):] + print(f" [{ts}] {sender}: {text}") + + elif choice == "13": + await _my_profile_menu(client) + + elif choice == "14": + email = await prompt("User email: ") + if not email: + continue + # Need to find user_id from email — try via conversation members + user_id = None + convs = await client.list_conversations() + for c in convs: + for m in c.get("members", []): + if m.get("email") == email: + user_id = m.get("user_id") or m.get("id") + break + if user_id: + break + if not user_id: + print("[!] User not found in your conversations.") + continue + profile = await client.get_profile(user_id) + if not profile: + print("[!] Could not load profile.") + continue + _print_profile(profile) + + elif choice == "15": + await _devices_menu(client) + + elif choice == "16": + conv, _ = await _select_conversation(client) + if not conv: + continue + messages = await client.get_messages(conv["conversation_id"]) + if not messages: + print("[*] No messages.") + continue + _print_messages(messages, client, conv) + msg_choice = await prompt("React to message #: ") + try: + msg_idx = int(msg_choice) - 1 + if not (0 <= msg_idx < len(messages)): + print("[!] Invalid message number.") + continue + except ValueError: + print("[!] Invalid number.") + continue + print("Reactions: thumbsup, heart, laugh, surprised, sad, thumbsdown") + reaction = await prompt("Reaction: ").strip().lower() + if reaction not in ("thumbsup", "heart", "laugh", "surprised", "sad", "thumbsdown"): + print("[!] Invalid reaction.") + continue + ok, msg = await client.react_message(messages[msg_idx]["message_id"], reaction, "add") + print(f"[{'+'if ok else '!'}] {msg}") + + elif choice == "17": + conv, _ = await _select_conversation(client) + if not conv: + continue + messages = await client.get_messages(conv["conversation_id"]) + if not messages: + print("[*] No messages.") + continue + _print_messages(messages, client, conv) + msg_choice = await prompt("Pin/Unpin message #: ") + try: + msg_idx = int(msg_choice) - 1 + if not (0 <= msg_idx < len(messages)): + print("[!] Invalid message number.") + continue + except ValueError: + print("[!] Invalid number.") + continue + m = messages[msg_idx] + action = "unpin" if m.get("pinned_at") else "pin" + ok, msg = await client.pin_message(m["message_id"], conv["conversation_id"], action) + print(f"[{'+'if ok else '!'}] {action.capitalize()}: {msg}") + + elif choice == "18": + conv, _ = await _select_conversation(client) + if not conv: + continue + pinned = await client.get_pinned_messages(conv["conversation_id"]) + if not pinned: + print("[*] No pinned messages.") + continue + print(f"\n[*] {len(pinned)} pinned message(s):") + for p in pinned: + print(f" {p.get('message_id', '?')[:8]}... pinned at {p.get('pinned_at', '?')}") + + elif choice == "19": + conv, _ = await _select_conversation(client, "Select source conversation") + if not conv: + continue + messages = await client.get_messages(conv["conversation_id"]) + if not messages: + print("[*] No messages.") + continue + _print_messages(messages, client, conv) + msg_choice = await prompt("Forward message #: ") + try: + msg_idx = int(msg_choice) - 1 + if not (0 <= msg_idx < len(messages)): + print("[!] Invalid message number.") + continue + except ValueError: + print("[!] Invalid number.") + continue + target_conv, _ = await _select_conversation(client, "Select target conversation") + if not target_conv: + continue + fwd_msg = messages[msg_idx] + fwd_msg["conversation_id"] = conv["conversation_id"] + try: + ok, result = await client.forward_message( + target_conv["conversation_id"], fwd_msg, target_conv["members"] + ) + except IdentityKeyChanged as ikc: + print(f"[!] Identity key changed for {ikc.user_id[:8]}. Accept the new key before sending.") + continue + print(f"[{'+'if ok else '!'}] {'Forwarded.' if ok else result}") + + elif choice == "20": + # Verify contact — show safety number for a DM conversation + conv, _ = await _select_conversation(client, "Select DM to verify") + if not conv: + continue + # Find peer user_id + peer_uid = "" + peer_name = "" + for m in conv.get("members", []): + if m.get("email") != client.email: + peer_uid = m.get("user_id") or m.get("id") or "" + peer_name = _sanitize_text(m.get("username") or m.get("email") or "?") + break + if not peer_uid: + print("[!] Could not identify peer user.") + continue + # Ensure we have their identity key in cache + info = await client._get_user_info(user_id=peer_uid) + if not info or not info.get("identity_key_bytes"): + print("[!] Could not retrieve identity key for this user.") + continue + status = client.get_verification_status(peer_uid) + print(f"\n--- Verification: {peer_name} ---") + print(f"Status: {status.upper()}") + safety = client.get_safety_number(peer_uid) + if safety: + print(f"\nSafety Number:\n{safety}") + fp = client.get_peer_fingerprint(peer_uid) + if fp: + print(f"\nTheir Fingerprint:\n{fp}") + my_fp = client.get_my_fingerprint() + if my_fp: + print(f"\nYour Fingerprint:\n{my_fp}") + if status != "verified": + action = await prompt("\nMark as verified? (y/n): ") + if action.lower() == "y": + client.verify_contact(peer_uid, info["identity_key_bytes"], + method="safety_number") + print("[+] Contact marked as verified.") + else: + action = await prompt("\nRemove verification? (y/n): ") + if action.lower() == "y": + client.unverify_contact(peer_uid) + print("[+] Verification removed.") + + elif choice == "21": + # Show own fingerprint + fp = client.get_my_fingerprint() + if fp: + print(f"\n--- Your Fingerprint ---\n{fp}") + else: + print("[!] Not logged in or identity key not available.") + + elif choice == "22": + # Change password + old_pw = getpass.getpass("Current password: ") + new_pw = getpass.getpass("New password: ") + confirm_pw = getpass.getpass("Confirm new password: ") + if new_pw != confirm_pw: + print("[!] Passwords do not match.") + elif not new_pw: + print("[!] Password cannot be empty.") + else: + ok, msg = client.change_password(old_pw, new_pw) + if ok: + print(f"[+] {msg}") + else: + print(f"[!] {msg}") + + elif choice == "23": + new_un = await prompt("New username: ") + new_un = new_un.strip() if new_un else "" + if not new_un: + print("[!] Username cannot be empty.") + else: + ok, msg = await client.change_username(new_un) + if ok: + print(f"[+] {msg}") + else: + print(f"[!] {msg}") + + elif choice in ("q", "Q", "quit", "exit"): + print("[*] Bye.") + break + + +def _print_messages(messages, client, conv): + """Print messages to terminal.""" + print() + for i, m in enumerate(messages): + if m.get("deleted"): + print(f" #{i+1} [Message deleted]") + continue + reply_info = "" + if m.get("reply_to"): + for j, orig in enumerate(messages): + if orig["message_id"] == m["reply_to"]: + reply_info = f" (reply to #{j+1})" + break + else: + reply_info = " (reply to older message)" + image_info = "" + if m.get("image"): + img = m["image"] + image_info = f" [Image: {_sanitize_text(img.get('filename', '?'))} ({_human_size(img.get('size', 0))})]" + file_info = "" + if m.get("file"): + fi = m["file"] + file_info = f" [File: {_sanitize_text(fi.get('filename', '?'))} ({_human_size(fi.get('size', 0))})]" + read_info = "" + if m.get("sender") == client.username: + read_by = m.get("read_by", []) + delivered_to = m.get("delivered_to", []) + member_map = {} + for mem in conv.get("members", []): + uid = mem.get("user_id") or mem.get("id", "") + if uid: + member_map[uid] = _sanitize_text(mem.get("username") or mem.get("email") or "?") + my_uid = client.session.get("user_id", "") if client.session else "" + others_read = [r for r in read_by if r.get("user_id") != my_uid] + others_delivered = [d for d in delivered_to if d.get("user_id") != my_uid] + if others_read: + names = ", ".join(member_map.get(r["user_id"], r["user_id"][:8]) for r in others_read) + read_info = f" [\u2713\u2713 Read by {names}]" + elif others_delivered: + read_info = " [\u2713\u2713 Delivered]" + else: + read_info = " [\u2713 Sent]" + pin_info = "" + if m.get("pinned_at"): + pin_info = " \U0001f4cc" + reaction_info = "" + reactions = m.get("reactions", []) + if reactions: + grouped = {} + for r in reactions: + grouped.setdefault(r["reaction"], 0) + grouped[r["reaction"]] += 1 + _REMOJI = {"thumbsup": "\U0001f44d", "heart": "\u2764\ufe0f", "laugh": "\U0001f602", + "surprised": "\U0001f62e", "sad": "\U0001f622", "thumbsdown": "\U0001f44e"} + parts = [f"{_REMOJI.get(k, k)}{v}" for k, v in grouped.items()] + reaction_info = " [" + " ".join(parts) + "]" + fwd_info = "" + if m.get("forwarded_from"): + fwd_sender = _sanitize_text(m["forwarded_from"].get("sender", "?")) + fwd_info = f" (fwd from {fwd_sender})" + text = _sanitize_text(m.get("text", "")) + sender = _sanitize_text(m.get("sender", "?")) + print(f" #{i+1} {sender}: {text}{image_info}{file_info}{reply_info}{read_info}{pin_info}{reaction_info}{fwd_info}") + + +async def _delete_message_action(client, messages): + del_choice = await prompt("Delete message #: ") + try: + del_idx = int(del_choice) - 1 + if not (0 <= del_idx < len(messages)): + print("[!] Invalid message number.") + return + except ValueError: + print("[!] Invalid number.") + return + ok, msg = await client.delete_message(messages[del_idx]["message_id"]) + print(f"[{'+'if ok else '!'}] {msg}") + + +async def _download_file_action(client, messages): + dl_choice = await prompt("Download from message #: ") + try: + dl_idx = int(dl_choice) - 1 + if not (0 <= dl_idx < len(messages)): + print("[!] Invalid message number.") + return + except ValueError: + print("[!] Invalid number.") + return + m = messages[dl_idx] + file_info = m.get("file") or m.get("image") + if not file_info: + print("[!] No file/image in this message.") + return + filename = _safe_filename(file_info.get("filename", "download")) + save_path = await prompt(f"Save as [{filename}]: ") + if not save_path: + save_path = filename + data = await client.download_file(file_info["file_id"], file_info) + if data: + with open(save_path, "wb") as f: + f.write(data) + print(f"[+] Saved to {save_path} ({_human_size(len(data))})") + else: + print("[!] Download failed.") + + +async def _invitations_menu(client): + invitations = await client.list_invitations() + if not invitations: + print("[*] No pending invitations.") + return + print("\nPending invitations:") + for i, inv in enumerate(invitations): + inv_name = _sanitize_text(inv.get("conversation_name") or inv.get("conversation_id", "")[:8]) + invited_by = _sanitize_text(inv.get("invited_by_username") or inv.get("invited_by", "")[:8]) + print(f" {i+1}) {inv_name} (invited by {invited_by})") + choice = await prompt("Select invitation (or empty to go back): ") + if not choice: + return + try: + idx = int(choice) - 1 + if not (0 <= idx < len(invitations)): + print("[!] Invalid selection.") + return + except ValueError: + print("[!] Invalid selection.") + return + inv = invitations[idx] + action = await prompt("(a)ccept or (d)ecline? ") + if action.lower().startswith("a"): + ok, msg = await client.accept_invitation(inv["conversation_id"]) + print(f"[{'+'if ok else '!'}] {msg}") + elif action.lower().startswith("d"): + ok, msg = await client.decline_invitation(inv["conversation_id"]) + print(f"[{'+'if ok else '!'}] {msg}") + + +def _print_profile(profile): + print(f"\n Username: {_sanitize_text(profile.get('username', '?'))}") + print(f" Email: {_sanitize_text(profile.get('email', '?'))}") + phone = profile.get("phone") + if phone: + print(f" Phone: {_sanitize_text(phone)}") + location = profile.get("location") + if location: + print(f" Location: {_sanitize_text(location)}") + has_avatar = profile.get("avatar_file") + print(f" Avatar: {'Yes' if has_avatar else 'No'}") + + +async def _my_profile_menu(client): + profile = await client.get_profile() + if not profile: + print("[!] Could not load profile.") + return + print("\n--- My Profile ---") + _print_profile(profile) + print(f" Phone visible: {profile.get('phone_visible', False)}") + print(f" Email visible: {profile.get('email_visible', False)}") + print(f" Location visible: {profile.get('location_visible', False)}") + + action = await prompt("\n(e)dit, (a)vatar upload, or empty to go back: ") + if not action: + return + if action.lower().startswith("e"): + print("[*] Leave fields empty to keep current value.") + phone = await prompt(f"Phone [{profile.get('phone', '')}]: ") + location = await prompt(f"Location [{profile.get('location', '')}]: ") + phone_vis = await prompt(f"Phone visible [{profile.get('phone_visible', False)}] (y/n): ") + email_vis = await prompt(f"Email visible [{profile.get('email_visible', False)}] (y/n): ") + loc_vis = await prompt(f"Location visible [{profile.get('location_visible', False)}] (y/n): ") + + fields = {} + if phone: + fields["phone"] = phone + if location: + fields["location"] = location + if phone_vis.lower() in ("y", "n"): + fields["phone_visible"] = phone_vis.lower() == "y" + if email_vis.lower() in ("y", "n"): + fields["email_visible"] = email_vis.lower() == "y" + if loc_vis.lower() in ("y", "n"): + fields["location_visible"] = loc_vis.lower() == "y" + if fields: + ok, msg = await client.update_profile(**fields) + print(f"[{'+'if ok else '!'}] {msg}") + else: + print("[*] No changes.") + elif action.lower().startswith("a"): + path = await prompt("Avatar image path: ") + if not path or not os.path.isfile(path): + print("[!] File not found.") + return + data = open(path, "rb").read() + ok, msg = await client.update_avatar(data) + print(f"[{'+'if ok else '!'}] {msg}") + + +async def _devices_menu(client): + resp = await client.send_and_recv("list_devices") + if resp.get("status") != "ok": + print(f"[!] {resp.get('data', {}).get('message', 'Failed')}") + return + devices = resp["data"].get("devices", []) + if not devices: + print("[*] No devices found.") + return + current_device_id = client.device_id + print("\nYour devices:") + for i, d in enumerate(devices): + name = _sanitize_text(d.get("device_name") or "Unnamed") + did = d.get("device_id", "?") + last_seen = _sanitize_text(d.get("last_seen_at", "?")) + current = " (this device)" if did == current_device_id else "" + print(f" {i+1}) {name} — {did[:8]}... — last seen: {last_seen}{current}") + action = await prompt("\n(r)emove a device, or empty to go back: ") + if not action or not action.lower().startswith("r"): + return + choice = await prompt("Remove device #: ") + try: + idx = int(choice) - 1 + if not (0 <= idx < len(devices)): + print("[!] Invalid selection.") + return + except ValueError: + print("[!] Invalid selection.") + return + d = devices[idx] + if d.get("device_id") == current_device_id: + print("[!] Cannot remove current device.") + return + resp = await client.send_and_recv("remove_device", device_id=d["device_id"]) + if resp.get("status") == "ok": + print("[+] Device removed.") + else: + print(f"[!] {resp.get('data', {}).get('message', 'Failed')}") + + +async def notification_printer(client: ChatClient): + """Print real-time notifications with sender name.""" + while True: + notif = await client._notification_queue.get() + notif_type = notif.get("type", "") + data = notif.get("data", {}) + if notif_type == "messages_read": + continue # Silent - read receipts shown when reading messages + if notif_type == "session_reset": + from_uid = data.get("from_user_id", "")[:8] + client.handle_session_reset_notification( + data.get("from_user_id", ""), + data.get("from_device_id") or None, + ) + print(f"\n[*] Session with {from_uid}... was reset. New session will be created on next message.") + continue + if notif_type == "group_invitation": + inv_name = _sanitize_text(data.get("conversation_name", "?")) + invited_by = _sanitize_text(data.get("invited_by_username", "?")) + print(f"\n[*] New invitation to '{inv_name}' from {invited_by}. Use option 8 to accept/decline.") + continue + if notif_type in ("conversation_created", "member_added", "member_removed", "conversation_renamed"): + print(f"\n[*] Conversation updated ({notif_type}).") + continue + if notif_type == "message_reacted": + username = _sanitize_text(data.get("username", data.get("user_id", "?")[:8])) + reaction = _sanitize_text(data.get("reaction", "?")) + action = data.get("action", "add") + print(f"\n[*] {username} {'added' if action == 'add' else 'removed'} reaction '{reaction}'") + continue + if notif_type in ("message_pinned", "message_unpinned"): + username = _sanitize_text(data.get("username", data.get("user_id", "?")[:8])) + act = "pinned" if notif_type == "message_pinned" else "unpinned" + print(f"\n[*] {username} {act} a message") + continue + if notif_type in ("user_online", "user_offline", "online_users"): + continue # Silent for CLI + payload = client.decrypt_notification(data) + if payload: + print(f"\n[*] New message from {_sanitize_text(payload['sender'])} in conversation {data.get('conversation_id', '?')[:8]}...") + # None = control message (sender key distribution), skip silently + + +async def main(): + setup_logging() + client = ChatClient() + await client.connect() + + client._listener_task = asyncio.create_task(client._background_listener()) + notif_task = asyncio.create_task(notification_printer(client)) + + print("=== Encrypted Chat Client ===") + print("1) Register") + print("2) Login") + print("3) Link new device (this device)") + print("4) Authorize new device (from this device)") + print("5) Rotate keys (revoke other devices)") + choice = await prompt("> ") + + if choice == "1": + username = await prompt("Username (display): ") + email = await prompt("Email: ") + password = await prompt_password("Password (for private key): ") + if not email or not password: + print("[!] Email and password required.") + await client.close() + return + ok, code_or_msg = await client.register(username, password, email=email) + if not ok: + print(f"[!] {code_or_msg}") + await client.close() + return + print(f"[*] Registration code: {code_or_msg}") + code = await prompt("Enter code: ") + ok2, msg2 = await client.confirm_registration(email, username, code) + print(f"[{'+'if ok2 else '!'}] {msg2}") + if ok2: + ok3, msg3 = await client.login(email, password) + print(f"[{'+'if ok3 else '!'}] {msg3}") + elif choice == "2": + email = await prompt("Email: ") + password = await prompt_password("Password (for private key): ") + ok, msg = await client.login(email, password) + print(f"[{'+'if ok else '!'}] {msg}") + elif choice == "3": + email = await prompt("Email: ") + password = await prompt_password("Password (for private key): ") + if not password: + print("[!] Password required.") + await client.close() + return + ok, code_or_msg = await client.pairing_start(email) + if not ok: + print(f"[!] {code_or_msg}") + await client.close() + return + code = code_or_msg + print(f"[*] Pairing code: {code}") + print("[*] Approve this code on an already-logged-in device.") + ok2, msg2 = await client.pairing_wait(code, email, password) + if not ok2: + print(f"[!] {msg2}") + await client.close() + return + print(f"[+] {msg2}") + ok3, msg3 = await client.login(email, password) + print(f"[{'+'if ok3 else '!'}] {msg3}") + elif choice == "4": + email = await prompt("Email: ") + password = await prompt_password("Password (for private key): ") + ok, msg = await client.login(email, password) + print(f"[{'+'if ok else '!'}] {msg}") + if not ok: + await client.close() + return + code = await prompt("Pairing code: ") + ok2, msg2 = await client.authorize_device(code) + print(f"[{'+'if ok2 else '!'}] {msg2}") + elif choice == "5": + email = await prompt("Email: ") + password = await prompt_password("Password (for private key): ") + ok, msg = await client.login(email, password) + print(f"[{'+'if ok else '!'}] {msg}") + if not ok: + await client.close() + return + confirm = await prompt("This will revoke other devices. Type 'YES' to continue: ") + if confirm != "YES": + print("[*] Cancelled.") + await client.close() + return + ok2, msg2 = await client.rotate_keys(client.username, password) + print(f"[{'+'if ok2 else '!'}] {msg2}") + else: + print("[!] Invalid choice.") + await client.close() + return + + if client.session: + await interactive_menu(client) + + notif_task.cancel() + await client.close() + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("\n[*] Bye.") diff --git a/crypto_utils.py b/crypto_utils.py new file mode 100644 index 0000000..1f6795d --- /dev/null +++ b/crypto_utils.py @@ -0,0 +1,935 @@ +"""Cryptographic utilities: Ed25519, X25519, AES-256-GCM, Double Ratchet, Sender Keys. + +RSA functions retained for login challenge-response only. +""" + +import hashlib +import hmac +import json +import os +import struct +import uuid +from dataclasses import dataclass, field + +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding, rsa +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey +from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from cryptography.hazmat.primitives.kdf.hkdf import HKDF +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + + +# --------------------------------------------------------------------------- +# Password-based key encryption (M3: PBKDF2 600k iterations + AES-256-GCM) +# --------------------------------------------------------------------------- + +PBKDF2_ITERATIONS = 600_000 +_ECP1_MAGIC = b"ECP1" # Encrypted Chat PBKDF v1 format marker + + +def _encrypt_private_key(raw_bytes: bytes, password: bytes) -> bytes: + """Encrypt raw key bytes with PBKDF2-HMAC-SHA256 (600k iterations) + AES-256-GCM. + + Output format: MAGIC(4) + salt(16) + nonce(12) + ciphertext_with_tag(N+16) + """ + salt = os.urandom(16) + kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, + salt=salt, iterations=PBKDF2_ITERATIONS) + derived = kdf.derive(password) + nonce = os.urandom(12) + aesgcm = AESGCM(derived) + ct = aesgcm.encrypt(nonce, raw_bytes, _ECP1_MAGIC) # AAD = magic bytes + return _ECP1_MAGIC + salt + nonce + ct + + +def _decrypt_private_key(data: bytes, password: bytes) -> bytes: + """Decrypt key bytes encrypted with _encrypt_private_key.""" + if not data.startswith(_ECP1_MAGIC): + raise ValueError("Not ECP1 format") + salt = data[4:20] + nonce = data[20:32] + ct = data[32:] + kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, + salt=salt, iterations=PBKDF2_ITERATIONS) + derived = kdf.derive(password) + aesgcm = AESGCM(derived) + return aesgcm.decrypt(nonce, ct, _ECP1_MAGIC) + + +# --------------------------------------------------------------------------- +# RSA (login challenge-response ONLY) +# --------------------------------------------------------------------------- + +def generate_rsa_keypair(key_size: int = 4096) -> tuple[rsa.RSAPrivateKey, rsa.RSAPublicKey]: + private_key = rsa.generate_private_key(public_exponent=65537, key_size=key_size) + return private_key, private_key.public_key() + + +def serialize_private_key(key: rsa.RSAPrivateKey, password: bytes | None = None) -> bytes: + if password: + raw = key.private_bytes(serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, + serialization.NoEncryption()) + return _encrypt_private_key(raw, password) + return key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, + serialization.NoEncryption()) + + +def serialize_public_key(key: rsa.RSAPublicKey) -> bytes: + return key.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo) + + +def load_private_key(data: bytes, password: bytes | None = None) -> rsa.RSAPrivateKey: + if data.startswith(_ECP1_MAGIC): + raw = _decrypt_private_key(data, password) + return serialization.load_der_private_key(raw, password=None) + # Legacy PEM format (old BestAvailableEncryption or unencrypted) + return serialization.load_pem_private_key(data, password=password) + + +def load_public_key(pem: bytes) -> rsa.RSAPublicKey: + return serialization.load_pem_public_key(pem) + + +def rsa_sign(private_key: rsa.RSAPrivateKey, data: bytes) -> bytes: + return private_key.sign( + data, + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), + hashes.SHA256(), + ) + + +def rsa_verify(public_key: rsa.RSAPublicKey, signature: bytes, data: bytes) -> bool: + try: + public_key.verify( + signature, data, + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO), + hashes.SHA256(), + ) + return True + except Exception: + return False + + +# --------------------------------------------------------------------------- +# AES-256-GCM (symmetric encryption — used by ratchet message keys & images) +# --------------------------------------------------------------------------- + +def aes_encrypt(plaintext: bytes, key: bytes | None = None) -> tuple[bytes, bytes, bytes, bytes]: + """Encrypt with AES-256-GCM. Returns (key, nonce, ciphertext, tag).""" + if key is None: + key = AESGCM.generate_key(bit_length=256) + nonce = os.urandom(12) + aesgcm = AESGCM(key) + ct_with_tag = aesgcm.encrypt(nonce, plaintext, None) + ciphertext = ct_with_tag[:-16] + tag = ct_with_tag[-16:] + return key, nonce, ciphertext, tag + + +def aes_decrypt(key: bytes, nonce: bytes, ciphertext: bytes, tag: bytes) -> bytes: + """Decrypt with AES-256-GCM.""" + aesgcm = AESGCM(key) + return aesgcm.decrypt(nonce, ciphertext + tag, None) + + +# --------------------------------------------------------------------------- +# Ed25519 Identity Keys +# --------------------------------------------------------------------------- + +def generate_identity_keypair() -> tuple[Ed25519PrivateKey, Ed25519PublicKey]: + priv = Ed25519PrivateKey.generate() + return priv, priv.public_key() + + +def serialize_ed25519_private(key: Ed25519PrivateKey, password: bytes | None = None) -> bytes: + if password: + raw = serialize_ed25519_private_raw(key) # 32 bytes + return _encrypt_private_key(raw, password) + return serialize_ed25519_private_raw(key) # 32 bytes, no password + + +def serialize_ed25519_private_raw(key: Ed25519PrivateKey) -> bytes: + """Serialize Ed25519 private key to 32 raw bytes (unencrypted).""" + return key.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption()) + + +def serialize_ed25519_public(key: Ed25519PublicKey) -> bytes: + """Serialize Ed25519 public key to 32 raw bytes.""" + return key.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) + + +def load_ed25519_private(data: bytes, password: bytes | None = None) -> Ed25519PrivateKey: + if data.startswith(_ECP1_MAGIC): + raw = _decrypt_private_key(data, password) + return Ed25519PrivateKey.from_private_bytes(raw) + # Legacy formats: PEM (old BestAvailableEncryption) or 32-byte raw + if password: + return serialization.load_pem_private_key(data, password=password) + if len(data) == 32: + return Ed25519PrivateKey.from_private_bytes(data) + return serialization.load_pem_private_key(data, password=None) + + +def load_ed25519_public(data: bytes) -> Ed25519PublicKey: + if len(data) == 32: + return Ed25519PublicKey.from_public_bytes(data) + return serialization.load_pem_public_key(data) + + +def ed25519_sign(private_key: Ed25519PrivateKey, data: bytes) -> bytes: + """Sign data with Ed25519. Returns 64-byte signature.""" + return private_key.sign(data) + + +def ed25519_verify(public_key: Ed25519PublicKey, signature: bytes, data: bytes) -> bool: + """Verify Ed25519 signature.""" + try: + public_key.verify(signature, data) + return True + except Exception: + return False + + +# --------------------------------------------------------------------------- +# X25519 Key Exchange +# --------------------------------------------------------------------------- + +def generate_x25519_keypair() -> tuple[X25519PrivateKey, X25519PublicKey]: + priv = X25519PrivateKey.generate() + return priv, priv.public_key() + + +def serialize_x25519_private(key: X25519PrivateKey) -> bytes: + """Serialize X25519 private key to 32 raw bytes.""" + return key.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption()) + + +def serialize_x25519_public(key: X25519PublicKey) -> bytes: + """Serialize X25519 public key to 32 raw bytes.""" + return key.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) + + +def load_x25519_private(data: bytes) -> X25519PrivateKey: + return X25519PrivateKey.from_private_bytes(data) + + +def load_x25519_public(data: bytes) -> X25519PublicKey: + return X25519PublicKey.from_public_bytes(data) + + +def x25519_dh(private_key: X25519PrivateKey, public_key: X25519PublicKey) -> bytes: + """Perform X25519 Diffie-Hellman. Returns 32-byte shared secret.""" + return private_key.exchange(public_key) + + +# --------------------------------------------------------------------------- +# Ed25519 <-> X25519 conversion (for Identity Key dual use) +# --------------------------------------------------------------------------- + +def ed25519_private_to_x25519(ed_private: Ed25519PrivateKey) -> X25519PrivateKey: + """Derive X25519 private key from Ed25519 private key via RFC 7748 clamping.""" + raw = ed_private.private_bytes( + serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption() + ) + # SHA-512 hash of the seed, take first 32 bytes, clamp per RFC 7748 + h = hashlib.sha512(raw).digest()[:32] + clamped = bytearray(h) + clamped[0] &= 248 + clamped[31] &= 127 + clamped[31] |= 64 + return X25519PrivateKey.from_private_bytes(bytes(clamped)) + + +def ed25519_public_to_x25519(ed_public: Ed25519PublicKey) -> X25519PublicKey: + """Derive X25519 public key from Ed25519 public key. + + Uses the cryptography library's internal conversion. For production use, + we compute the X25519 public key from the converted private key when possible. + For remote keys (where we don't have the private key), we use a pure-Python + implementation of the Ed25519->X25519 point conversion. + """ + # Montgomery u = (1 + y) / (1 - y) mod p, where p = 2^255 - 19 + raw = ed_public.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) + y = int.from_bytes(raw, "little") + # Clear the sign bit + y &= (1 << 255) - 1 + p = (1 << 255) - 19 + # u = (1 + y) * inverse(1 - y) mod p + one_plus_y = (1 + y) % p + one_minus_y = (1 - y) % p + inv = pow(one_minus_y, p - 2, p) + u = (one_plus_y * inv) % p + x25519_bytes = u.to_bytes(32, "little") + return X25519PublicKey.from_public_bytes(x25519_bytes) + + +# --------------------------------------------------------------------------- +# HKDF +# --------------------------------------------------------------------------- + +_HKDF_INFO_SELF = b"EncryptedChat_SelfKey" +_HKDF_INFO_RK = b"EncryptedChat_RootKey" + + +def derive_self_encryption_key(identity_private: Ed25519PrivateKey) -> bytes: + """Derive a static AES-256 key from identity key for encrypting own sent messages. + + This is NOT a ratchet — it's a static key. Safe because only the owner + has the identity private key, and self-copies don't need forward secrecy. + """ + raw = identity_private.private_bytes( + serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption() + ) + return hkdf_derive(raw, salt=b"self_encryption", info=_HKDF_INFO_SELF, length=32) + + +_HKDF_INFO_LOCAL = b"EncryptedChat_LocalStorage" + + +def derive_local_storage_key(identity_private: Ed25519PrivateKey) -> bytes: + """Derive AES-256 key for encrypting local session/sender key files.""" + raw = identity_private.private_bytes( + serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption() + ) + return hkdf_derive(raw, salt=b"local_storage", info=_HKDF_INFO_LOCAL, length=32) + + +_HKDF_INFO_CK_MSG = b"\x01" # chain key -> message key +_HKDF_INFO_CK_NEXT = b"\x02" # chain key -> next chain key + + +def hkdf_derive(input_key: bytes, salt: bytes, info: bytes, length: int = 32) -> bytes: + return HKDF(algorithm=hashes.SHA256(), length=length, salt=salt, info=info).derive(input_key) + + +def kdf_rk(root_key: bytes, dh_output: bytes) -> tuple[bytes, bytes]: + """Root key KDF. Returns (new_root_key, chain_key). + + Uses HKDF with the root key as salt and DH output as input key material. + Derives 64 bytes: first 32 = new root key, last 32 = chain key. + """ + derived = hkdf_derive(dh_output, salt=root_key, info=_HKDF_INFO_RK, length=64) + return derived[:32], derived[32:] + + +def kdf_ck(chain_key: bytes) -> tuple[bytes, bytes]: + """Chain key KDF. Returns (new_chain_key, message_key). + + Uses HMAC-SHA256: + message_key = HMAC(chain_key, 0x01) + new_chain_key = HMAC(chain_key, 0x02) + """ + message_key = hmac.new(chain_key, _HKDF_INFO_CK_MSG, hashlib.sha256).digest() + new_chain_key = hmac.new(chain_key, _HKDF_INFO_CK_NEXT, hashlib.sha256).digest() + return new_chain_key, message_key + + +# --------------------------------------------------------------------------- +# X3DH +# --------------------------------------------------------------------------- + +_X3DH_INFO = b"EncryptedChat_X3DH" + + +def generate_signed_prekey(identity_private: Ed25519PrivateKey) -> dict: + """Generate a signed pre-key (SPK). + + Returns {private: X25519PrivateKey, public: X25519PublicKey, signature: bytes, id: str}. + """ + spk_priv, spk_pub = generate_x25519_keypair() + spk_pub_bytes = serialize_x25519_public(spk_pub) + signature = ed25519_sign(identity_private, spk_pub_bytes) + return { + "private": spk_priv, + "public": spk_pub, + "signature": signature, + "id": str(uuid.uuid4()), + } + + +def generate_one_time_prekeys(count: int = 50) -> list[dict]: + """Generate a batch of one-time pre-keys. + + Returns [{private: X25519PrivateKey, public: X25519PublicKey, id: str}, ...]. + """ + result = [] + for _ in range(count): + priv, pub = generate_x25519_keypair() + result.append({"private": priv, "public": pub, "id": str(uuid.uuid4())}) + return result + + +def x3dh_initiate( + ik_private_ed: Ed25519PrivateKey, + ik_public_remote_ed: Ed25519PublicKey, + spk_remote: X25519PublicKey, + spk_signature: bytes, + opk_remote: X25519PublicKey | None = None, +) -> tuple[bytes, X25519PrivateKey, X25519PublicKey]: + """Initiator side of X3DH. + + Args: + ik_private_ed: Our Ed25519 identity private key + ik_public_remote_ed: Remote Ed25519 identity public key + spk_remote: Remote signed pre-key (X25519 public) + spk_signature: Ed25519 signature of spk_remote by ik_public_remote_ed + opk_remote: Optional one-time pre-key (X25519 public) + + Returns: + (shared_secret, ephemeral_private, ephemeral_public) + """ + # Verify SPK signature + spk_remote_bytes = serialize_x25519_public(spk_remote) + if not ed25519_verify(ik_public_remote_ed, spk_signature, spk_remote_bytes): + raise ValueError("Invalid SPK signature") + + # Convert identity keys to X25519 + ik_x25519_private = ed25519_private_to_x25519(ik_private_ed) + ik_x25519_remote = ed25519_public_to_x25519(ik_public_remote_ed) + + # Generate ephemeral keypair + ek_priv, ek_pub = generate_x25519_keypair() + + # DH computations + dh1 = x25519_dh(ik_x25519_private, spk_remote) # IK_A, SPK_B + dh2 = x25519_dh(ek_priv, ik_x25519_remote) # EK_A, IK_B + dh3 = x25519_dh(ek_priv, spk_remote) # EK_A, SPK_B + + dh_concat = dh1 + dh2 + dh3 + if opk_remote is not None: + dh4 = x25519_dh(ek_priv, opk_remote) # EK_A, OPK_B + dh_concat += dh4 + + # Derive shared secret + shared_secret = hkdf_derive(dh_concat, salt=b"\x00" * 32, info=_X3DH_INFO, length=32) + return shared_secret, ek_priv, ek_pub + + +def x3dh_respond( + ik_private_ed: Ed25519PrivateKey, + spk_private: X25519PrivateKey, + ik_remote_ed: Ed25519PublicKey, + ek_remote: X25519PublicKey, + opk_private: X25519PrivateKey | None = None, +) -> bytes: + """Responder side of X3DH. + + Args: + ik_private_ed: Our Ed25519 identity private key + spk_private: Our signed pre-key private (X25519) + ik_remote_ed: Remote Ed25519 identity public key + ek_remote: Remote ephemeral key (X25519 public) + opk_private: Our one-time pre-key private (X25519), if used + + Returns: + shared_secret (32 bytes) + """ + ik_x25519_private = ed25519_private_to_x25519(ik_private_ed) + ik_x25519_remote = ed25519_public_to_x25519(ik_remote_ed) + + dh1 = x25519_dh(spk_private, ik_x25519_remote) # SPK_B, IK_A + dh2 = x25519_dh(ik_x25519_private, ek_remote) # IK_B, EK_A + dh3 = x25519_dh(spk_private, ek_remote) # SPK_B, EK_A + + dh_concat = dh1 + dh2 + dh3 + if opk_private is not None: + dh4 = x25519_dh(opk_private, ek_remote) # OPK_B, EK_A + dh_concat += dh4 + + shared_secret = hkdf_derive(dh_concat, salt=b"\x00" * 32, info=_X3DH_INFO, length=32) + return shared_secret + + +# --------------------------------------------------------------------------- +# Double Ratchet +# --------------------------------------------------------------------------- + +MAX_SKIP = 256 # max messages to skip in a single chain (out-of-order tolerance) + + +@dataclass +class RatchetHeader: + """Header sent with each ratchet message.""" + dh_pub: bytes # sender's current ratchet public key (32 bytes) + n: int # message number in current sending chain + pn: int # number of messages in previous sending chain + + def serialize(self) -> bytes: + return json.dumps({ + "dh_pub": serialize_x25519_public(load_x25519_public(self.dh_pub)).hex() + if isinstance(self.dh_pub, bytes) else serialize_x25519_public(self.dh_pub).hex(), + "n": self.n, + "pn": self.pn, + }).encode() + + def to_dict(self) -> dict: + pub_hex = self.dh_pub.hex() if isinstance(self.dh_pub, bytes) else \ + serialize_x25519_public(self.dh_pub).hex() + return {"dh_pub": pub_hex, "n": self.n, "pn": self.pn} + + @classmethod + def from_dict(cls, d: dict) -> "RatchetHeader": + return cls(dh_pub=bytes.fromhex(d["dh_pub"]), n=d["n"], pn=d["pn"]) + + +class DoubleRatchet: + """Signal Double Ratchet implementation.""" + + def __init__(self): + self.dh_pair: tuple[X25519PrivateKey, X25519PublicKey] | None = None + self.dh_remote: X25519PublicKey | None = None + self.root_key: bytes = b"" + self.send_chain_key: bytes | None = None + self.recv_chain_key: bytes | None = None + self.send_n: int = 0 + self.recv_n: int = 0 + self.prev_send_n: int = 0 + # (dh_pub_hex, n) -> message_key for out-of-order messages + self.skipped: dict[tuple[str, int], bytes] = {} + + @classmethod + def init_alice(cls, shared_secret: bytes, bob_spk_pub: X25519PublicKey) -> "DoubleRatchet": + """Initialize as initiator (Alice) after X3DH. + + Alice performs the first DH ratchet step immediately. + """ + ratchet = cls() + ratchet.dh_pair = generate_x25519_keypair() + ratchet.dh_remote = bob_spk_pub + + # Perform DH ratchet to derive send chain + dh_output = x25519_dh(ratchet.dh_pair[0], ratchet.dh_remote) + ratchet.root_key, ratchet.send_chain_key = kdf_rk(shared_secret, dh_output) + ratchet.recv_chain_key = None + ratchet.send_n = 0 + ratchet.recv_n = 0 + ratchet.prev_send_n = 0 + return ratchet + + @classmethod + def init_bob(cls, shared_secret: bytes, spk_pair: tuple[X25519PrivateKey, X25519PublicKey]) -> "DoubleRatchet": + """Initialize as responder (Bob) after X3DH. + + Bob uses his SPK as the initial ratchet key pair. + """ + ratchet = cls() + ratchet.dh_pair = spk_pair + ratchet.root_key = shared_secret + ratchet.send_chain_key = None + ratchet.recv_chain_key = None + ratchet.send_n = 0 + ratchet.recv_n = 0 + ratchet.prev_send_n = 0 + return ratchet + + def encrypt(self, plaintext: bytes) -> dict: + """Encrypt a message. + + Returns {header: {dh_pub, n, pn}, ciphertext: bytes, nonce: bytes}. + """ + if self.send_chain_key is None: + raise RuntimeError("Send chain not initialized") + + self.send_chain_key, message_key = kdf_ck(self.send_chain_key) + + header = RatchetHeader( + dh_pub=serialize_x25519_public(self.dh_pair[1]), + n=self.send_n, + pn=self.prev_send_n, + ) + + # Encrypt with AES-256-GCM using the message key + nonce = os.urandom(12) + aesgcm = AESGCM(message_key) + # Include header as AAD to bind ciphertext to header + aad = header.serialize() + ct_with_tag = aesgcm.encrypt(nonce, plaintext, aad) + + self.send_n += 1 + + return { + "header": header.to_dict(), + "ciphertext": ct_with_tag, # includes 16-byte tag + "nonce": nonce, + } + + def decrypt(self, header_dict: dict, ciphertext: bytes, nonce: bytes) -> bytes: + """Decrypt a message. Handles DH ratchet step if new dh_pub. + + State is snapshotted before modification and restored on failure (M9 fix). + """ + header = RatchetHeader.from_dict(header_dict) + remote_dh_pub_bytes = header.dh_pub + + # Check if this is from a skipped message (no state modification needed) + skip_key = (remote_dh_pub_bytes.hex(), header.n) + if skip_key in self.skipped: + mk = self.skipped.pop(skip_key) + aad = header.serialize() + aesgcm = AESGCM(mk) + try: + return aesgcm.decrypt(nonce, ciphertext, aad) + except Exception: + self.skipped[skip_key] = mk # restore skipped key + raise + + # Snapshot state before modifications + snap = self._snapshot() + + try: + remote_dh_pub = load_x25519_public(remote_dh_pub_bytes) + current_remote_bytes = serialize_x25519_public(self.dh_remote) if self.dh_remote else None + + if current_remote_bytes is None or remote_dh_pub_bytes != current_remote_bytes: + # New DH ratchet step + self._skip_messages(header.pn) + self._dh_ratchet(remote_dh_pub) + + self._skip_messages(header.n) + + # Derive message key from receive chain + self.recv_chain_key, mk = kdf_ck(self.recv_chain_key) + self.recv_n += 1 + + aad = header.serialize() + aesgcm = AESGCM(mk) + return aesgcm.decrypt(nonce, ciphertext, aad) + except Exception: + self._restore(snap) + raise + + def _snapshot(self) -> dict: + """Capture mutable state for rollback on decrypt failure.""" + return { + "dh_pair": self.dh_pair, + "dh_remote": self.dh_remote, + "root_key": self.root_key, + "send_chain_key": self.send_chain_key, + "recv_chain_key": self.recv_chain_key, + "send_n": self.send_n, + "recv_n": self.recv_n, + "prev_send_n": self.prev_send_n, + "skipped": dict(self.skipped), + } + + def _restore(self, snap: dict): + """Restore state from snapshot.""" + self.dh_pair = snap["dh_pair"] + self.dh_remote = snap["dh_remote"] + self.root_key = snap["root_key"] + self.send_chain_key = snap["send_chain_key"] + self.recv_chain_key = snap["recv_chain_key"] + self.send_n = snap["send_n"] + self.recv_n = snap["recv_n"] + self.prev_send_n = snap["prev_send_n"] + self.skipped = snap["skipped"] + + def _skip_messages(self, until: int): + """Skip ahead in the receive chain, storing message keys for out-of-order delivery.""" + if self.recv_chain_key is None: + return + if until - self.recv_n > MAX_SKIP: + raise RuntimeError(f"Too many skipped messages ({until - self.recv_n} > {MAX_SKIP})") + while self.recv_n < until: + self.recv_chain_key, mk = kdf_ck(self.recv_chain_key) + remote_hex = serialize_x25519_public(self.dh_remote).hex() if self.dh_remote else "" + self.skipped[(remote_hex, self.recv_n)] = mk + self.recv_n += 1 + + def _dh_ratchet(self, remote_dh_pub: X25519PublicKey): + """Perform a DH ratchet step: update receive chain, generate new DH pair, update send chain.""" + self.prev_send_n = self.send_n + self.send_n = 0 + self.recv_n = 0 + self.dh_remote = remote_dh_pub + + # Derive new receive chain key + dh_output = x25519_dh(self.dh_pair[0], self.dh_remote) + self.root_key, self.recv_chain_key = kdf_rk(self.root_key, dh_output) + + # Generate new DH pair and derive new send chain key + self.dh_pair = generate_x25519_keypair() + dh_output = x25519_dh(self.dh_pair[0], self.dh_remote) + self.root_key, self.send_chain_key = kdf_rk(self.root_key, dh_output) + + def export_state(self) -> bytes: + """Serialize full ratchet state for persistent storage.""" + state = { + "dh_priv": serialize_x25519_private(self.dh_pair[0]).hex() if self.dh_pair else None, + "dh_pub": serialize_x25519_public(self.dh_pair[1]).hex() if self.dh_pair else None, + "dh_remote": serialize_x25519_public(self.dh_remote).hex() if self.dh_remote else None, + "root_key": self.root_key.hex(), + "send_ck": self.send_chain_key.hex() if self.send_chain_key else None, + "recv_ck": self.recv_chain_key.hex() if self.recv_chain_key else None, + "send_n": self.send_n, + "recv_n": self.recv_n, + "prev_send_n": self.prev_send_n, + "skipped": {f"{k[0]}:{k[1]}": v.hex() for k, v in self.skipped.items()}, + } + return json.dumps(state).encode() + + @classmethod + def import_state(cls, data: bytes) -> "DoubleRatchet": + """Deserialize ratchet state.""" + state = json.loads(data) + r = cls() + if state["dh_priv"] and state["dh_pub"]: + priv = load_x25519_private(bytes.fromhex(state["dh_priv"])) + pub = load_x25519_public(bytes.fromhex(state["dh_pub"])) + r.dh_pair = (priv, pub) + if state["dh_remote"]: + r.dh_remote = load_x25519_public(bytes.fromhex(state["dh_remote"])) + r.root_key = bytes.fromhex(state["root_key"]) + r.send_chain_key = bytes.fromhex(state["send_ck"]) if state["send_ck"] else None + r.recv_chain_key = bytes.fromhex(state["recv_ck"]) if state["recv_ck"] else None + r.send_n = state["send_n"] + r.recv_n = state["recv_n"] + r.prev_send_n = state["prev_send_n"] + r.skipped = {} + for k_str, v_hex in state.get("skipped", {}).items(): + parts = k_str.rsplit(":", 1) + dh_hex = parts[0] + n = int(parts[1]) + r.skipped[(dh_hex, n)] = bytes.fromhex(v_hex) + return r + + +# --------------------------------------------------------------------------- +# Sender Keys (group messaging) +# --------------------------------------------------------------------------- + +class SenderKeyState: + """Sender key chain for group messaging. + + Each sender in a group has their own sender key chain. + Other group members receive the initial sender_key via pairwise Double Ratchet. + """ + + def __init__(self, sender_key: bytes | None = None): + if sender_key is None: + sender_key = os.urandom(32) + self.sender_key = sender_key + self.chain_id = hashlib.sha256(sender_key).digest() + self.chain_key = hkdf_derive(sender_key, salt=b"\x00" * 32, info=b"SenderKeyChain", length=32) + self.n = 0 + # For receivers: track chain state to allow fast-forward + self._known_keys: dict[int, bytes] = {} + + def encrypt(self, plaintext: bytes) -> dict: + """Encrypt with current chain key. + + Returns {chain_id: hex, n: int, ciphertext: bytes, nonce: bytes}. + """ + self.chain_key, message_key = kdf_ck(self.chain_key) + nonce = os.urandom(12) + aesgcm = AESGCM(message_key) + # AAD includes chain_id and message number + aad = self.chain_id + struct.pack(">I", self.n) + ct_with_tag = aesgcm.encrypt(nonce, plaintext, aad) + result = { + "chain_id": self.chain_id.hex(), + "n": self.n, + "ciphertext": ct_with_tag, + "nonce": nonce, + } + self.n += 1 + return result + + MAX_SENDER_KEY_SKIP = 256 + + def decrypt(self, chain_id_hex: str, n: int, ciphertext: bytes, nonce: bytes) -> bytes: + """Decrypt a group message. Fast-forwards the chain if needed. + + State is snapshotted before modification and restored on failure (M9 fix). + """ + chain_id = bytes.fromhex(chain_id_hex) + if chain_id != self.chain_id: + raise ValueError("Chain ID mismatch") + + if n - self.n > self.MAX_SENDER_KEY_SKIP: + raise ValueError(f"Sender key skip too large ({n - self.n} > {self.MAX_SENDER_KEY_SKIP})") + + # Snapshot before fast-forward + snap_chain_key = self.chain_key + snap_n = self.n + snap_known = dict(self._known_keys) + + try: + # Fast-forward the chain to reach message n + while self.n <= n: + self.chain_key, mk = kdf_ck(self.chain_key) + self._known_keys[self.n] = mk + self.n += 1 + + mk = self._known_keys.pop(n, None) + if mk is None: + raise ValueError(f"Message key for n={n} not available (already consumed)") + + aad = chain_id + struct.pack(">I", n) + aesgcm = AESGCM(mk) + return aesgcm.decrypt(nonce, ciphertext, aad) + except Exception: + self.chain_key = snap_chain_key + self.n = snap_n + self._known_keys = snap_known + raise + + def export_key(self) -> bytes: + """Export sender key for distribution to group members. + + Contains everything needed to initialize a receiving SenderKeyState. + """ + return json.dumps({ + "sender_key": self.sender_key.hex(), + }).encode() + + def export_state(self) -> bytes: + """Serialize full state for persistent storage.""" + return json.dumps({ + "sender_key": self.sender_key.hex(), + "chain_id": self.chain_id.hex(), + "chain_key": self.chain_key.hex(), + "n": self.n, + "known_keys": {str(k): v.hex() for k, v in self._known_keys.items()}, + }).encode() + + @classmethod + def import_state(cls, data: bytes) -> "SenderKeyState": + state = json.loads(data) + obj = cls.__new__(cls) + obj.sender_key = bytes.fromhex(state["sender_key"]) + obj.chain_id = bytes.fromhex(state["chain_id"]) + obj.chain_key = bytes.fromhex(state["chain_key"]) + obj.n = state["n"] + obj._known_keys = {int(k): bytes.fromhex(v) for k, v in state.get("known_keys", {}).items()} + return obj + + @classmethod + def from_key(cls, exported_key: bytes) -> "SenderKeyState": + """Initialize a receiving SenderKeyState from an exported key.""" + data = json.loads(exported_key) + return cls(sender_key=bytes.fromhex(data["sender_key"])) + + +# --------------------------------------------------------------------------- +# Contact Key Verification (Safety Numbers / Fingerprints / QR Codes) +# --------------------------------------------------------------------------- + +FINGERPRINT_VERSION = 0 + + +def compute_fingerprint(user_id: str, identity_key_bytes: bytes, iterations: int = 5200) -> bytes: + """Compute a 32-byte fingerprint for a user's identity key. + + Uses iterated SHA-512 (Signal's NumericFingerprint algorithm). + Seed: version(2B) + identity_key(32B) + user_id(UTF-8). + Each iteration: SHA-512(previous_hash + identity_key). + Output: first 32 bytes of final hash. + """ + version_bytes = FINGERPRINT_VERSION.to_bytes(2, "big") + data = version_bytes + identity_key_bytes + user_id.encode("utf-8") + for _ in range(iterations): + data = hashlib.sha512(data + identity_key_bytes).digest() + return data[:32] + + +def format_fingerprint(fp_bytes: bytes) -> str: + """Format 32-byte fingerprint as 6 groups of 5 zero-padded digits (30 digits). + + Each group: int(bytes[i*5:(i+1)*5], big-endian) % 100000. + Output: two lines of 3 groups each, space-separated. + """ + groups = [] + for i in range(6): + num = int.from_bytes(fp_bytes[i * 5:(i + 1) * 5], "big") % 100000 + groups.append(f"{num:05d}") + return " ".join(groups[:3]) + "\n" + " ".join(groups[3:]) + + +def compute_safety_number(my_uid: str, my_ik_bytes: bytes, + their_uid: str, their_ik_bytes: bytes) -> str: + """Compute a 60-digit safety number for a pair of users. + + Both users see the same number regardless of who computes it. + Lower user_id's fingerprint comes first (deterministic ordering). + Output: 12 groups of 5 digits, formatted as 3 lines of 4 groups. + """ + fp_mine = compute_fingerprint(my_uid, my_ik_bytes) + fp_theirs = compute_fingerprint(their_uid, their_ik_bytes) + if my_uid < their_uid: + combined = fp_mine + fp_theirs + else: + combined = fp_theirs + fp_mine + # 64 bytes -> 12 groups of 5 digits + groups = [] + for i in range(12): + num = int.from_bytes(combined[i * 5:(i + 1) * 5], "big") % 100000 + groups.append(f"{num:05d}") + lines = [ + " ".join(groups[0:4]), + " ".join(groups[4:8]), + " ".join(groups[8:12]), + ] + return "\n".join(lines) + + +def encode_verification_qr(user_id: str, identity_key_bytes: bytes) -> bytes: + """Encode user identity for QR code verification. + + Format: version(1B=0x01) + uid_len(1B) + uid(UTF-8) + identity_key(32B). + """ + uid_bytes = user_id.encode("utf-8") + return b"\x01" + len(uid_bytes).to_bytes(1, "big") + uid_bytes + identity_key_bytes + + +def decode_verification_qr(data: bytes) -> tuple[str, bytes]: + """Decode QR code verification payload. + + Returns (user_id, identity_key_bytes). + Raises ValueError on invalid format. + """ + if len(data) < 3: + raise ValueError("QR data too short") + if data[0] != 0x01: + raise ValueError(f"Unknown QR version: {data[0]}") + uid_len = data[1] + if len(data) < 2 + uid_len + 32: + raise ValueError("QR data truncated") + user_id = data[2:2 + uid_len].decode("utf-8") + identity_key = data[2 + uid_len:2 + uid_len + 32] + return user_id, identity_key + + +# --------------------------------------------------------------------------- +# Message Padding (metadata privacy — hide plaintext length) +# --------------------------------------------------------------------------- + +_PAD_MAGIC = b"\x01" +_PAD_BUCKETS = [64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536] + + +def pad_plaintext(plaintext: bytes) -> bytes: + """Pad plaintext to nearest bucket size to hide message length. + + Format: 0x01 + plaintext + random_padding + pad_length(4B big-endian) + Prefix 0x01 distinguishes padded messages from legacy unpadded (which start with '{'). + """ + content = _PAD_MAGIC + plaintext + # +4 for the length suffix + min_size = len(content) + 4 + target = next((b for b in _PAD_BUCKETS if b >= min_size), min_size) + pad_len = target - len(content) + return content + os.urandom(pad_len - 4) + struct.pack(">I", pad_len) + + +def unpad_plaintext(data: bytes) -> bytes: + """Remove padding. Returns raw plaintext for both padded and legacy unpadded messages.""" + if not data or data[0:1] != _PAD_MAGIC: + return data # legacy unpadded message (starts with '{' for JSON) + if len(data) < 5: + return data # too short to be validly padded + pad_len = struct.unpack(">I", data[-4:])[0] + if pad_len < 4 or pad_len > len(data) - 1: + return data # invalid padding metadata, treat as legacy + return data[1:len(data) - pad_len] diff --git a/db.py b/db.py new file mode 100644 index 0000000..76ecae0 --- /dev/null +++ b/db.py @@ -0,0 +1,1714 @@ +"""MySQL database layer for the encrypted chat server.""" + +import os +import uuid + +import logging +import mysql.connector +from mysql.connector import pooling +from dotenv import load_dotenv + +from crypto_utils import ( + generate_identity_keypair, + serialize_ed25519_public, + generate_signed_prekey, + serialize_x25519_public, + generate_one_time_prekeys, +) + +load_dotenv() + +# Sentinel device_id for self-encrypted copies and legacy (pre-multi-device) rows +SELF_DEVICE_ID = "00000000-0000-0000-0000-000000000000" + + +_logger = logging.getLogger(__name__) + +_pool = None + + +def _get_pool(): + """Get or create the connection pool (lazy init).""" + global _pool + if _pool is None: + pool_size = int(os.getenv("DB_POOL_SIZE", "10")) + _pool = pooling.MySQLConnectionPool( + pool_name="chat_pool", + pool_size=pool_size, + pool_reset_session=True, + host=os.getenv("MYSQL_HOST", "localhost"), + port=int(os.getenv("MYSQL_PORT", "3306")), + user=os.getenv("MYSQL_USER", "root"), + password=os.getenv("MYSQL_PASSWORD", ""), + database=os.getenv("MYSQL_DATABASE", "encrypted_chat"), + ) + _logger.info("DB connection pool created (size=%d)", pool_size) + return _pool + + +def get_connection(): + """Get a connection from the pool.""" + return _get_pool().get_connection() + + +def generate_uuid() -> str: + return str(uuid.uuid4()) + + +# --- Devices --- + +def create_device(user_id: str, device_name: str | None = None) -> str: + """Create a new device for a user. Returns device_id.""" + conn = get_connection() + try: + cursor = conn.cursor() + device_id = generate_uuid() + cursor.execute( + "INSERT INTO devices (id, user_id, device_name) VALUES (%s, %s, %s)", + (device_id, user_id, device_name), + ) + conn.commit() + return device_id + finally: + conn.close() + + +def get_user_devices(user_id: str) -> list[dict]: + """Get all devices for a user.""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT id, user_id, device_name, created_at, last_seen_at " + "FROM devices WHERE user_id = %s ORDER BY created_at", + (user_id,), + ) + return cursor.fetchall() + finally: + conn.close() + + +def get_device(device_id: str) -> dict | None: + """Get a single device by ID.""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT id, user_id, device_name, created_at, last_seen_at " + "FROM devices WHERE id = %s", + (device_id,), + ) + return cursor.fetchone() + finally: + conn.close() + + +def update_device_last_seen(device_id: str): + """Update last_seen_at timestamp for a device.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "UPDATE devices SET last_seen_at = NOW() WHERE id = %s", + (device_id,), + ) + conn.commit() + finally: + conn.close() + + +def delete_device(device_id: str): + """Delete a device. CASCADE removes its prekeys.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute("DELETE FROM devices WHERE id = %s", (device_id,)) + # Also clean up prekeys explicitly for device_id column + cursor.execute("DELETE FROM signed_prekeys WHERE device_id = %s", (device_id,)) + cursor.execute("DELETE FROM one_time_prekeys WHERE device_id = %s", (device_id,)) + conn.commit() + finally: + conn.close() + + +# --- Users --- + +def create_user(username: str, email: str, rsa_public_key_pem: str, identity_key: bytes) -> str: + """Register a new user. Returns user ID.""" + conn = get_connection() + try: + cursor = conn.cursor() + user_id = generate_uuid() + cursor.execute( + "INSERT INTO users (id, username, email, rsa_public_key, identity_key) " + "VALUES (%s, %s, %s, %s, %s)", + (user_id, username, email, rsa_public_key_pem, identity_key), + ) + conn.commit() + return user_id + finally: + conn.close() + + +def get_user_by_email(email: str) -> dict | None: + """Get user by email.""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT id, username, rsa_public_key, email, identity_key FROM users WHERE email = %s", + (email,), + ) + return cursor.fetchone() + finally: + conn.close() + + +def get_user_by_id(user_id: str) -> dict | None: + """Get user by ID.""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT id, username, rsa_public_key, email, identity_key FROM users WHERE id = %s", + (user_id,), + ) + return cursor.fetchone() + finally: + conn.close() + + +def shares_conversation(user_id_a: str, user_id_b: str) -> bool: + """Check if two users share at least one conversation.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT 1 FROM conversation_members cm1 " + "JOIN conversation_members cm2 ON cm1.conversation_id = cm2.conversation_id " + "WHERE cm1.user_id = %s AND cm2.user_id = %s LIMIT 1", + (user_id_a, user_id_b), + ) + return cursor.fetchone() is not None + finally: + conn.close() + + +def get_user_contacts(user_id: str) -> list[str]: + """Get all user IDs that share at least one conversation with the given user.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT DISTINCT cm2.user_id " + "FROM conversation_members cm1 " + "JOIN conversation_members cm2 ON cm1.conversation_id = cm2.conversation_id " + "WHERE cm1.user_id = %s AND cm2.user_id != %s", + (user_id, user_id), + ) + return [row[0] for row in cursor.fetchall()] + finally: + conn.close() + + +def update_user_rsa_key(user_id: str, rsa_public_key_pem: str): + """Update user's RSA public key (for login).""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute("UPDATE users SET rsa_public_key = %s WHERE id = %s", (rsa_public_key_pem, user_id)) + conn.commit() + finally: + conn.close() + + +def update_username(user_id: str, new_username: str): + """Update user's display name.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute("UPDATE users SET username = %s WHERE id = %s", (new_username, user_id)) + conn.commit() + finally: + conn.close() + + +# --- Pre-keys --- + +def store_signed_prekey(user_id: str, spk_id: str, public_key: bytes, signature: bytes, + device_id: str | None = None): + """Store (or replace) a signed pre-key for a user's device.""" + conn = get_connection() + try: + cursor = conn.cursor() + # Remove old SPKs for this user+device + if device_id: + cursor.execute("DELETE FROM signed_prekeys WHERE user_id = %s AND device_id = %s", + (user_id, device_id)) + else: + cursor.execute("DELETE FROM signed_prekeys WHERE user_id = %s AND device_id IS NULL", + (user_id,)) + cursor.execute( + "INSERT INTO signed_prekeys (id, user_id, device_id, public_key, signature) " + "VALUES (%s, %s, %s, %s, %s)", + (spk_id, user_id, device_id, public_key, signature), + ) + conn.commit() + finally: + conn.close() + + +def get_signed_prekey(user_id: str, device_id: str | None = None) -> dict | None: + """Get the current signed pre-key for a user (optionally per device).""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + if device_id: + cursor.execute( + "SELECT id, public_key, signature, device_id, created_at FROM signed_prekeys " + "WHERE user_id = %s AND device_id = %s " + "ORDER BY created_at DESC LIMIT 1", + (user_id, device_id), + ) + else: + cursor.execute( + "SELECT id, public_key, signature, device_id, created_at FROM signed_prekeys " + "WHERE user_id = %s ORDER BY created_at DESC LIMIT 1", + (user_id,), + ) + return cursor.fetchone() + finally: + conn.close() + + +def store_one_time_prekeys(user_id: str, prekeys: list[dict], device_id: str | None = None): + """Store a batch of one-time pre-keys. Each dict has {id, public_key (bytes)}.""" + conn = get_connection() + try: + cursor = conn.cursor() + for pk in prekeys: + cursor.execute( + "INSERT INTO one_time_prekeys (id, user_id, device_id, public_key) " + "VALUES (%s, %s, %s, %s)", + (pk["id"], user_id, device_id, pk["public_key"]), + ) + conn.commit() + finally: + conn.close() + + +def consume_one_time_prekey(user_id: str, device_id: str | None = None) -> dict | None: + """Atomically consume one OTP: SELECT FOR UPDATE + DELETE. + Returns {id, public_key} or None.""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + conn.start_transaction() + if device_id: + cursor.execute( + "SELECT id, public_key FROM one_time_prekeys " + "WHERE user_id = %s AND device_id = %s LIMIT 1 FOR UPDATE", + (user_id, device_id), + ) + else: + cursor.execute( + "SELECT id, public_key FROM one_time_prekeys " + "WHERE user_id = %s LIMIT 1 FOR UPDATE", + (user_id,), + ) + row = cursor.fetchone() + if row: + cursor.execute("DELETE FROM one_time_prekeys WHERE id = %s", (row["id"],)) + conn.commit() + return row + except Exception: + conn.rollback() + raise + finally: + conn.close() + + +def count_one_time_prekeys(user_id: str, device_id: str | None = None) -> int: + """Count remaining OTPs for a user (optionally per device).""" + conn = get_connection() + try: + cursor = conn.cursor() + if device_id: + cursor.execute( + "SELECT COUNT(*) FROM one_time_prekeys WHERE user_id = %s AND device_id = %s", + (user_id, device_id), + ) + else: + cursor.execute("SELECT COUNT(*) FROM one_time_prekeys WHERE user_id = %s", (user_id,)) + return cursor.fetchone()[0] + finally: + conn.close() + + +def get_key_bundle(user_id: str) -> dict | None: + """Get complete key bundle for X3DH (single device — legacy compat). + + Returns {identity_key, signed_prekey_id, signed_prekey, spk_signature, + one_time_prekey_id, one_time_prekey} or None. + OTP is consumed atomically. + """ + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + # Get user identity key + cursor.execute("SELECT identity_key FROM users WHERE id = %s", (user_id,)) + user = cursor.fetchone() + if not user: + return None + + # Get signed prekey + cursor.execute( + "SELECT id, public_key, signature, device_id FROM signed_prekeys WHERE user_id = %s " + "ORDER BY created_at DESC LIMIT 1", + (user_id,), + ) + spk = cursor.fetchone() + if not spk: + return None + + # Consume one OTP (may be None) — use transaction for atomicity (H12 fix) + conn.start_transaction() + cursor.execute( + "SELECT id, public_key FROM one_time_prekeys WHERE user_id = %s LIMIT 1 FOR UPDATE", + (user_id,), + ) + opk = cursor.fetchone() + if opk: + cursor.execute("DELETE FROM one_time_prekeys WHERE id = %s", (opk["id"],)) + conn.commit() + + result = { + "identity_key": user["identity_key"], + "signed_prekey_id": spk["id"], + "signed_prekey": spk["public_key"], + "spk_signature": spk["signature"], + } + if opk: + result["one_time_prekey_id"] = opk["id"] + result["one_time_prekey"] = opk["public_key"] + return result + except Exception: + try: + conn.rollback() + except Exception: + pass + raise + finally: + conn.close() + + +def get_key_bundles_for_user(user_id: str) -> dict | None: + """Get key bundles for ALL devices of a user. Returns + {identity_key, device_bundles: [{device_id, signed_prekey_id, signed_prekey_pub, + spk_signature, opk_id, opk_pub}]} or None. + Consumes one OPK per device atomically. + """ + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + # Get user identity key + cursor.execute("SELECT identity_key FROM users WHERE id = %s", (user_id,)) + user = cursor.fetchone() + if not user: + return None + + # Get all signed prekeys (one per device, most recent) + cursor.execute( + "SELECT id, public_key, signature, device_id FROM signed_prekeys " + "WHERE user_id = %s ORDER BY created_at DESC", + (user_id,), + ) + all_spks = cursor.fetchall() + if not all_spks: + return None + + # De-duplicate: keep only the most recent SPK per device_id + seen_devices = set() + spks_by_device = [] + for spk in all_spks: + dev = spk.get("device_id") or "__legacy__" + if dev not in seen_devices: + seen_devices.add(dev) + spks_by_device.append(spk) + + device_bundles = [] + # Commit the implicit transaction from the read-only queries above + # so we can start an explicit transaction for atomic OPK consumption. + conn.commit() + conn.start_transaction() + for spk in spks_by_device: + dev_id = spk.get("device_id") + # Consume one OPK for this device + if dev_id: + cursor.execute( + "SELECT id, public_key FROM one_time_prekeys " + "WHERE user_id = %s AND device_id = %s LIMIT 1 FOR UPDATE", + (user_id, dev_id), + ) + else: + cursor.execute( + "SELECT id, public_key FROM one_time_prekeys " + "WHERE user_id = %s AND device_id IS NULL LIMIT 1 FOR UPDATE", + (user_id,), + ) + opk = cursor.fetchone() + if opk: + cursor.execute("DELETE FROM one_time_prekeys WHERE id = %s", (opk["id"],)) + + bundle = { + "device_id": dev_id, + "signed_prekey_id": spk["id"], + "signed_prekey_pub": spk["public_key"], + "spk_signature": spk["signature"], + } + if opk: + bundle["opk_id"] = opk["id"] + bundle["opk_pub"] = opk["public_key"] + device_bundles.append(bundle) + conn.commit() + + return { + "identity_key": user["identity_key"], + "device_bundles": device_bundles, + } + except Exception: + try: + conn.rollback() + except Exception: + pass + raise + finally: + conn.close() + + +# --- Conversations --- + +def create_conversation(member_user_ids: list[str], joined_at=None, name=None, created_by=None) -> str: + conn = get_connection() + try: + cursor = conn.cursor() + conv_id = generate_uuid() + cursor.execute("INSERT INTO conversations (id, name, created_by) VALUES (%s, %s, %s)", + (conv_id, name, created_by)) + for uid in member_user_ids: + cursor.execute( + "INSERT INTO conversation_members (conversation_id, user_id, joined_at) VALUES (%s, %s, %s)", + (conv_id, uid, joined_at), + ) + conn.commit() + return conv_id + finally: + conn.close() + + +def add_conversation_member(conversation_id: str, user_id: str, joined_at=None): + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "INSERT IGNORE INTO conversation_members (conversation_id, user_id, joined_at) VALUES (%s, %s, %s)", + (conversation_id, user_id, joined_at), + ) + conn.commit() + finally: + conn.close() + + +def remove_conversation_member(conversation_id: str, user_id: str): + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "DELETE FROM conversation_members WHERE conversation_id = %s AND user_id = %s", + (conversation_id, user_id), + ) + conn.commit() + finally: + conn.close() + + +def count_conversation_members(conversation_id: str) -> int: + """Count members in a conversation.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT COUNT(*) FROM conversation_members WHERE conversation_id = %s", + (conversation_id,), + ) + return cursor.fetchone()[0] + finally: + conn.close() + + +def get_conversation_file_ids(conversation_id: str) -> list[str]: + """Get all file IDs (images + files) uploaded to a conversation.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT file_id FROM image_uploads WHERE conversation_id = %s", + (conversation_id,), + ) + return [row[0] for row in cursor.fetchall()] + finally: + conn.close() + + +def delete_conversation(conversation_id: str): + """Delete a conversation entirely. CASCADE cleans up members, messages, etc.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute("DELETE FROM conversations WHERE id = %s", (conversation_id,)) + conn.commit() + finally: + conn.close() + + +def get_conversation_members(conversation_id: str) -> list[dict]: + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT u.id, u.username, u.email, u.identity_key FROM conversation_members cm " + "JOIN users u ON cm.user_id = u.id " + "WHERE cm.conversation_id = %s", + (conversation_id,), + ) + return cursor.fetchall() + finally: + conn.close() + + +def find_direct_conversation(user_id_a: str, user_id_b: str) -> str | None: + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT cm1.conversation_id FROM conversation_members cm1 " + "JOIN conversation_members cm2 ON cm1.conversation_id = cm2.conversation_id " + "WHERE cm1.user_id = %s AND cm2.user_id = %s " + "AND (SELECT COUNT(*) FROM conversation_members cm3 " + " WHERE cm3.conversation_id = cm1.conversation_id) = 2 " + "LIMIT 1", + (user_id_a, user_id_b), + ) + row = cursor.fetchone() + return row[0] if row else None + finally: + conn.close() + + +def update_conversation_creator(conversation_id: str, new_creator_id: str): + """Transfer group creator role to another member.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "UPDATE conversations SET created_by = %s WHERE id = %s", + (new_creator_id, conversation_id), + ) + conn.commit() + finally: + conn.close() + + +def get_conversation(conversation_id: str) -> dict | None: + """Get conversation by ID.""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT id, created_at, name, created_by, avatar_file FROM conversations WHERE id = %s", + (conversation_id,), + ) + return cursor.fetchone() + finally: + conn.close() + + +def update_conversation_avatar(conversation_id: str, avatar_file: str): + """Set avatar file for a conversation.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "UPDATE conversations SET avatar_file = %s WHERE id = %s", + (avatar_file, conversation_id), + ) + conn.commit() + finally: + conn.close() + + +def update_conversation_name(conversation_id: str, name: str): + """Update the name of a conversation.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "UPDATE conversations SET name = %s WHERE id = %s", + (name, conversation_id), + ) + conn.commit() + finally: + conn.close() + + +def is_conversation_member(conversation_id: str, user_id: str) -> bool: + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT 1 FROM conversation_members WHERE conversation_id = %s AND user_id = %s", + (conversation_id, user_id), + ) + return cursor.fetchone() is not None + finally: + conn.close() + + +def list_user_conversations(user_id: str) -> list[dict]: + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT c.id, c.created_at, c.name, c.created_by, c.avatar_file FROM conversations c " + "JOIN conversation_members cm ON c.id = cm.conversation_id " + "WHERE cm.user_id = %s ORDER BY c.created_at DESC", + (user_id,), + ) + convs = cursor.fetchall() + if not convs: + return convs + # Batch-fetch all members for all conversations in one query (N+1 fix) + conv_ids = [c["id"] for c in convs] + placeholders = ",".join(["%s"] * len(conv_ids)) + cursor.execute( + f"SELECT cm.conversation_id, u.id AS user_id, u.username, u.email " + f"FROM conversation_members cm JOIN users u ON cm.user_id = u.id " + f"WHERE cm.conversation_id IN ({placeholders})", + conv_ids, + ) + members_by_conv: dict[str, list[dict]] = {} + for row in cursor.fetchall(): + cid = row.pop("conversation_id") + members_by_conv.setdefault(cid, []).append(row) + for conv in convs: + conv["members"] = members_by_conv.get(conv["id"], []) + return convs + finally: + conn.close() + + +# --- Group Invitations --- + +def create_invitation(conversation_id: str, user_id: str, invited_by: str): + """Create a pending group invitation.""" + conn = get_connection() + try: + cursor = conn.cursor() + inv_id = generate_uuid() + cursor.execute( + "INSERT IGNORE INTO group_invitations (id, conversation_id, user_id, invited_by) " + "VALUES (%s, %s, %s, %s)", + (inv_id, conversation_id, user_id, invited_by), + ) + conn.commit() + finally: + conn.close() + + +def get_pending_invitations(user_id: str) -> list[dict]: + """Get all pending invitations for a user, joined with conversation and inviter info.""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT gi.id, gi.conversation_id, gi.invited_by, gi.created_at, " + "c.name AS conversation_name, u.username AS invited_by_username " + "FROM group_invitations gi " + "JOIN conversations c ON gi.conversation_id = c.id " + "JOIN users u ON gi.invited_by = u.id " + "WHERE gi.user_id = %s " + "ORDER BY gi.created_at DESC", + (user_id,), + ) + return cursor.fetchall() + finally: + conn.close() + + +def delete_invitation(conversation_id: str, user_id: str): + """Delete a pending invitation.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "DELETE FROM group_invitations WHERE conversation_id = %s AND user_id = %s", + (conversation_id, user_id), + ) + conn.commit() + finally: + conn.close() + + +def has_pending_invitation(conversation_id: str, user_id: str) -> bool: + """Check if a user has a pending invitation for a conversation.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT 1 FROM group_invitations WHERE conversation_id = %s AND user_id = %s", + (conversation_id, user_id), + ) + return cursor.fetchone() is not None + finally: + conn.close() + + +# --- Messages --- + +def store_message( + conversation_id: str, + sender_id: str, + ratchet_header: bytes, + recipients: list[dict], + x3dh_header: bytes | None = None, + sender_chain_id: bytes | None = None, + sender_chain_n: int | None = None, + image_file_id: str | None = None, + sender_device_id: str | None = None, +) -> str: + """Store an encrypted message with per-recipient ciphertext. + + recipients: [{user_id, encrypted_content (bytes), nonce (bytes), + device_id (str, optional), ratchet_header (bytes, optional), + x3dh_header (bytes, optional)}] + """ + conn = get_connection() + try: + cursor = conn.cursor() + msg_id = generate_uuid() + cursor.execute( + "INSERT INTO messages (id, conversation_id, sender_id, sender_device_id, " + "ratchet_header, x3dh_header, sender_chain_id, sender_chain_n, image_file_id) " + "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)", + (msg_id, conversation_id, sender_id, sender_device_id, ratchet_header, + x3dh_header, sender_chain_id, sender_chain_n, image_file_id), + ) + for r in recipients: + device_id = r.get("device_id", SELF_DEVICE_ID) + cursor.execute( + "INSERT INTO message_recipients (message_id, user_id, device_id, " + "encrypted_content, nonce, ratchet_header, x3dh_header) " + "VALUES (%s, %s, %s, %s, %s, %s, %s)", + (msg_id, r["user_id"], device_id, r["encrypted_content"], r["nonce"], + r.get("ratchet_header"), r.get("x3dh_header")), + ) + conn.commit() + cursor.execute("SELECT created_at FROM messages WHERE id = %s", (msg_id,)) + row = cursor.fetchone() + created_at = row[0].isoformat() if row else None + return msg_id, created_at + finally: + conn.close() + + +def get_messages(conversation_id: str, user_id: str, limit: int = 50, offset: int = 0, + device_id: str | None = None, after_ts: str | None = None) -> list[dict]: + """Get messages for a user in a conversation, JOINing their per-recipient ciphertext. + + If device_id is set, returns rows where mr.device_id matches OR is the sentinel + (self-encrypted / legacy). May return duplicate message IDs when both device-specific + and self-encrypted rows exist — caller should deduplicate (prefer device-specific). + + If after_ts is set, only returns messages created after that timestamp (ISO format). + Results are ordered ASC when after_ts is used, DESC otherwise. + """ + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + if device_id: + where_parts = ["m.conversation_id = %s", + "(cm.joined_at IS NULL OR m.created_at >= cm.joined_at)"] + params = [user_id, device_id, SELF_DEVICE_ID, user_id, conversation_id] + + if after_ts: + where_parts.append("m.created_at > %s") + params.append(after_ts) + + where_clause = " AND ".join(where_parts) + order = "ASC" if after_ts else "DESC" + + cursor.execute( + "SELECT m.id, m.conversation_id, m.sender_id, m.sender_device_id, " + "m.ratchet_header, m.x3dh_header, " + "m.sender_chain_id, m.sender_chain_n, m.created_at, m.deleted_at, m.image_file_id, " + "m.pinned_at, m.pinned_by, " + "mr.encrypted_content, mr.nonce, mr.device_id AS mr_device_id, " + "mr.ratchet_header AS mr_ratchet_header, mr.x3dh_header AS mr_x3dh_header " + "FROM messages m " + "JOIN message_recipients mr ON m.id = mr.message_id AND mr.user_id = %s " + " AND (mr.device_id = %s OR mr.device_id = %s) " + "JOIN conversation_members cm ON cm.conversation_id = m.conversation_id AND cm.user_id = %s " + f"WHERE {where_clause} " + f"ORDER BY m.created_at {order} LIMIT %s OFFSET %s", + (*params, limit, offset), + ) + else: + where_parts = ["m.conversation_id = %s", + "(cm.joined_at IS NULL OR m.created_at >= cm.joined_at)"] + params = [user_id, user_id, conversation_id] + + if after_ts: + where_parts.append("m.created_at > %s") + params.append(after_ts) + + where_clause = " AND ".join(where_parts) + order = "ASC" if after_ts else "DESC" + + cursor.execute( + "SELECT m.id, m.conversation_id, m.sender_id, m.sender_device_id, " + "m.ratchet_header, m.x3dh_header, " + "m.sender_chain_id, m.sender_chain_n, m.created_at, m.deleted_at, m.image_file_id, " + "m.pinned_at, m.pinned_by, " + "mr.encrypted_content, mr.nonce, mr.device_id AS mr_device_id, " + "mr.ratchet_header AS mr_ratchet_header, mr.x3dh_header AS mr_x3dh_header " + "FROM messages m " + "JOIN message_recipients mr ON m.id = mr.message_id AND mr.user_id = %s " + "JOIN conversation_members cm ON cm.conversation_id = m.conversation_id AND cm.user_id = %s " + f"WHERE {where_clause} " + f"ORDER BY m.created_at {order} LIMIT %s OFFSET %s", + (*params, limit, offset), + ) + return cursor.fetchall() + finally: + conn.close() + + +def count_messages(conversation_id: str, user_id: str) -> int: + """Count total messages visible to a user in a conversation.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT COUNT(DISTINCT m.id) " + "FROM messages m " + "JOIN message_recipients mr ON m.id = mr.message_id AND mr.user_id = %s " + "JOIN conversation_members cm ON cm.conversation_id = m.conversation_id AND cm.user_id = %s " + "WHERE m.conversation_id = %s AND (cm.joined_at IS NULL OR m.created_at >= cm.joined_at)", + (user_id, user_id, conversation_id), + ) + row = cursor.fetchone() + return row[0] if row else 0 + finally: + conn.close() + + +def get_message_conversation(message_id: str) -> str | None: + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute("SELECT conversation_id FROM messages WHERE id = %s", (message_id,)) + row = cursor.fetchone() + return row[0] if row else None + finally: + conn.close() + + +def get_message_sender(message_id: str) -> str | None: + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute("SELECT sender_id FROM messages WHERE id = %s", (message_id,)) + row = cursor.fetchone() + return row[0] if row else None + finally: + conn.close() + + +def get_deleted_messages_since(conversation_id: str, user_id: str, since_ts: str) -> list[str]: + """Return message IDs that were soft-deleted since the given timestamp.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT m.id FROM messages m " + "JOIN conversation_members cm ON cm.conversation_id = m.conversation_id AND cm.user_id = %s " + "WHERE m.conversation_id = %s AND m.deleted_at IS NOT NULL AND m.deleted_at > %s", + (user_id, conversation_id, since_ts), + ) + return [row[0] for row in cursor.fetchall()] + finally: + conn.close() + + +# --- Reactions --- + +ALLOWED_REACTIONS = {"thumbsup", "heart", "laugh", "surprised", "sad", "thumbsdown"} + + +def add_reaction(message_id: str, user_id: str, reaction: str) -> tuple[bool, str | None]: + """Add or replace a reaction. Returns (changed, old_reaction_or_None).""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT reaction FROM message_reactions WHERE message_id = %s AND user_id = %s", + (message_id, user_id), + ) + row = cursor.fetchone() + old_reaction = row["reaction"] if row else None + + if old_reaction == reaction: + return False, None # already same reaction + + if old_reaction: + cursor.execute( + "UPDATE message_reactions SET reaction = %s, created_at = CURRENT_TIMESTAMP " + "WHERE message_id = %s AND user_id = %s", + (reaction, message_id, user_id), + ) + else: + cursor.execute( + "INSERT INTO message_reactions (id, message_id, user_id, reaction) " + "VALUES (%s, %s, %s, %s)", + (generate_uuid(), message_id, user_id, reaction), + ) + conn.commit() + return True, old_reaction + finally: + conn.close() + + +def remove_reaction(message_id: str, user_id: str) -> bool: + """Remove a user's reaction. Returns True if deleted.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "DELETE FROM message_reactions WHERE message_id = %s AND user_id = %s", + (message_id, user_id), + ) + conn.commit() + return cursor.rowcount > 0 + finally: + conn.close() + + +def get_reactions(message_ids: list[str]) -> dict[str, list[dict]]: + """Get reactions for multiple messages. Returns {msg_id: [{user_id, reaction, created_at}]}.""" + if not message_ids: + return {} + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + placeholders = ",".join(["%s"] * len(message_ids)) + cursor.execute( + f"SELECT message_id, user_id, reaction, created_at " + f"FROM message_reactions WHERE message_id IN ({placeholders}) " + f"ORDER BY created_at", + tuple(message_ids), + ) + result = {} + for row in cursor.fetchall(): + mid = row["message_id"] + if mid not in result: + result[mid] = [] + result[mid].append({ + "user_id": row["user_id"], + "reaction": row["reaction"], + "created_at": row["created_at"].isoformat() if hasattr(row["created_at"], "isoformat") else str(row["created_at"]), + }) + return result + finally: + conn.close() + + +# --- Pins --- + +def pin_message(message_id: str, user_id: str, conversation_id: str) -> bool: + """Pin a message. Returns True on success.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "UPDATE messages SET pinned_at = NOW(), pinned_by = %s " + "WHERE id = %s AND conversation_id = %s AND pinned_at IS NULL", + (user_id, message_id, conversation_id), + ) + conn.commit() + return cursor.rowcount > 0 + finally: + conn.close() + + +def unpin_message(message_id: str, conversation_id: str) -> bool: + """Unpin a message. Returns True on success.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "UPDATE messages SET pinned_at = NULL, pinned_by = NULL " + "WHERE id = %s AND conversation_id = %s AND pinned_at IS NOT NULL", + (message_id, conversation_id), + ) + conn.commit() + return cursor.rowcount > 0 + finally: + conn.close() + + +def get_pinned_messages(conversation_id: str, user_id: str) -> list[dict]: + """Get pinned messages for a conversation (membership verified via JOIN).""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT m.id AS message_id, m.sender_id, m.pinned_at, m.pinned_by, m.created_at " + "FROM messages m " + "JOIN conversation_members cm ON cm.conversation_id = m.conversation_id AND cm.user_id = %s " + "WHERE m.conversation_id = %s AND m.pinned_at IS NOT NULL AND m.deleted_at IS NULL " + "ORDER BY m.pinned_at DESC", + (user_id, conversation_id), + ) + rows = cursor.fetchall() + for r in rows: + for k in ("pinned_at", "created_at"): + if r.get(k) and hasattr(r[k], "isoformat"): + r[k] = r[k].isoformat() + return rows + finally: + conn.close() + + +# --- Group Sender Keys --- + +def store_sender_key(conversation_id: str, sender_id: str, chain_id: bytes, + device_id: str | None = None): + """Store or update a sender key chain ID for a group member's device.""" + conn = get_connection() + try: + cursor = conn.cursor() + dev = device_id or SELF_DEVICE_ID + cursor.execute( + "REPLACE INTO group_sender_keys (conversation_id, sender_id, device_id, chain_id) " + "VALUES (%s, %s, %s, %s)", + (conversation_id, sender_id, dev, chain_id), + ) + conn.commit() + finally: + conn.close() + + +def get_sender_key(conversation_id: str, sender_id: str, + device_id: str | None = None) -> dict | None: + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + dev = device_id or SELF_DEVICE_ID + cursor.execute( + "SELECT chain_id, created_at FROM group_sender_keys " + "WHERE conversation_id = %s AND sender_id = %s AND device_id = %s", + (conversation_id, sender_id, dev), + ) + return cursor.fetchone() + finally: + conn.close() + + +# --- Read Receipts --- + +def filter_message_ids_by_conversation(conversation_id: str, message_ids: list[str]) -> list[str]: + """Return only message_ids that belong to the given conversation.""" + if not message_ids: + return [] + conn = get_connection() + try: + cursor = conn.cursor() + placeholders = ",".join(["%s"] * len(message_ids)) + cursor.execute( + f"SELECT id FROM messages WHERE id IN ({placeholders}) AND conversation_id = %s", + (*message_ids, conversation_id), + ) + return [row[0] for row in cursor.fetchall()] + finally: + conn.close() + + +def mark_messages_read(conversation_id: str, user_id: str, message_ids: list[str]): + if not message_ids: + return + conn = get_connection() + try: + cursor = conn.cursor() + # M1 fix: JOIN messages to verify message_ids belong to conversation_id + placeholders = ",".join(["%s"] * len(message_ids)) + cursor.execute( + f"INSERT IGNORE INTO message_reads (message_id, user_id) " + f"SELECT m.id, %s FROM messages m " + f"WHERE m.id IN ({placeholders}) AND m.conversation_id = %s", + (user_id, *message_ids, conversation_id), + ) + conn.commit() + finally: + conn.close() + + +def mark_conversation_read(conversation_id: str, user_id: str) -> int: + """Mark ALL unread messages in a conversation as read for user. Returns count marked.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "INSERT IGNORE INTO message_reads (message_id, user_id) " + "SELECT m.id, %s " + "FROM messages m " + "JOIN message_recipients mr ON mr.message_id = m.id AND mr.user_id = %s " + "LEFT JOIN message_reads mrd ON mrd.message_id = m.id AND mrd.user_id = %s " + "WHERE m.conversation_id = %s AND m.sender_id != %s " + "AND m.deleted_at IS NULL AND mrd.message_id IS NULL", + (user_id, user_id, user_id, conversation_id, user_id), + ) + count = cursor.rowcount + conn.commit() + return count + finally: + conn.close() + + +def get_unread_counts(user_id: str, max_age_days: int = 0) -> dict[str, int]: + """Return {conversation_id: unread_count} for all conversations the user is in. + + max_age_days: if > 0, only count messages younger than this many days. + Must match METADATA_RETENTION_DAYS to avoid phantom unreads after read cleanup. + """ + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + age_filter = "" + params = [user_id, user_id, user_id] + if max_age_days > 0: + age_filter = " AND m.created_at >= DATE_SUB(NOW(), INTERVAL %s DAY)" + params.append(max_age_days) + cursor.execute( + "SELECT m.conversation_id, COUNT(DISTINCT m.id) AS cnt " + "FROM messages m " + "JOIN message_recipients mr ON mr.message_id = m.id AND mr.user_id = %s " + "LEFT JOIN message_reads mrd ON mrd.message_id = m.id AND mrd.user_id = %s " + "WHERE m.sender_id != %s AND m.deleted_at IS NULL AND mrd.message_id IS NULL" + f"{age_filter} " + "GROUP BY m.conversation_id", + params, + ) + return {row["conversation_id"]: row["cnt"] for row in cursor.fetchall()} + finally: + conn.close() + + +def get_message_read_status(message_ids: list[str]) -> dict: + if not message_ids: + return {} + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + placeholders = ",".join(["%s"] * len(message_ids)) + cursor.execute( + f"SELECT mr.message_id, mr.user_id, mr.read_at " + f"FROM message_reads mr " + f"WHERE mr.message_id IN ({placeholders})", + tuple(message_ids), + ) + result = {} + for row in cursor.fetchall(): + mid = row["message_id"] + if mid not in result: + result[mid] = [] + result[mid].append({ + "user_id": row["user_id"], + "read_at": row["read_at"].isoformat() if hasattr(row["read_at"], "isoformat") else str(row["read_at"]), + }) + return result + finally: + conn.close() + + +# --- Delivery Receipts --- + +def mark_messages_delivered(conversation_id: str, user_id: str, message_ids: list[str]): + """Batch insert delivery receipts (INSERT IGNORE — idempotent).""" + if not message_ids: + return + conn = get_connection() + try: + cursor = conn.cursor() + # M1 fix: JOIN messages to verify message_ids belong to conversation_id + placeholders = ",".join(["%s"] * len(message_ids)) + cursor.execute( + f"INSERT IGNORE INTO message_deliveries (message_id, user_id) " + f"SELECT m.id, %s FROM messages m " + f"WHERE m.id IN ({placeholders}) AND m.conversation_id = %s", + (user_id, *message_ids, conversation_id), + ) + conn.commit() + finally: + conn.close() + + +def get_message_delivery_status(message_ids: list[str]) -> dict: + """Get delivery status for messages. Returns {msg_id: [{user_id, delivered_at}]}.""" + if not message_ids: + return {} + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + placeholders = ",".join(["%s"] * len(message_ids)) + cursor.execute( + f"SELECT md.message_id, md.user_id, md.delivered_at " + f"FROM message_deliveries md " + f"WHERE md.message_id IN ({placeholders})", + tuple(message_ids), + ) + result = {} + for row in cursor.fetchall(): + mid = row["message_id"] + if mid not in result: + result[mid] = [] + result[mid].append({ + "user_id": row["user_id"], + "delivered_at": row["delivered_at"].isoformat() if hasattr(row["delivered_at"], "isoformat") else str(row["delivered_at"]), + }) + return result + finally: + conn.close() + + +# --- Delete --- + +def soft_delete_message(message_id: str, sender_id: str) -> dict | None: + """Soft-delete a message if sender matches. Returns {'image_file_id': ...} or None.""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT sender_id, image_file_id FROM messages WHERE id = %s AND deleted_at IS NULL", + (message_id,), + ) + row = cursor.fetchone() + if not row or row["sender_id"] != sender_id: + return None + cursor.execute( + "UPDATE messages SET deleted_at = NOW() WHERE id = %s", + (message_id,), + ) + # Clear per-recipient ciphertext + cursor.execute( + "UPDATE message_recipients SET encrypted_content = %s WHERE message_id = %s", + (b"", message_id), + ) + conn.commit() + return {"image_file_id": row.get("image_file_id")} + finally: + conn.close() + + +def set_message_image_file_id(message_id: str, file_id: str): + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "UPDATE messages SET image_file_id = %s WHERE id = %s", + (file_id, message_id), + ) + conn.commit() + finally: + conn.close() + + +# --- Image Uploads --- + +def create_image_upload(file_id: str, conversation_id: str, uploader_id: str, file_size: int): + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "INSERT INTO image_uploads (file_id, conversation_id, uploader_id, file_size) " + "VALUES (%s, %s, %s, %s)", + (file_id, conversation_id, uploader_id, file_size), + ) + conn.commit() + finally: + conn.close() + + +def complete_image_upload(file_id: str): + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "UPDATE image_uploads SET completed = TRUE WHERE file_id = %s", + (file_id,), + ) + conn.commit() + finally: + conn.close() + + +def get_image_upload(file_id: str) -> dict | None: + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT file_id, conversation_id, uploader_id, file_size, completed, created_at " + "FROM image_uploads WHERE file_id = %s", + (file_id,), + ) + return cursor.fetchone() + finally: + conn.close() + + +def delete_image_upload(file_id: str): + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute("DELETE FROM image_uploads WHERE file_id = %s", (file_id,)) + conn.commit() + finally: + conn.close() + + +# --- User Profiles --- + +def create_default_profile(user_id: str): + """Create a default profile for a new user.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "INSERT IGNORE INTO user_profiles (user_id) VALUES (%s)", + (user_id,), + ) + conn.commit() + finally: + conn.close() + + +def get_user_profile(user_id: str, viewer_id: str | None = None) -> dict | None: + """Get user profile joined with user info. Respects visibility if viewer is different user.""" + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT u.id AS user_id, u.username, u.email, u.created_at, " + "p.phone, p.phone_visible, p.email_visible, p.location, " + "p.location_visible, p.avatar_file, p.updated_at " + "FROM users u LEFT JOIN user_profiles p ON u.id = p.user_id " + "WHERE u.id = %s", + (user_id,), + ) + row = cursor.fetchone() + if not row: + return None + # If viewing someone else's profile, apply visibility rules + if viewer_id and viewer_id != user_id: + if not row.get("email_visible"): + row["email"] = None + if not row.get("phone_visible"): + row["phone"] = None + if not row.get("location_visible"): + row["location"] = None + return row + finally: + conn.close() + + +def update_user_profile(user_id: str, **fields): + """Upsert user profile fields. Allowed: phone, phone_visible, email_visible, + location, location_visible, avatar_file.""" + allowed = {"phone", "phone_visible", "email_visible", "location", + "location_visible", "avatar_file"} + filtered = {k: v for k, v in fields.items() if k in allowed} + if not filtered: + return + conn = get_connection() + try: + cursor = conn.cursor() + # Upsert: insert default then update + cursor.execute( + "INSERT IGNORE INTO user_profiles (user_id) VALUES (%s)", + (user_id,), + ) + set_clause = ", ".join(f"{k} = %s" for k in filtered) + values = list(filtered.values()) + [user_id] + cursor.execute( + f"UPDATE user_profiles SET {set_clause} WHERE user_id = %s", + values, + ) + conn.commit() + finally: + conn.close() + + +def batch_reencrypt_messages(user_id: str, updates: list[dict]): + """Batch upsert message_recipients rows with self-encryption key data. + + Each update: {message_id, encrypted_content (bytes), nonce (bytes)}. + Sets ratchet_header to '{"self":true}' and clears x3dh_header. + Uses INSERT ... ON DUPLICATE KEY UPDATE so it works for both sent messages + (which already have a SELF_DEVICE_ID row) and received messages (which don't). + """ + if not updates: + return + conn = get_connection() + try: + cursor = conn.cursor() + self_header = b'{"self":true}' + for u in updates: + cursor.execute( + "INSERT INTO message_recipients " + "(message_id, user_id, device_id, encrypted_content, nonce, ratchet_header, x3dh_header) " + "VALUES (%s, %s, %s, %s, %s, %s, NULL) " + "ON DUPLICATE KEY UPDATE encrypted_content = VALUES(encrypted_content), " + "nonce = VALUES(nonce), ratchet_header = VALUES(ratchet_header), x3dh_header = NULL", + (u["message_id"], user_id, SELF_DEVICE_ID, + u["encrypted_content"], u["nonce"], self_header), + ) + conn.commit() + finally: + conn.close() + + +# --- Phantom Users --- + +def create_phantom_user(email: str) -> dict: + """Create a phantom user with valid crypto keys for X3DH. + + Phantom users have rsa_public_key = 'PHANTOM' as a marker. + Returns user dict: {id, username, email, identity_key}. + """ + username = email.split("@")[0] + user_id = generate_uuid() + + # Generate real crypto keys so X3DH works on the client side + ik_private, ik_public = generate_identity_keypair() + ik_public_bytes = serialize_ed25519_public(ik_public) + + spk = generate_signed_prekey(ik_private) + spk_pub_bytes = serialize_x25519_public(spk["public"]) + spk_sig = spk["signature"] + + opks = generate_one_time_prekeys(count=5) + + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "INSERT INTO users (id, username, email, rsa_public_key, identity_key) " + "VALUES (%s, %s, %s, %s, %s)", + (user_id, username, email, "PHANTOM", ik_public_bytes), + ) + cursor.execute( + "INSERT INTO signed_prekeys (id, user_id, public_key, signature) VALUES (%s, %s, %s, %s)", + (spk["id"], user_id, spk_pub_bytes, spk_sig), + ) + for opk in opks: + cursor.execute( + "INSERT INTO one_time_prekeys (id, user_id, public_key) VALUES (%s, %s, %s)", + (opk["id"], user_id, serialize_x25519_public(opk["public"])), + ) + conn.commit() + return {"id": user_id, "username": username, "email": email, "identity_key": ik_public_bytes} + finally: + conn.close() + + +def is_phantom_user(user_id: str) -> bool: + """Check if a user is a phantom (rsa_public_key == 'PHANTOM').""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute("SELECT rsa_public_key FROM users WHERE id = %s", (user_id,)) + row = cursor.fetchone() + return row is not None and row[0] == "PHANTOM" + finally: + conn.close() + + +def delete_phantom_user(user_id: str): + """Delete a phantom user. CASCADE removes signed_prekeys, one_time_prekeys, + conversation_members, message_recipients, etc.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "DELETE FROM users WHERE id = %s AND rsa_public_key = %s", + (user_id, "PHANTOM"), + ) + conn.commit() + finally: + conn.close() + + +def upgrade_phantom_user(phantom_id: str, username: str, rsa_public_key_pem: str, + identity_key: bytes) -> str | None: + """Upgrade a phantom user to a real user in-place. + + Preserves user_id and all FK references (conversation_members, group_invitations, etc.). + Deletes phantom's server-generated prekeys (real user will upload own on first login). + Returns phantom_id as the new user_id, or None if phantom no longer exists. + """ + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "UPDATE users SET username = %s, rsa_public_key = %s, identity_key = %s " + "WHERE id = %s AND rsa_public_key = 'PHANTOM'", + (username, rsa_public_key_pem, identity_key, phantom_id), + ) + if cursor.rowcount == 0: + conn.rollback() + return None + # Remove phantom's server-generated crypto keys — real user uploads own + cursor.execute("DELETE FROM signed_prekeys WHERE user_id = %s", (phantom_id,)) + cursor.execute("DELETE FROM one_time_prekeys WHERE user_id = %s", (phantom_id,)) + conn.commit() + return phantom_id + finally: + conn.close() + + +def get_all_phantom_user_ids() -> set[str]: + """Return set of all phantom user IDs (for server startup cache).""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute("SELECT id FROM users WHERE rsa_public_key = %s", ("PHANTOM",)) + return {row[0] for row in cursor.fetchall()} + finally: + conn.close() + + +def cleanup_stale_phantoms(max_age_days: int = 30) -> int: + """Delete phantom users older than max_age_days with no active conversations with real users.""" + conn = get_connection() + try: + cursor = conn.cursor() + # Two-step: SELECT ids first, then DELETE. + # MySQL error 1093: can't DELETE from table referenced in subquery. + cursor.execute(""" + SELECT u.id FROM users u + WHERE u.rsa_public_key = 'PHANTOM' + AND u.created_at < DATE_SUB(NOW(), INTERVAL %s DAY) + AND NOT EXISTS ( + SELECT 1 FROM conversation_members cm1 + JOIN conversation_members cm2 ON cm1.conversation_id = cm2.conversation_id + JOIN users u2 ON cm2.user_id = u2.id + WHERE cm1.user_id = u.id + AND u2.rsa_public_key != 'PHANTOM' + ) + """, (max_age_days,)) + ids = [row[0] for row in cursor.fetchall()] + if not ids: + return 0 + cursor.execute( + "DELETE FROM users WHERE id IN (%s)" % ",".join(["%s"] * len(ids)), + ids, + ) + deleted = cursor.rowcount + conn.commit() + return deleted + finally: + conn.close() + + +def remove_conversation_member_atomic(conversation_id: str, user_id: str) -> bool: + """Remove member and return True if actually removed (row existed). M6 TOCTOU fix.""" + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "DELETE FROM conversation_members WHERE conversation_id = %s AND user_id = %s", + (conversation_id, user_id), + ) + conn.commit() + return cursor.rowcount > 0 + finally: + conn.close() + + +def get_stale_uploads(max_age_seconds: int = 3600) -> list[dict]: + conn = get_connection() + try: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT file_id FROM image_uploads " + "WHERE completed = FALSE AND created_at < DATE_SUB(NOW(), INTERVAL %s SECOND)", + (max_age_seconds,), + ) + return cursor.fetchall() + finally: + conn.close() + + +# --------------------------------------------------------------------------- +# Metadata retention cleanup +# --------------------------------------------------------------------------- + +def cleanup_old_reads(days: int = 90, batch_size: int = 10000) -> int: + """Delete message_reads older than N days in batches. + + Only deletes reads for messages whose created_at is also past the retention + window. This prevents phantom unreads: get_unread_counts uses the same + time window (max_age_days) so messages outside the window aren't counted. + """ + total = 0 + while True: + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "DELETE FROM message_reads " + "WHERE read_at < DATE_SUB(NOW(), INTERVAL %s DAY) " + "AND message_id IN (" + " SELECT id FROM messages " + " WHERE created_at < DATE_SUB(NOW(), INTERVAL %s DAY)" + ") LIMIT %s", + (days, days, batch_size), + ) + count = cursor.rowcount + conn.commit() + total += count + if count < batch_size: + break + finally: + conn.close() + return total + + +def cleanup_old_reactions(days: int = 90, batch_size: int = 10000) -> int: + """Delete message_reactions older than N days in batches.""" + total = 0 + while True: + conn = get_connection() + try: + cursor = conn.cursor() + cursor.execute( + "DELETE FROM message_reactions WHERE created_at < DATE_SUB(NOW(), INTERVAL %s DAY) LIMIT %s", + (days, batch_size), + ) + count = cursor.rowcount + conn.commit() + total += count + if count < batch_size: + break + finally: + conn.close() + return total diff --git a/gemini.md b/gemini.md new file mode 100644 index 0000000..48b422b --- /dev/null +++ b/gemini.md @@ -0,0 +1,152 @@ +# Gemini Advanced Roadmap: Beyond the Basics + +Tento dokument obsahuje pokročilé návrhy na vylepšení bezpečnosti, architektury a UX aplikace `encrypted_chat`. Tyto body jdou nad rámec běžného "best practice" a směřují k funkcionalitě profesionálních secure messengerů (Signal, Threema, Wire) se zaměřením na ochranu metadat a anti-forenzní techniky. + +--- + +## 1. Ochrana Metadat & Traffic Analysis Resistance +*Cíl: Server by neměl vědět, KDO s KÝM komunikuje, ani JAKÝ typ dat si posílají.* + +### Sealed Sender (Odesílatel v obálce) +- **Koncept:** Server zná pouze `recipient_id`. Identita odesílatele (`sender_id`) je zašifrována uvnitř zprávy (v "obálce"), kterou server nedokáže přečíst. +- **Implementace:** + 1. Odesílatel vygeneruje klíč pro obálku (např. z profilu příjemce). + 2. Zabalí `sender_id` a payload do šifrovaného bloku. + 3. Server doručí blob příjemci bez ověření odesílatele (ověření proběhne až na klientovi po rozbalení). + 4. **Výhoda:** Při kompromitaci serveru útočník nevidí sociální graf (kdo se s kým baví). + +### Traffic Padding & Constant Bitrate +- **Problém:** Délka paketu prozrazuje obsah (krátký paket = "Ahoj", dlouhý paket = obrázek/klíč). Intervaly prozrazují aktivitu. +- **Řešení:** + 1. **Padding:** Všechny zprávy doplňovat náhodnými daty na fixní velikosti (např. bloky 4KB). + 2. **Dummy Traffic (Chaff):** Klient náhodně odesílá "falešné" pakety na server, které server zahodí nebo vrátí (echo). + 3. **Výhoda:** Pro síťového analytika (ISP) vypadá tok dat jako konstantní šum. + +--- + +## 2. Anti-Forenzní Ochrana (Client-side) +*Cíl: Minimalizovat dopad fyzického zabavení zařízení nebo vynuceného odemčení.* + +### Duress Password (Heslo pod nátlakem) +- **Funkce:** Uživatel si nastaví *druhé* heslo. +- **Chování:** Pokud se přihlásí tímto heslem: + - **Varianta A (Decoy):** Odemkne se prázdná nebo falešná databáze s neškodnými konverzacemi. + - **Varianta B (Panic):** Aplikace na pozadí tiše provede **secure wipe** (přepis) privátních klíčů a reálné DB, zatímco uživateli zobrazí "Connection Error". + +### Secure Deletion & DB Vacuuming +- **Problém:** SQL `DELETE` data nesmaže fyzicky, jen označí místo jako volné. +- **Řešení:** + 1. Před smazáním zprávy přepsat obsah náhodnými byty (`UPDATE messages SET content = random_blob WHERE id = ...`). + 2. Pravidelně spouštět `VACUUM` (u SQLite) nebo optimalizaci tabulek. + 3. Pro soubory (obrázky) použít bezpečné mazání (overwrite passes) před `os.unlink()`. + +### Disappearing Messages (TTL) +- **Funkce:** Odesílatel nastaví životnost zprávy (např. 1 minuta). +- **Implementace:** Odpočet začíná okamžikem zobrazení (Read Receipt). Po uplynutí času klient data nenávratně smaže z disku (včetně secure wipe). Server maže ihned po doručení. + +--- + +## 3. Infrastruktura & Škálování +*Cíl: Odlehčit Python procesu a databázi pro podporu tisíců uživatelů.* + +### Object Storage (MinIO / S3) + Presigned URLs +- **Problém:** `server.py` blokuje I/O při příjmu velkých souborů. +- **Řešení:** + 1. Klient požádá server o upload. + 2. Server vygeneruje **Presigned PUT URL** (časově omezený token pro přímý upload do MinIO/S3). + 3. Klient nahrává data přímo do úložiště (obchází aplikační server). + 4. Server ukládá pouze odkaz (URL/Key). +- **Výhoda:** Masivní zrychlení, server řeší jen metadata. + +### Read/Write Splitting (MySQL Replication) +- **Architektura:** + - **Master DB:** Pouze pro `INSERT`, `UPDATE`, `DELETE`. + - **Read Replicas (Slaves):** Pro těžké `SELECT` dotazy (historie zpráv, hledání). +- **Implementace v `db.py`:** Router, který podle typu dotazu volí connection pool. + +--- + +## 4. Protokol & Funkce +*Cíl: Rozšíření možností komunikace bez nutnosti centralizovaného streamování.* + +### P2P Volání (WebRTC Signalizace) +- **Koncept:** Využít existující bezpečný kanál (Double Ratchet) pro výměnu SDP (Session Description Protocol) paketů. +- **Flow:** + 1. Alice pošle Bobovi zašifrovanou zprávu typu `CALL_OFFER` s parametry WebRTC. + 2. Bob odpoví `CALL_ANSWER`. + 3. Klienti si vymění `ICE_CANDIDATES` (IP adresy/porty) a naváží přímé P2P spojení (UDP). + 4. Audio/Video stream (SRTP) jde mimo server. + +### Diferenciální Synchronizace (Merkle Trees) +- **Problém:** Stahování seznamu kontaktů (`get_user_contacts`) je pomalé při velkém množství dat. +- **Řešení:** Klient a server si udržují Hash Tree (Merkle Tree) stavu. Při synchronizaci porovnají pouze root hash. Pokud se liší, stahují se jen změněné větve stromu (delta update). + +--- + +## 5. UI/UX (PyQt Speciality) +*Cíl: Ochrana soukromí na úrovni OS a skrytá komunikace.* + +### Privacy Overlay (Task Switcher) +- **Funkce:** Detekovat událost ztráty fokusu okna (`QEvent.WindowDeactivate`) nebo minimalizace. +- **Akce:** Překrýt obsah okna rozmazaným efektem (`QGraphicsBlurEffect`) nebo logem aplikace. +- **Důvod:** Zabrání operačnímu systému (Windows/Linux/macOS) vytvořit čitelný náhled okna v Alt+Tab menu nebo v historii aktivit. + +### Steganografie +- **Funkce:** Ukrýt šifrovanou zprávu do obrazových dat nevinného obrázku (např. meme kočky). +- **Implementace:** Modifikace LSB (Least Significant Bit) pixelů obrázku. +- **Výhoda:** Pro síťového admina nebo forenzní analýzu to vypadá jako běžné posílání obrázků, přítomnost šifrované komunikace je popiratelná. + +--- + +## 6. High Availability Architecture (Distribuovaný Cluster) +*Cíl: Zajištění provozu i při výpadku/napadení serveru (Active-Active "RAID 1 přes síť").* + +### Architektura: Geograficky Distribuovaný "Zero-Trust" Cluster + +#### 1. Vstupní brána (Global Traffic Manager) +- **Funkce:** Rozděluje klienty mezi dostupné servery (Round Robin / Geo-DNS). +- **Self-Healing:** Při výpadku Serveru A okamžitě přesměruje provoz na Server B. Uživatel nic nepozná. + +#### 2. Aplikační vrstva (Stateless Servers) +- **Stav:** Servery jsou **bezstavové**. `server.py` neukládá nic důležitého v RAM. +- **Škálování:** Můžete spustit N instancí serveru. Je jedno, ke kterému se uživatel připojí. +- **Komunikace:** Servery spolu mluví přes rychlý Message Bus (Redis Pub/Sub) pro doručování real-time zpráv mezi uživateli na různých uzlech. + +#### 3. Datová vrstva (Zrcadlení Dat - "RAID 1") +- **Databáze (MySQL Galera Cluster):** Synchronní multi-master replikace. Zápis na Serveru A se potvrdí, až když je fyzicky zapsán i na Serveru B (a C). + - *Efekt:* Ztráta serveru neznamená ztrátu dat (klíčů, zpráv). +- **Soubory (MinIO Cluster):** Distribuovaný Object Storage s Erasure Coding. Soubory jsou matematicky rozprostřeny přes všechny servery. Výpadek disku/serveru nevadí. + +#### 4. Bezpečnostní pojistky ("Poisoned Node") +- **Soft Delete:** Databáze nemaže data ihned, ale označuje je jako smazané (ochrana proti `DELETE *` od útočníka). +- **Client-Side Verification:** I kdyby kompromitovaný server posílal podvržené klíče, klienti ověřují digitální podpisy (Identity Keys). Server nemůže zfalšovat identitu uživatelů. + +--- + +## 7. Technický Upgrade pro Stateless Architekturu +*Cíl: Odstranit závislost na paměti procesu (RAM) pro umožnění horizontálního škálování.* + +### 1. Redis jako Distribuovaná Paměť +Nahrazení Python `dict` struktur, které jsou lokální pro jeden proces, za centrální Redis úložiště přístupné všem serverům. + +* **Párovací Session:** + * *Stav:* `pairing_sessions` (dict) -> Redis Key `pair:{code}` (Hash/String s TTL). + * *Efekt:* Uživatel může vyžádat kód na Serveru A a potvrdit ho na Serveru B. +* **Rate Limiting:** + * *Stav:* `rate_limits` (dict) -> Redis Key `rl:{ip}:{action}` (Counter s EXPIRE). + * *Efekt:* Limity platí globálně pro celý cluster, ne jen per server. + +### 2. Redis Pub/Sub pro Real-Time Routing +Doručení zprávy uživateli, který je připojen k JINÉMU serveru než odesílatel. + +* **Princip:** + 1. Server A (odesílatel) zjistí, že příjemce Bob není připojen lokálně. + 2. Server A publikuje zprávu do Redis kanálu `user:{bob_user_id}`. + 3. Server B (kde je Bob připojen) tento kanál poslouchá (subscribe). + 4. Server B přijme zprávu z Redisu a pošle ji Bobovi do otevřeného TCP socketu. + +### 3. Session Sticky vs. Stateless Uploads +Řešení pro nahrávání souborů po částech (chunks). + +* **Varianta A (Infrastructure - Sticky Sessions):** Load Balancer (HAProxy/Nginx) zajistí, že všechny požadavky od jedné IP jdou vždy na stejný server. Nejjednodušší, nevyžaduje změnu kódu. +* **Varianta B (Architectural - Direct Upload):** Viz bod 3 "Object Storage + Presigned URLs". Server vůbec nepřijímá data souboru, pouze vygeneruje token. Plně stateless řešení. diff --git a/gui_client.py b/gui_client.py new file mode 100644 index 0000000..2513061 --- /dev/null +++ b/gui_client.py @@ -0,0 +1,6338 @@ +"""PyQt6 GUI client for encrypted chat.""" + +import asyncio +import json +import logging +import os +from collections import OrderedDict + +logger = logging.getLogger(__name__) +import re +import sys +from functools import partial + +from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer, QUrl, QSize, QRect, QPoint, QPointF +from PyQt6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QLineEdit, QLabel, QListWidget, QListWidgetItem, QTextEdit, + QSplitter, QMessageBox, QInputDialog, QMenu, QStackedWidget, + QDialog, QFileDialog, QScrollArea, QFrame, QSystemTrayIcon, + QSizePolicy, QStyledItemDelegate, +) +from PyQt6.QtGui import QFont, QFontMetricsF, QAction, QPixmap, QImage, QDesktopServices, QIcon, QPainter, QColor, QBrush, QPen, QShortcut, QKeySequence +from PyQt6.QtWidgets import QGraphicsDropShadowEffect +from PyQt6.QtWidgets import QStyle + +from chat_core import ChatClient, IdentityKeyChanged +from theme import ThemeManager, c, qss, tm + +# H10: Image validation limits +MAX_IMAGE_DATA_SIZE = 10 * 1024 * 1024 # 10 MB max raw image data +MAX_IMAGE_DIMENSION = 8192 # 8K pixels max + + +def _safe_load_image(data: bytes) -> QImage | None: + """Load image with size and dimension validation (H10).""" + if not data or len(data) > MAX_IMAGE_DATA_SIZE: + return None + qimg = QImage.fromData(data) + if qimg.isNull(): + return None + if qimg.width() > MAX_IMAGE_DIMENSION or qimg.height() > MAX_IMAGE_DIMENSION: + return None + return qimg + + +def _safe_filename(name: str, default: str = "file") -> str: + """Sanitize filename — strip path components, prevent traversal (H11).""" + name = os.path.basename(name) + name = name.replace("\x00", "") + return name if name else default + + +_AVATAR_CACHE_MAX = 512 # max cached avatar pixmaps per cache + + +class _LRUPixmapCache: + """Simple LRU cache for QPixmap objects with a fixed max size.""" + + def __init__(self, maxsize: int = _AVATAR_CACHE_MAX): + self._data: OrderedDict[str, QPixmap] = OrderedDict() + self._maxsize = maxsize + + def get(self, key: str) -> QPixmap | None: + if key in self._data: + self._data.move_to_end(key) + return self._data[key] + return None + + def put(self, key: str, value: QPixmap) -> None: + if key in self._data: + self._data.move_to_end(key) + self._data[key] = value + else: + self._data[key] = value + if len(self._data) > self._maxsize: + self._data.popitem(last=False) + + def __contains__(self, key: str) -> bool: + return key in self._data + + def __getitem__(self, key: str) -> QPixmap: + self._data.move_to_end(key) + return self._data[key] + + def __setitem__(self, key: str, value: QPixmap) -> None: + self.put(key, value) + + def clear(self) -> None: + self._data.clear() + + +# URL regex: matches http:// and https:// URLs in raw (not-yet-escaped) text +_URL_RE = re.compile( + r'(https?://[^\s<>"\')\]]+)', + re.IGNORECASE, +) +_URL_TRAILING_PUNCT = re.compile(r'[.,;:!?]+$') + + +def _linkify_urls(raw_text: str, https_color: str | None = None, + http_color: str | None = None) -> str: + """HTML-escape text and convert URLs into clickable tags. + + HTTPS links get blue styling. HTTP links get orange + unlock icon warning. + Processes raw (unescaped) text — returns HTML-safe string. + """ + _https = https_color or c().link_https + _http = http_color or c().link_http + + def _esc(s): + return s.replace("&", "&").replace("<", "<").replace(">", ">") + + parts = _URL_RE.split(raw_text) + result = [] + for i, part in enumerate(parts): + if i % 2 == 1: + # URL match — strip trailing sentence punctuation + trail_m = _URL_TRAILING_PUNCT.search(part) + if trail_m: + url = part[:trail_m.start()] + trail = part[trail_m.start():] + else: + url = part + trail = "" + url_esc = _esc(url) + if url.lower().startswith("http://"): + result.append( + f'' + f'\U0001f513 {url_esc}' + ) + else: + result.append( + f'' + f'{url_esc}' + ) + if trail: + result.append(_esc(trail)) + else: + result.append(_esc(part)) + return "".join(result) + + +def setup_logging(): + level_name = os.getenv("LOG_LEVEL", "WARNING").upper() + level = getattr(logging, level_name, logging.WARNING) + logging.basicConfig(level=level, format="%(levelname)s: %(message)s") + + + +MAX_INPUT_CHARS = int(os.getenv("MAX_INPUT_CHARS", "2000")) + +# Custom item data roles for conversation list delegate +ROLE_CONV_ID = Qt.ItemDataRole.UserRole +ROLE_DISPLAY_NAME = Qt.ItemDataRole.UserRole + 1 +ROLE_PREVIEW = Qt.ItemDataRole.UserRole + 2 # last message preview text +ROLE_TIMESTAMP = Qt.ItemDataRole.UserRole + 3 # last message relative time +ROLE_UNREAD = Qt.ItemDataRole.UserRole + 4 # int unread count +ROLE_IS_FAV = Qt.ItemDataRole.UserRole + 5 # bool +ROLE_AVATAR = Qt.ItemDataRole.UserRole + 6 # QPixmap (circular, 44px) +ROLE_VERIFIED = Qt.ItemDataRole.UserRole + 7 # str: "verified", "trusted", "" (DMs only) +ROLE_RECEIPT = Qt.ItemDataRole.UserRole + 8 # str: "read", "delivered", "sent", "" (own msgs only) + + +def _relative_time(ts: str) -> str: + """Convert ISO timestamp to relative time string for conversation list.""" + if not ts or len(ts) < 16: + return "" + try: + from datetime import datetime, timezone + # Parse "YYYY-MM-DD HH:MM:SS" or "YYYY-MM-DDTHH:MM:SS" + clean = ts.replace("T", " ")[:19] + msg_time = datetime.strptime(clean, "%Y-%m-%d %H:%M:%S").replace( + tzinfo=timezone.utc + ) + now = datetime.now(timezone.utc) + diff = now - msg_time + secs = int(diff.total_seconds()) + if secs < 60: + return "now" + if secs < 3600: + return f"{secs // 60}m" + if secs < 86400: + return f"{secs // 3600}h" + days = secs // 86400 + if days == 1: + return "Yesterday" + if days < 7: + return msg_time.strftime("%a") # Mon, Tue, ... + return msg_time.strftime("%d/%m") + except Exception: + return ts[11:16] if len(ts) >= 16 else "" + + +def _format_msg_time(ts: str) -> str: + """Format timestamp for message bubbles: HH:MM today, 'Yesterday HH:MM', + 'Mon HH:MM' this week, 'DD.MM. HH:MM' older.""" + if not ts or len(ts) < 16: + return "" + try: + from datetime import datetime, timezone + clean = ts.replace("T", " ")[:19] + msg_time = datetime.strptime(clean, "%Y-%m-%d %H:%M:%S").replace( + tzinfo=timezone.utc + ) + now = datetime.now(timezone.utc) + hhmm = msg_time.strftime("%H:%M") + if msg_time.date() == now.date(): + return hhmm + diff_days = (now.date() - msg_time.date()).days + if diff_days == 1: + return f"Yesterday {hhmm}" + if diff_days < 7: + return f"{msg_time.strftime('%a')} {hhmm}" + if msg_time.year == now.year: + return f"{msg_time.day}.{msg_time.month}. {hhmm}" + return f"{msg_time.day}.{msg_time.month}.{msg_time.year} {hhmm}" + except Exception: + return ts[11:16] if len(ts) >= 16 else "" + + +class ConversationDelegate(QStyledItemDelegate): + """Custom delegate that paints Signal/Telegram-style conversation rows.""" + + ITEM_HEIGHT = 68 + AVATAR_SIZE = 44 + BADGE_SIZE = 20 + HPAD = 10 + VPAD = 10 + + def sizeHint(self, option, index): + return QSize(option.rect.width(), self.ITEM_HEIGHT) + + def paint(self, painter, option, index): + painter.save() + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + t = c() + + rect = option.rect + is_selected = bool(option.state & QStyle.StateFlag.State_Selected) + is_hover = bool(option.state & QStyle.StateFlag.State_MouseOver) + + # Background + if is_selected: + painter.fillRect(rect, QColor(t.bg_selected)) + elif is_hover: + painter.fillRect(rect, QColor(t.bg_hover)) + + # Data from item roles + name = index.data(ROLE_DISPLAY_NAME) or "" + preview = index.data(ROLE_PREVIEW) or "" + timestamp = index.data(ROLE_TIMESTAMP) or "" + unread = index.data(ROLE_UNREAD) or 0 + is_fav = index.data(ROLE_IS_FAV) or False + avatar_pix = index.data(ROLE_AVATAR) + verified = index.data(ROLE_VERIFIED) or "" + + x = rect.x() + self.HPAD + y = rect.y() + self.VPAD + avail_w = rect.width() - 2 * self.HPAD + + # -- Avatar (left) -- + av_y = rect.y() + (rect.height() - self.AVATAR_SIZE) // 2 + if avatar_pix and not avatar_pix.isNull(): + painter.drawPixmap(x, av_y, self.AVATAR_SIZE, self.AVATAR_SIZE, + avatar_pix) + else: + painter.setBrush(QBrush(QColor(t.bg_secondary))) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawEllipse(x, av_y, self.AVATAR_SIZE, self.AVATAR_SIZE) + painter.setPen(QColor(t.text_muted)) + f = QFont() + f.setPointSize(14) + f.setBold(True) + painter.setFont(f) + painter.drawText( + QRect(x, av_y, self.AVATAR_SIZE, self.AVATAR_SIZE), + Qt.AlignmentFlag.AlignCenter, + name[0].upper() if name else "?", + ) + + text_x = x + self.AVATAR_SIZE + 10 + text_w = avail_w - self.AVATAR_SIZE - 10 + + # -- Timestamp (top-right) -- + ts_w = 50 + painter.setPen(QColor(t.text_muted)) + ts_font = QFont() + ts_font.setPointSize(8) + painter.setFont(ts_font) + ts_rect = QRect( + rect.x() + rect.width() - self.HPAD - ts_w, + y, ts_w, 18, + ) + painter.drawText(ts_rect, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, timestamp) + + name_w = text_w - ts_w - 8 + + # -- Name (line 1) -- + name_font = QFont() + name_font.setPointSize(10) + if unread > 0: + name_font.setBold(True) + painter.setFont(name_font) + painter.setPen(QColor(t.text_primary)) + display_name = name + if is_fav: + display_name = f"\u2605 {name}" + # Elide name to fit + fm = painter.fontMetrics() + elided_name = fm.elidedText(display_name, Qt.TextElideMode.ElideRight, name_w) + painter.drawText(text_x, y, name_w, 22, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, + elided_name) + + # -- Verification badge (after name) -- + if verified == "verified": + name_text_w = fm.horizontalAdvance(elided_name) + badge_x = text_x + name_text_w + 4 + badge_y_center = y + 11 + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(QBrush(QColor(t.success))) + painter.drawEllipse(badge_x, badge_y_center - 5, 10, 10) + # Checkmark inside circle + painter.setPen(QPen(QColor(t.bg_primary), 1.5)) + painter.drawLine(badge_x + 2, badge_y_center, badge_x + 4, badge_y_center + 2) + painter.drawLine(badge_x + 4, badge_y_center + 2, badge_x + 8, badge_y_center - 3) + + # -- Preview (line 2) -- + preview_y = y + 24 + preview_font = QFont() + preview_font.setPointSize(9) + painter.setFont(preview_font) + preview_w = text_w - (self.BADGE_SIZE + 8 if unread > 0 else 0) + fm2 = painter.fontMetrics() + receipt = index.data(ROLE_RECEIPT) or "" + preview_x = text_x + if receipt: + check_color = QColor(t.success) if receipt == "read" else QColor(t.text_muted) + painter.setPen(check_color) + single_w = fm2.horizontalAdvance("\u2713") + overlap = single_w * 0.4 + cy = preview_y + 10 # vertical center of 20px row + # First check + painter.drawText(int(preview_x), preview_y, int(single_w), 20, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, + "\u2713") + if receipt in ("delivered", "read"): + # Second check, overlapping + x2 = preview_x + single_w - overlap + painter.drawText(int(x2), preview_y, int(single_w), 20, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, + "\u2713") + total_w = single_w * 2 - overlap + 4 + else: + total_w = single_w + 4 + preview_x += total_w + preview_w -= total_w + preview_x = int(preview_x) + preview_w = int(preview_w) + painter.setPen(QColor(t.text_muted)) + elided_preview = fm2.elidedText(preview, Qt.TextElideMode.ElideRight, preview_w) + painter.drawText(preview_x, preview_y, preview_w, 20, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, + elided_preview) + + # -- Unread badge (bottom-right) -- + if unread > 0: + badge_x = rect.x() + rect.width() - self.HPAD - self.BADGE_SIZE + badge_y = preview_y + 1 + painter.setBrush(QBrush(QColor(t.accent))) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawRoundedRect(badge_x, badge_y, self.BADGE_SIZE, self.BADGE_SIZE, 10, 10) + painter.setPen(QColor(t.accent_text)) + badge_font = QFont() + badge_font.setPointSize(7) + badge_font.setBold(True) + painter.setFont(badge_font) + badge_text = str(unread) if unread < 100 else "99+" + painter.drawText( + QRect(badge_x, badge_y, self.BADGE_SIZE, self.BADGE_SIZE), + Qt.AlignmentFlag.AlignCenter, badge_text, + ) + + # -- Bottom separator line -- + painter.setPen(QColor(t.separator)) + painter.drawLine(text_x, rect.bottom(), rect.right() - self.HPAD, rect.bottom()) + + painter.restore() + + +class _ReceiptFooter(QWidget): + """Tiny widget that draws timestamp + receipt checkmarks with tight spacing.""" + + def __init__(self, time_str: str, status: str, + time_color: str, check_color: str, read_color: str, + parent=None): + super().__init__(parent) + self._time = time_str + self._status = status # "", "sent", "delivered", "read" + self._time_color = QColor(time_color) + self._check_color = QColor(check_color) + self._read_color = QColor(read_color) + self._font = QFont() + self._font.setPointSize(8) + fm = QFontMetricsF(self._font) + tw = fm.horizontalAdvance(self._time + " ") + cw = fm.horizontalAdvance("\u2713") + # 2nd check overlaps 1st by 40% of its width + overlap = cw * 0.4 + checks_w = 0.0 + if status == "sent": + checks_w = cw + elif status in ("delivered", "read"): + checks_w = cw * 2 - overlap + total_w = tw + checks_w + 2 + h = fm.height() + 2 + self.setFixedSize(int(total_w + 1), int(h + 1)) + + def paintEvent(self, event): + p = QPainter(self) + p.setRenderHint(QPainter.RenderHint.Antialiasing) + p.setFont(self._font) + fm = QFontMetricsF(self._font) + y_base = fm.ascent() + 1 + + # Draw time + p.setPen(self._time_color) + p.drawText(QPointF(0, y_base), self._time) + x = fm.horizontalAdvance(self._time) + 4 + + if not self._status: + p.end() + return + + cw = fm.horizontalAdvance("\u2713") + overlap = cw * 0.4 + color = self._read_color if self._status == "read" else self._check_color + + # First check + p.setPen(color) + p.drawText(QPointF(x, y_base), "\u2713") + + # Second check (tight overlap) + if self._status in ("delivered", "read"): + p.drawText(QPointF(x + cw - overlap, y_base), "\u2713") + + p.end() + + +class MessageInput(QTextEdit): + """Multiline message input: Enter sends, Shift+Enter inserts newline.""" + send_requested = pyqtSignal() + file_dropped = pyqtSignal(str) + + @staticmethod + def _style_normal(): + t = c() + return ( + f"QTextEdit {{ background-color: {t.bg_secondary}; border: 1px solid {t.border}; " + f"border-radius: 18px; padding: 8px 14px; color: {t.text_primary}; }}" + f"QTextEdit:focus {{ border: 1px solid {t.border_focus}; }}" + ) + + @staticmethod + def _style_drop(): + t = c() + return ( + f"QTextEdit {{ background-color: {t.bg_secondary}; border: 2px dashed {t.accent}; " + f"border-radius: 18px; padding: 8px 14px; color: {t.text_primary}; }}" + ) + + def __init__(self, parent=None): + super().__init__(parent) + self.setAcceptRichText(False) + self.setPlaceholderText("Type a message...") + self.setMinimumHeight(52) + self.setMaximumHeight(120) + self.setAcceptDrops(True) + self.drop_enabled = False + self.setStyleSheet(self._style_normal()) + self.textChanged.connect(self._auto_resize) + # Tight line spacing — set on document default cursor format + from PyQt6.QtGui import QTextBlockFormat + fmt = QTextBlockFormat() + fmt.setTopMargin(0) + fmt.setBottomMargin(0) + fmt.setLineHeight(0, 0) # 0 = SingleHeight + self._block_fmt = fmt + # Apply to default block format + cursor = self.textCursor() + cursor.setBlockFormat(fmt) + self.setTextCursor(cursor) + + def _auto_resize(self): + doc_height = int(self.document().size().height()) + 16 # padding + new_h = max(52, min(doc_height, 120)) + self.setFixedHeight(new_h) + + def keyPressEvent(self, event): + if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): + if event.modifiers() & Qt.KeyboardModifier.ShiftModifier: + # Insert plain newline instead of new paragraph + self.textCursor().insertText("\n") + return + else: + self.send_requested.emit() + return + super().keyPressEvent(event) + + def dragEnterEvent(self, event): + if not self.drop_enabled: + event.ignore() + return + if event.mimeData().hasUrls() and any(u.isLocalFile() for u in event.mimeData().urls()): + event.acceptProposedAction() + self.setStyleSheet(self._style_drop()) + else: + super().dragEnterEvent(event) + + def dragMoveEvent(self, event): + if event.mimeData().hasUrls(): + event.acceptProposedAction() + else: + super().dragMoveEvent(event) + + def dragLeaveEvent(self, event): + self.setStyleSheet(self._style_normal()) + super().dragLeaveEvent(event) + + def dropEvent(self, event): + self.setStyleSheet(self._style_normal()) + if event.mimeData().hasUrls(): + for url in event.mimeData().urls(): + if url.isLocalFile(): + self.file_dropped.emit(url.toLocalFile()) + event.acceptProposedAction() + else: + super().dropEvent(event) + + +class AsyncBridge(QThread): + """Runs asyncio event loop in a background thread, emits Qt signals.""" + connected = pyqtSignal() + connection_error = pyqtSignal(str) + login_result = pyqtSignal(bool, str) + register_result = pyqtSignal(bool, str) + conversations_loaded = pyqtSignal(list) + messages_loaded = pyqtSignal(str, list) # conv_id, messages + older_messages_loaded = pyqtSignal(str, list) # conv_id, older messages + message_sent = pyqtSignal(bool, str) + message_sent_payload = pyqtSignal(str, dict) # conv_id, message dict (for local append) + new_notification = pyqtSignal(dict) # decrypted payload + pairing_code = pyqtSignal(str) + pairing_complete = pyqtSignal(bool, str) + add_member_result = pyqtSignal(bool, str) + remove_member_result = pyqtSignal(bool, str) + authorize_result = pyqtSignal(bool, str) + rotate_result = pyqtSignal(bool, str) + reencrypt_status = pyqtSignal(str) + messages_read_notification = pyqtSignal(dict) + message_delivered_notification = pyqtSignal(dict) + message_deleted_notification = pyqtSignal(dict) + image_sent = pyqtSignal(bool, str) + image_downloaded = pyqtSignal(str, bytes) # file_id, decrypted bytes + delete_message_result = pyqtSignal(bool, str) + reconnected = pyqtSignal() + conversation_updated = pyqtSignal() + connection_state_changed = pyqtSignal(str) # "connected", "disconnected", "reconnecting" + profile_loaded = pyqtSignal(dict) + profile_updated = pyqtSignal(bool, str) + avatar_loaded = pyqtSignal(str, bytes) # user_id, avatar_bytes + online_status_changed = pyqtSignal(str, bool) # user_id, is_online + online_users_loaded = pyqtSignal(list) # list of user_ids + invitations_loaded = pyqtSignal(list) # list of invitation dicts + invitation_result = pyqtSignal(bool, str) # ok, message + invitation_received = pyqtSignal(dict) # invitation notification data + group_avatar_loaded = pyqtSignal(str, bytes) # conv_id, avatar_bytes + group_avatar_updated = pyqtSignal(bool, str) # ok, message + session_reset_notification = pyqtSignal(str, str) # from_user_id, from_device_id + reaction_result = pyqtSignal(bool, str) # ok, message + reaction_notification = pyqtSignal(dict) # {message_id, conversation_id, user_id, reaction, action} + pin_notification = pyqtSignal(dict) # {message_id, conversation_id, user_id, action=pin} + unpin_notification = pyqtSignal(dict) # {message_id, conversation_id, user_id, action=unpin} + pinned_messages_loaded = pyqtSignal(str, list) # conv_id, list of pinned msg dicts + forward_result = pyqtSignal(bool, str) # ok, message + key_change_warning = pyqtSignal(str, str, str, bool, bytes) # user_id, username, old_key_hex, was_verified, new_key_bytes + password_changed = pyqtSignal(bool, str) # ok, message + username_changed = pyqtSignal(bool, str) # ok, message + + def __init__(self): + super().__init__() + self.client = ChatClient() + self.loop: asyncio.AbstractEventLoop | None = None + self._running = True + self.client._reencrypt_progress_cb = self._emit_reencrypt_status + self.client._key_change_cb = self._emit_key_change_warning + self._ready: asyncio.Event | None = None + self._avatar_inflight: set[str] = set() + self._group_avatar_inflight: set[str] = set() + self._invitations_inflight = False + + def _emit_reencrypt_status(self, message: str): + self.reencrypt_status.emit(message) + + def _emit_key_change_warning(self, user_id: str, username: str, old_key_hex: str, was_verified: bool, new_key_bytes: bytes = b""): + self.key_change_warning.emit(user_id, username, old_key_hex, was_verified, new_key_bytes) + + def run(self): + if sys.platform == "win32": + self.loop = asyncio.SelectorEventLoop() + else: + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + self._ready = asyncio.Event() + try: + self.loop.run_until_complete(self._run()) + except Exception as e: + logger.error("AsyncBridge loop crashed: %s", e, exc_info=True) + finally: + self.loop.close() + + async def _run(self): + try: + await self.client.connect() + self.client._listener_task = asyncio.create_task(self.client._background_listener()) + if self._ready: + self._ready.set() + self.connected.emit() + self.connection_state_changed.emit("connected") + except Exception as e: + self.connection_error.emit(str(e)) + return + + # Process notifications + await self._notification_loop() + + async def _notification_loop(self): + while self._running: + try: + # Check if listener task died (connection lost) + if (self.client._listener_task and self.client._listener_task.done() + and not self.client.connected): + self.connection_state_changed.emit("disconnected") + if self.client.session: + await self._auto_reconnect() + continue + + notif = await asyncio.wait_for( + self.client._notification_queue.get(), timeout=0.5 + ) + notif_type = notif.get("type", "") + data = notif.get("data", {}) + if notif_type in ("conversation_created", "member_added", "member_removed", + "conversation_renamed"): + self.conversation_updated.emit() + elif notif_type == "group_invitation": + self.invitation_received.emit(data) + elif notif_type == "user_online": + self.online_status_changed.emit(data.get("user_id", ""), True) + elif notif_type == "user_offline": + self.online_status_changed.emit(data.get("user_id", ""), False) + elif notif_type == "online_users": + self.online_users_loaded.emit(data.get("user_ids", [])) + elif notif_type == "messages_read": + self.messages_read_notification.emit(data) + elif notif_type == "message_delivered": + self.message_delivered_notification.emit(data) + elif notif_type == "message_deleted": + self.message_deleted_notification.emit(data) + elif notif_type == "session_reset": + from_uid = data.get("from_user_id", "") + from_did = data.get("from_device_id", "") + self.client.handle_session_reset_notification(from_uid, from_did or None) + self.session_reset_notification.emit(from_uid, from_did) + elif notif_type == "username_changed": + self.conversation_updated.emit() + elif notif_type == "message_reacted": + self.reaction_notification.emit(data) + elif notif_type == "message_pinned": + self.pin_notification.emit(data) + elif notif_type == "message_unpinned": + self.unpin_notification.emit(data) + elif notif_type == "new_message": + try: + payload = self.client.decrypt_notification(data) + except IdentityKeyChanged as ikc: + cached = self.client._user_cache.get(ikc.user_id) + uname = cached.get("username", "") if cached else "" + old_hex = "" + known = self.client._known_identity_keys.get(ikc.user_id) + if known: + old_hex = known.get("identity_key", "") + was_verified = ikc.status == "changed_verified" + self.key_change_warning.emit(ikc.user_id, uname, old_hex, was_verified, ikc.new_key_bytes) + continue + if payload: + self.new_notification.emit(payload) + # None = control message (e.g. sender key distribution), skip silently + except asyncio.TimeoutError: + continue + except Exception as e: + logger.error("Notification loop exception: %s", e, exc_info=True) + break + + async def _auto_reconnect(self): + """Auto-reconnect with exponential backoff.""" + delay = 1 + while self._running and not self.client.connected: + self.connection_state_changed.emit("reconnecting") + try: + await self.client.reconnect() + if self.client.connected and self.client.session: + self.connection_state_changed.emit("connected") + self.conversation_updated.emit() + return + if self.client.login_rejected: + self.connection_state_changed.emit("revoked") + return + except Exception: + pass + await asyncio.sleep(delay) + delay = min(delay * 2, 30) + + def schedule(self, coro): + """Schedule a coroutine on the asyncio loop from the Qt thread.""" + if self.loop and self.loop.is_running(): + asyncio.run_coroutine_threadsafe(coro, self.loop) + else: + # Avoid "coroutine was never awaited" warnings if loop is down. + try: + coro.close() + except Exception: + pass + + async def _do_register(self, username, password, email): + if self._ready: + await self._ready.wait() + ok, code_or_msg = await self.client.register(username, password, email=email) + self.register_result.emit(ok, code_or_msg) + + async def _do_login(self, email, password): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.login(email, password) + self.login_result.emit(ok, msg) + + async def _do_logout(self): + if self._ready: + self._ready.clear() + try: + await self.client.close() + except Exception: + pass + self.client = ChatClient() + self.client._reencrypt_progress_cb = self._emit_reencrypt_status + try: + await self.client.connect() + self.client._listener_task = asyncio.create_task(self.client._background_listener()) + if self._ready: + self._ready.set() + self.reconnected.emit() + except Exception as e: + self.connection_error.emit(str(e)) + + async def _do_load_conversations(self): + if self._ready: + await self._ready.wait() + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + + async def _do_load_messages(self, conv_id): + if self._ready: + await self._ready.wait() + msgs = await self.client.get_messages(conv_id) + self.messages_loaded.emit(conv_id, msgs) + + async def _do_load_older_messages(self, conv_id, offset): + if self._ready: + await self._ready.wait() + msgs = await self.client.get_messages(conv_id, limit=50, offset=offset) + self.older_messages_loaded.emit(conv_id, msgs) + + async def _do_send_message(self, conv_id, text, members, reply_to=None): + if self._ready: + await self._ready.wait() + try: + ok, result = await self.client.send_message(conv_id, text, members, reply_to=reply_to) + except IdentityKeyChanged as ikc: + cached = self.client._user_cache.get(ikc.user_id) + uname = cached.get("username", "") if cached else "" + old_hex = "" + known = self.client._known_identity_keys.get(ikc.user_id) + if known: + old_hex = known.get("identity_key", "") + was_verified = ikc.status == "changed_verified" + self.key_change_warning.emit(ikc.user_id, uname, old_hex, was_verified, ikc.new_key_bytes) + self.message_sent.emit(False, "Identity key changed — accept new key first.") + return + except Exception as e: + logger.error("send_message exception: %s", e, exc_info=True) + self.message_sent.emit(False, str(e)) + return + if ok and isinstance(result, dict): + self.message_sent.emit(True, "Message sent.") + self.message_sent_payload.emit(conv_id, result) + else: + self.message_sent.emit(ok, result if isinstance(result, str) else "Message sent.") + + async def _do_find_or_create_and_send(self, username, text): + if self._ready: + await self._ready.wait() + try: + conv_id, msg = await self.client.find_or_create_conversation(username) + if not conv_id: + self.message_sent.emit(False, msg) + return + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + members = [] + for cv in convs: + if cv["conversation_id"] == conv_id: + members = cv["members"] + break + ok, result = await self.client.send_message(conv_id, text, members) + if ok and isinstance(result, dict): + self.message_sent.emit(True, "Message sent.") + self.message_sent_payload.emit(conv_id, result) + else: + self.message_sent.emit(ok, result if isinstance(result, str) else "Message sent.") + except IdentityKeyChanged as ikc: + cached = self.client._user_cache.get(ikc.user_id) + uname = cached.get("username", "") if cached else "" + old_hex = "" + known = self.client._known_identity_keys.get(ikc.user_id) + if known: + old_hex = known.get("identity_key", "") + was_verified = ikc.status == "changed_verified" + self.key_change_warning.emit(ikc.user_id, uname, old_hex, was_verified, ikc.new_key_bytes) + self.message_sent.emit(False, "Identity key changed — accept new key first.") + except Exception as e: + logger.error("find_or_create_and_send exception: %s", e, exc_info=True) + self.message_sent.emit(False, str(e)) + + async def _do_create_group(self, members, name=None): + if self._ready: + await self._ready.wait() + conv_id, msg = await self.client.create_conversation(members, name=name) + if conv_id: + self.message_sent.emit(True, f"Group created") + else: + self.message_sent.emit(False, msg) + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + + async def _do_link_device(self, username, password): + if self._ready: + await self._ready.wait() + ok, code_or_msg = await self.client.pairing_start(username) + if not ok: + self.pairing_complete.emit(False, code_or_msg) + return + code = code_or_msg + self.pairing_code.emit(code) + ok2, msg2 = await self.client.pairing_wait(code, username, password) + self.pairing_complete.emit(ok2, msg2) + + async def _do_authorize_device(self, code): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.authorize_device(code) + self.authorize_result.emit(ok, msg) + + async def _do_rotate_keys(self, username, password): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.rotate_keys(username, password) + self.rotate_result.emit(ok, msg) + + def do_register(self, username, password, email): + self.schedule(self._do_register(username, password, email)) + + def do_login(self, email, password): + self.schedule(self._do_login(email, password)) + + def load_conversations(self): + self.schedule(self._do_load_conversations()) + + def load_messages(self, conv_id): + self.schedule(self._do_load_messages(conv_id)) + + def load_older_messages(self, conv_id, offset): + self.schedule(self._do_load_older_messages(conv_id, offset)) + + def send_message(self, conv_id, text, members, reply_to=None): + self.schedule(self._do_send_message(conv_id, text, members, reply_to)) + + def send_new_chat(self, username, text): + self.schedule(self._do_find_or_create_and_send(username, text)) + + def create_group(self, members, name=None): + self.schedule(self._do_create_group(members, name=name)) + + async def _do_add_member(self, conv_id, email): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.add_member(conv_id, email) + self.add_member_result.emit(ok, msg) + if ok: + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + + def add_member(self, conv_id, email): + self.schedule(self._do_add_member(conv_id, email)) + + async def _do_remove_member(self, conv_id, user_id): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.remove_member(conv_id, user_id) + self.remove_member_result.emit(ok, msg) + if ok: + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + + def remove_member(self, conv_id, user_id): + self.schedule(self._do_remove_member(conv_id, user_id)) + + group_left = pyqtSignal(bool, str) + group_renamed = pyqtSignal(bool, str) + conversation_deleted = pyqtSignal(bool, str) + + async def _do_leave_group(self, conv_id): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.leave_group(conv_id) + self.group_left.emit(ok, msg) + if ok: + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + + def leave_group(self, conv_id): + self.schedule(self._do_leave_group(conv_id)) + + async def _do_rename_conversation(self, conv_id, name): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.rename_conversation(conv_id, name) + self.group_renamed.emit(ok, msg) + if ok: + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + + def rename_conversation(self, conv_id, name): + self.schedule(self._do_rename_conversation(conv_id, name)) + + async def _do_delete_conversation(self, conv_id): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.delete_conversation(conv_id) + self.conversation_deleted.emit(ok, msg) + if ok: + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + + def delete_conversation(self, conv_id): + self.schedule(self._do_delete_conversation(conv_id)) + + def link_device(self, username, password): + self.schedule(self._do_link_device(username, password)) + + def authorize_device(self, code): + self.schedule(self._do_authorize_device(code)) + + def rotate_keys(self, username, password): + self.schedule(self._do_rotate_keys(username, password)) + + async def _do_change_password(self, old_password, new_password): + if self._ready: + await self._ready.wait() + ok, msg = self.client.change_password(old_password, new_password) + self.password_changed.emit(ok, msg) + + def change_password(self, old_password, new_password): + self.schedule(self._do_change_password(old_password, new_password)) + + async def _do_change_username(self, new_username): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.change_username(new_username) + self.username_changed.emit(ok, msg) + + def change_username(self, new_username): + self.schedule(self._do_change_username(new_username)) + + async def _do_delete_message(self, message_id): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.delete_message(message_id) + self.delete_message_result.emit(ok, msg) + + def delete_message(self, message_id): + self.schedule(self._do_delete_message(message_id)) + + def reset_session(self, peer_user_id, peer_device_id=None): + self.schedule(self.client.reset_session(peer_user_id, peer_device_id)) + + async def _do_send_image(self, conv_id, image_path, members, reply_to=None): + if self._ready: + await self._ready.wait() + try: + ok, result = await self.client.send_image(conv_id, image_path, members, reply_to=reply_to) + except IdentityKeyChanged as ikc: + cached = self.client._user_cache.get(ikc.user_id) + uname = cached.get("username", "") if cached else "" + old_hex = "" + known = self.client._known_identity_keys.get(ikc.user_id) + if known: + old_hex = known.get("identity_key", "") + was_verified = ikc.status == "changed_verified" + self.key_change_warning.emit(ikc.user_id, uname, old_hex, was_verified, ikc.new_key_bytes) + self.image_sent.emit(False, "Identity key changed — accept new key first.") + return + except Exception as e: + logger.error("send_image exception: %s", e, exc_info=True) + self.image_sent.emit(False, str(e)) + return + if ok and isinstance(result, dict): + self.image_sent.emit(True, "Image sent.") + self.message_sent_payload.emit(conv_id, result) + else: + self.image_sent.emit(ok, result if isinstance(result, str) else "Image sent.") + + def send_image(self, conv_id, image_path, members, reply_to=None): + self.schedule(self._do_send_image(conv_id, image_path, members, reply_to)) + + async def _do_download_image(self, file_id, image_info): + if self._ready: + await self._ready.wait() + data = await self.client.download_image(file_id, image_info) + if data: + self.image_downloaded.emit(file_id, data) + + def download_image(self, file_id, image_info): + self.schedule(self._do_download_image(file_id, image_info)) + + file_sent = pyqtSignal(bool, str) + file_downloaded = pyqtSignal(bytes, dict) # decrypted_bytes, file_info + + async def _do_send_file(self, conv_id, file_path, members, reply_to=None): + if self._ready: + await self._ready.wait() + try: + ok, result = await self.client.send_file(conv_id, file_path, members, reply_to=reply_to) + except IdentityKeyChanged as ikc: + cached = self.client._user_cache.get(ikc.user_id) + uname = cached.get("username", "") if cached else "" + old_hex = "" + known = self.client._known_identity_keys.get(ikc.user_id) + if known: + old_hex = known.get("identity_key", "") + was_verified = ikc.status == "changed_verified" + self.key_change_warning.emit(ikc.user_id, uname, old_hex, was_verified, ikc.new_key_bytes) + self.file_sent.emit(False, "Identity key changed — accept new key first.") + return + except Exception as e: + logger.error("send_file exception: %s", e, exc_info=True) + self.file_sent.emit(False, str(e)) + return + if ok and isinstance(result, dict): + self.file_sent.emit(True, "File sent.") + self.message_sent_payload.emit(conv_id, result) + else: + self.file_sent.emit(ok, result if isinstance(result, str) else "File sent.") + + def send_file(self, conv_id, file_path, members, reply_to=None): + self.schedule(self._do_send_file(conv_id, file_path, members, reply_to)) + + async def _do_download_file(self, file_id, file_info): + if self._ready: + await self._ready.wait() + data = await self.client.download_file(file_id, file_info) + if data: + self.file_downloaded.emit(data, file_info) + + def download_file(self, file_id, file_info): + self.schedule(self._do_download_file(file_id, file_info)) + + async def _do_get_profile(self, user_id=None): + if self._ready: + await self._ready.wait() + profile = await self.client.get_profile(user_id) + if profile: + self.profile_loaded.emit(profile) + + def get_profile(self, user_id=None): + self.schedule(self._do_get_profile(user_id)) + + async def _do_update_profile(self, **fields): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.update_profile(**fields) + self.profile_updated.emit(ok, msg) + + def update_profile(self, **fields): + self.schedule(self._do_update_profile(**fields)) + + async def _do_update_avatar(self, image_data): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.update_avatar(image_data) + self.profile_updated.emit(ok, msg) + + def update_avatar(self, image_data): + self.schedule(self._do_update_avatar(image_data)) + + async def _do_get_avatar(self, user_id): + if self._ready: + await self._ready.wait() + if not user_id or user_id in self._avatar_inflight: + return + self._avatar_inflight.add(user_id) + try: + data = await self.client.get_avatar(user_id) + if data: + self.avatar_loaded.emit(user_id, data) + finally: + self._avatar_inflight.discard(user_id) + + def get_avatar(self, user_id): + self.schedule(self._do_get_avatar(user_id)) + + async def _do_list_invitations(self): + if self._ready: + await self._ready.wait() + if self._invitations_inflight: + return + self._invitations_inflight = True + try: + invitations = await self.client.list_invitations() + self.invitations_loaded.emit(invitations) + finally: + self._invitations_inflight = False + + def list_invitations(self): + self.schedule(self._do_list_invitations()) + + async def _do_accept_invitation(self, conv_id): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.accept_invitation(conv_id) + self.invitation_result.emit(ok, msg) + if ok: + invitations = await self.client.list_invitations() + self.invitations_loaded.emit(invitations) + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + + def accept_invitation(self, conv_id): + self.schedule(self._do_accept_invitation(conv_id)) + + async def _do_decline_invitation(self, conv_id): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.decline_invitation(conv_id) + self.invitation_result.emit(ok, msg) + if ok: + invitations = await self.client.list_invitations() + self.invitations_loaded.emit(invitations) + + def decline_invitation(self, conv_id): + self.schedule(self._do_decline_invitation(conv_id)) + + async def _do_update_group_avatar(self, conv_id, image_data): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.update_group_avatar(conv_id, image_data) + self.group_avatar_updated.emit(ok, msg) + if ok: + convs = await self.client.list_conversations() + self.conversations_loaded.emit(convs) + + def update_group_avatar(self, conv_id, image_data): + self.schedule(self._do_update_group_avatar(conv_id, image_data)) + + async def _do_get_group_avatar(self, conv_id): + if self._ready: + await self._ready.wait() + if not conv_id or conv_id in self._group_avatar_inflight: + return + self._group_avatar_inflight.add(conv_id) + try: + data = await self.client.get_group_avatar(conv_id) + if data: + self.group_avatar_loaded.emit(conv_id, data) + finally: + self._group_avatar_inflight.discard(conv_id) + + def get_group_avatar(self, conv_id): + self.schedule(self._do_get_group_avatar(conv_id)) + + # --- Reactions, Pins, Forwarding --- + + async def _do_react_message(self, message_id, reaction, action): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.react_message(message_id, reaction, action) + self.reaction_result.emit(ok, msg) + + def react_message(self, message_id, reaction, action="add"): + self.schedule(self._do_react_message(message_id, reaction, action)) + + async def _do_pin_message(self, message_id, conversation_id, action): + if self._ready: + await self._ready.wait() + ok, msg = await self.client.pin_message(message_id, conversation_id, action) + if not ok: + self.reaction_result.emit(False, msg) + + def pin_message(self, message_id, conversation_id, action="pin"): + self.schedule(self._do_pin_message(message_id, conversation_id, action)) + + async def _do_get_pinned_messages(self, conv_id): + if self._ready: + await self._ready.wait() + pinned = await self.client.get_pinned_messages(conv_id) + self.pinned_messages_loaded.emit(conv_id, pinned) + + def get_pinned_messages(self, conv_id): + self.schedule(self._do_get_pinned_messages(conv_id)) + + async def _do_forward_message(self, target_conv_id, original_msg, target_members): + if self._ready: + await self._ready.wait() + try: + ok, result = await self.client.forward_message(target_conv_id, original_msg, target_members) + except IdentityKeyChanged as ikc: + cached = self.client._user_cache.get(ikc.user_id) + uname = cached.get("username", "") if cached else "" + old_hex = "" + known = self.client._known_identity_keys.get(ikc.user_id) + if known: + old_hex = known.get("identity_key", "") + was_verified = ikc.status == "changed_verified" + self.key_change_warning.emit(ikc.user_id, uname, old_hex, was_verified, ikc.new_key_bytes) + self.forward_result.emit(False, "Identity key changed — accept new key first.") + return + except Exception as e: + logger.error("forward_message exception: %s", e, exc_info=True) + self.forward_result.emit(False, str(e)) + return + if ok and isinstance(result, dict): + self.forward_result.emit(True, "Message forwarded.") + self.message_sent_payload.emit(target_conv_id, result) + else: + self.forward_result.emit(ok, result if isinstance(result, str) else "Forwarded.") + + def forward_message(self, target_conv_id, original_msg, target_members): + self.schedule(self._do_forward_message(target_conv_id, original_msg, target_members)) + + def logout(self): + self.schedule(self._do_logout()) + + def stop(self): + self._running = False + if self.loop: + asyncio.run_coroutine_threadsafe(self.client.close(), self.loop) + + +def _make_frameless(dlg: QDialog, title_text: str = ""): + """Configure a QDialog as frameless with custom title bar, rounded container, + and drop shadow. Returns a QVBoxLayout for the dialog content area — + callers just add their widgets to the returned layout. + + Usage:: + + dlg = QDialog(self) + dlg.setMinimumWidth(380) + content_layout = _make_frameless(dlg, "My Title") + content_layout.addWidget(QLabel("Hello!")) + dlg.exec() + """ + dlg.setWindowFlags( + Qt.WindowType.FramelessWindowHint | Qt.WindowType.Dialog + ) + dlg.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + dlg._drag_pos = None + + t = c() + + # -- Outer layout (transparent, holds container with margins for shadow) -- + outer = QVBoxLayout(dlg) + outer.setContentsMargins(12, 12, 12, 12) + outer.setSpacing(0) + + # -- Rounded container -- + container = QWidget() + container.setObjectName("_framelessContainer") + container.setStyleSheet( + f"#_framelessContainer {{ background-color: {t.bg_primary}; border-radius: 12px; }}" + ) + shadow = QGraphicsDropShadowEffect(container) + shadow.setBlurRadius(24) + shadow.setOffset(0, 4) + shadow.setColor(QColor(0, 0, 0, 80)) + container.setGraphicsEffect(shadow) + + container_lay = QVBoxLayout(container) + container_lay.setContentsMargins(0, 0, 0, 0) + container_lay.setSpacing(0) + + # -- Title bar -- + title_bar = QWidget() + title_bar.setFixedHeight(40) + title_bar.setStyleSheet( + f"background-color: {t.bg_secondary}; " + f"border-top-left-radius: 12px; border-top-right-radius: 12px;" + ) + bar_layout = QHBoxLayout(title_bar) + bar_layout.setContentsMargins(16, 0, 8, 0) + bar_layout.setSpacing(0) + + title_label = QLabel(title_text) + title_label.setStyleSheet( + f"color: {t.text_primary}; font-size: 11pt; font-weight: bold; " + f"background: transparent;" + ) + bar_layout.addWidget(title_label) + bar_layout.addStretch() + + close_btn = QPushButton("\u2715") + close_btn.setFixedSize(28, 28) + close_btn.setCursor(Qt.CursorShape.PointingHandCursor) + close_btn.setStyleSheet( + f"QPushButton {{ background: transparent; color: {t.text_muted}; " + f"border: none; border-radius: 14px; font-size: 12pt; }}" + f"QPushButton:hover {{ background-color: {t.error}; color: {t.accent_text}; }}" + ) + close_btn.clicked.connect(dlg.reject) + bar_layout.addWidget(close_btn) + + # Dragging via title bar + def _mouse_press(event): + if event.button() == Qt.MouseButton.LeftButton: + dlg._drag_pos = event.globalPosition().toPoint() - dlg.frameGeometry().topLeft() + event.accept() + def _mouse_move(event): + if dlg._drag_pos is not None and event.buttons() & Qt.MouseButton.LeftButton: + dlg.move(event.globalPosition().toPoint() - dlg._drag_pos) + event.accept() + def _mouse_release(event): + dlg._drag_pos = None + title_bar.mousePressEvent = _mouse_press + title_bar.mouseMoveEvent = _mouse_move + title_bar.mouseReleaseEvent = _mouse_release + + container_lay.addWidget(title_bar) + + # -- Content widget -- + content = QWidget() + content_layout = QVBoxLayout(content) + content_layout.setContentsMargins(16, 12, 16, 16) + content_layout.setSpacing(8) + container_lay.addWidget(content) + + outer.addWidget(container) + + # Store refs for later theming + dlg._frameless_container = container + dlg._frameless_title_bar = title_bar + dlg._frameless_title_label = title_label + return content_layout + + +class UserProfileDialog(QDialog): + """Dialog for viewing/editing user profiles.""" + + def __init__(self, bridge: AsyncBridge, user_id: str, editable: bool = False, parent=None): + super().__init__(parent) + self.bridge = bridge + self.user_id = user_id + self.editable = editable + self.setMinimumWidth(400) + self._build_ui() + self._connect_signals() + self.bridge.get_profile(user_id) + + def _build_ui(self): + t = c() + title_text = "Edit Profile" if self.editable else "User Profile" + self.layout_main = _make_frameless(self, title_text) + self.layout_main.setSpacing(12) + + # Avatar + self.avatar_label = QLabel() + self.avatar_label.setFixedSize(80, 80) + self.avatar_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + t = c() + self.avatar_label.setStyleSheet( + f"background-color: {t.bg_secondary}; border-radius: 40px; " + f"font-size: 21pt; color: {t.accent};" + ) + self.avatar_label.setText("?") + self.layout_main.addWidget(self.avatar_label, alignment=Qt.AlignmentFlag.AlignCenter) + + if self.editable: + avatar_btn = QPushButton("Change Avatar") + avatar_btn.setObjectName("secondaryBtn") + avatar_btn.clicked.connect(self._on_change_avatar) + self.layout_main.addWidget(avatar_btn, alignment=Qt.AlignmentFlag.AlignCenter) + + # Info fields + self.username_label = QLabel("") + self.username_label.setStyleSheet(f"font-size: 14pt; font-weight: bold; color: {t.accent};") + self.username_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.layout_main.addWidget(self.username_label) + + self.info_area = QVBoxLayout() + self.layout_main.addLayout(self.info_area) + + # Editable fields (only shown in edit mode) + if self.editable: + self.layout_main.addSpacing(8) + + form_label = QLabel("Profile Settings") + form_label.setStyleSheet(f"font-weight: bold; color: {t.accent};") + self.layout_main.addWidget(form_label) + + self.phone_input = QLineEdit() + self.phone_input.setPlaceholderText("Phone number") + self.layout_main.addWidget(self.phone_input) + + self.location_input = QLineEdit() + self.location_input.setPlaceholderText("Location") + self.layout_main.addWidget(self.location_input) + + from PyQt6.QtWidgets import QCheckBox + self.email_visible_cb = QCheckBox("Email visible to others") + self.email_visible_cb.setStyleSheet(f"color: {t.text_primary};") + self.layout_main.addWidget(self.email_visible_cb) + + self.phone_visible_cb = QCheckBox("Phone visible to others") + self.phone_visible_cb.setStyleSheet(f"color: {t.text_primary};") + self.layout_main.addWidget(self.phone_visible_cb) + + self.location_visible_cb = QCheckBox("Location visible to others") + self.location_visible_cb.setStyleSheet(f"color: {t.text_primary};") + self.layout_main.addWidget(self.location_visible_cb) + + save_btn = QPushButton("Save") + save_btn.clicked.connect(self._on_save) + self.layout_main.addWidget(save_btn) + + # Security section (only when viewing another user, not own profile) + if not self.editable: + self._security_section = QVBoxLayout() + self.layout_main.addLayout(self._security_section) + + close_btn = QPushButton("Close") + close_btn.setObjectName("secondaryBtn") + close_btn.clicked.connect(self.accept) + self.layout_main.addWidget(close_btn) + + def _connect_signals(self): + self.bridge.profile_loaded.connect(self._on_profile_loaded) + self.bridge.avatar_loaded.connect(self._on_avatar_loaded) + self.bridge.profile_updated.connect(self._on_profile_updated) + + def _on_profile_loaded(self, profile): + if profile.get("user_id") != self.user_id: + return + username = profile.get("username", "?") + self.username_label.setText(username) + # Set avatar initial + self.avatar_label.setText(username[0].upper() if username else "?") + + # Clear info area + while self.info_area.count(): + item = self.info_area.takeAt(0) + if item.widget(): + item.widget().deleteLater() + + # Email + email = profile.get("email") + if email: + self.info_area.addWidget(QLabel(f"Email: {email}")) + + # Phone + phone = profile.get("phone") + if phone: + self.info_area.addWidget(QLabel(f"Phone: {phone}")) + + # Location + location = profile.get("location") + if location: + self.info_area.addWidget(QLabel(f"Location: {location}")) + + # Member since + created_at = profile.get("created_at", "") + if created_at: + date_str = created_at[:10] if len(created_at) >= 10 else created_at + label = QLabel(f"Member since: {date_str}") + label.setStyleSheet(f"color: {c().text_muted};") + self.info_area.addWidget(label) + + # Populate editable fields + if self.editable: + self.phone_input.setText(phone or "") + self.location_input.setText(location or "") + self.email_visible_cb.setChecked(bool(profile.get("email_visible", 1))) + self.phone_visible_cb.setChecked(bool(profile.get("phone_visible", 0))) + self.location_visible_cb.setChecked(bool(profile.get("location_visible", 0))) + + # Try to load avatar + if profile.get("avatar_file"): + self.bridge.get_avatar(self.user_id) + + # Security section (viewing another user, not self) + my_uid = self.bridge.client.session.get("user_id", "") if self.bridge.client.session else "" + if not self.editable and hasattr(self, "_security_section") and self.user_id != my_uid: + self._populate_security_section() + + def _populate_security_section(self): + """Populate security/verification info for a peer user.""" + t = c() + sec = self._security_section + # Clear previous contents + while sec.count(): + item = sec.takeAt(0) + if item.widget(): + item.widget().deleteLater() + + sec.addSpacing(8) + header = QLabel("Security") + header.setStyleSheet(f"font-weight: bold; color: {t.accent};") + sec.addWidget(header) + + status = self.bridge.client.get_verification_status(self.user_id) + if status == "verified": + status_label = QLabel("\u2705 Identity verified") + status_label.setStyleSheet(f"color: {t.success}; font-weight: bold;") + elif status == "trusted": + status_label = QLabel("\U0001f512 Trusted (first use)") + status_label.setStyleSheet(f"color: {t.warning};") + else: + status_label = QLabel("\u26A0 Unverified") + status_label.setStyleSheet(f"color: {t.text_muted};") + sec.addWidget(status_label) + + fp = self.bridge.client.get_peer_fingerprint(self.user_id) + if fp: + fp_label = QLabel(f"Fingerprint:\n{fp}") + fp_label.setStyleSheet( + f"font-family: monospace; font-size: 8pt; color: {t.text_primary}; " + f"background: {t.bg_secondary}; padding: 4px; border-radius: 4px;" + ) + fp_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + sec.addWidget(fp_label) + + def _on_avatar_loaded(self, user_id, data): + if user_id != self.user_id: + return + qimg = _safe_load_image(data) + if qimg is not None: + pixmap = QPixmap.fromImage(qimg) + # Circular crop + size = 80 + scaled = pixmap.scaled(size, size, Qt.AspectRatioMode.KeepAspectRatioByExpanding, + Qt.TransformationMode.SmoothTransformation) + result = QPixmap(size, size) + result.fill(QColor(0, 0, 0, 0)) + painter = QPainter(result) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + painter.setBrush(QBrush(scaled)) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawEllipse(0, 0, size, size) + painter.end() + self.avatar_label.setPixmap(result) + + def _on_change_avatar(self): + path, _ = QFileDialog.getOpenFileName( + self, "Select Avatar", "", + "Images (*.png *.jpg *.jpeg);;All Files (*)", + ) + if not path: + return + try: + with open(path, "rb") as f: + data = f.read() + if len(data) > 2 * 1024 * 1024: + QMessageBox.warning(self, "Error", "Avatar too large (max 2 MB).") + return + self.bridge.update_avatar(data) + except Exception as e: + QMessageBox.warning(self, "Error", f"Failed to read file: {e}") + + def _on_save(self): + fields = { + "phone": self.phone_input.text().strip() or None, + "location": self.location_input.text().strip() or None, + "email_visible": 1 if self.email_visible_cb.isChecked() else 0, + "phone_visible": 1 if self.phone_visible_cb.isChecked() else 0, + "location_visible": 1 if self.location_visible_cb.isChecked() else 0, + } + self.bridge.update_profile(**fields) + + def _on_profile_updated(self, ok, msg): + if ok: + # Refresh profile + self.bridge.get_profile(self.user_id) + else: + QMessageBox.warning(self, "Error", msg) + + def closeEvent(self, event): + # Disconnect signals to avoid stale references + try: + self.bridge.profile_loaded.disconnect(self._on_profile_loaded) + self.bridge.avatar_loaded.disconnect(self._on_avatar_loaded) + self.bridge.profile_updated.disconnect(self._on_profile_updated) + except Exception: + pass + super().closeEvent(event) + + def reject(self): + try: + self.bridge.profile_loaded.disconnect(self._on_profile_loaded) + self.bridge.avatar_loaded.disconnect(self._on_avatar_loaded) + self.bridge.profile_updated.disconnect(self._on_profile_updated) + except Exception: + pass + super().reject() + + def accept(self): + try: + self.bridge.profile_loaded.disconnect(self._on_profile_loaded) + self.bridge.avatar_loaded.disconnect(self._on_avatar_loaded) + self.bridge.profile_updated.disconnect(self._on_profile_updated) + except Exception: + pass + super().accept() + + +class VerificationDialog(QDialog): + """Dialog for viewing safety numbers, fingerprints, QR codes, and verifying contacts.""" + + def __init__(self, bridge: AsyncBridge, peer_user_id: str, peer_name: str, parent=None): + super().__init__(parent) + self.bridge = bridge + self.peer_user_id = peer_user_id + self.peer_name = peer_name + self.setMinimumWidth(420) + self._build_ui() + + def _build_ui(self): + t = c() + lay = _make_frameless(self, "Verify Contact") + lay.setSpacing(10) + + # Peer name + name_label = QLabel(self.peer_name) + name_label.setStyleSheet(f"font-size: 14pt; font-weight: bold; color: {t.accent};") + name_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + lay.addWidget(name_label) + + # Verification status + status = self.bridge.client.get_verification_status(self.peer_user_id) + if status == "verified": + status_text = "\u2705 Verified" + status_color = t.success + elif status == "trusted": + status_text = "\U0001f512 Trusted (TOFU)" + status_color = t.warning + else: + status_text = "\u26A0 Unverified" + status_color = t.error + self._status_label = QLabel(status_text) + self._status_label.setStyleSheet(f"font-size: 11pt; font-weight: bold; color: {status_color};") + self._status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + lay.addWidget(self._status_label) + + lay.addSpacing(4) + + # Safety Number + safety = self.bridge.client.get_safety_number(self.peer_user_id) + if safety: + sn_header = QLabel("Safety Number") + sn_header.setStyleSheet(f"font-weight: bold; color: {t.text_secondary};") + lay.addWidget(sn_header) + + sn_label = QLabel(safety) + sn_label.setStyleSheet( + f"font-family: monospace; font-size: 13pt; letter-spacing: 2px; " + f"color: {t.text_primary}; background: {t.bg_secondary}; " + f"padding: 12px; border-radius: 8px;" + ) + sn_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + sn_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + lay.addWidget(sn_label) + + lay.addSpacing(4) + + # QR Code + qr_data = self.bridge.client.get_verification_qr_data() + if qr_data: + qr_header = QLabel("Your QR Code (for peer to scan)") + qr_header.setStyleSheet(f"font-weight: bold; color: {t.text_secondary};") + lay.addWidget(qr_header) + + qr_pixmap = self._generate_qr_pixmap(qr_data) + if qr_pixmap: + qr_label = QLabel() + qr_label.setPixmap(qr_pixmap) + qr_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + lay.addWidget(qr_label) + + save_qr_btn = QPushButton("Save QR Code") + save_qr_btn.setObjectName("secondaryBtn") + save_qr_btn.clicked.connect(lambda: self._save_qr(qr_pixmap)) + lay.addWidget(save_qr_btn) + + lay.addSpacing(4) + + # Fingerprints + my_fp = self.bridge.client.get_my_fingerprint() + peer_fp = self.bridge.client.get_peer_fingerprint(self.peer_user_id) + + if my_fp or peer_fp: + fp_header = QLabel("Fingerprints") + fp_header.setStyleSheet(f"font-weight: bold; color: {t.text_secondary};") + lay.addWidget(fp_header) + + if my_fp: + my_fp_label = QLabel(f"Yours:\n{my_fp}") + my_fp_label.setStyleSheet( + f"font-family: monospace; font-size: 9pt; color: {t.text_primary}; " + f"background: {t.bg_secondary}; padding: 6px; border-radius: 4px;" + ) + my_fp_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + lay.addWidget(my_fp_label) + + if peer_fp: + peer_name_esc = self.peer_name.replace("&", "&").replace("<", "<") + peer_fp_label = QLabel(f"{peer_name_esc}:\n{peer_fp}") + peer_fp_label.setStyleSheet( + f"font-family: monospace; font-size: 9pt; color: {t.text_primary}; " + f"background: {t.bg_secondary}; padding: 6px; border-radius: 4px;" + ) + peer_fp_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + lay.addWidget(peer_fp_label) + + lay.addSpacing(8) + + # Action buttons + btn_row = QHBoxLayout() + + if status != "verified": + verify_btn = QPushButton("Mark as Verified") + verify_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.success}; color: {t.bg_primary}; " + f"font-weight: bold; padding: 8px 16px; border-radius: 6px; }}" + f"QPushButton:hover {{ opacity: 0.9; }}" + ) + verify_btn.clicked.connect(self._on_verify) + btn_row.addWidget(verify_btn) + else: + unverify_btn = QPushButton("Remove Verification") + unverify_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.warning}; color: {t.bg_primary}; " + f"font-weight: bold; padding: 8px 16px; border-radius: 6px; }}" + ) + unverify_btn.clicked.connect(self._on_unverify) + btn_row.addWidget(unverify_btn) + + # Scan QR button + scan_btn = QPushButton("Scan QR Code") + scan_btn.setObjectName("secondaryBtn") + scan_btn.clicked.connect(self._on_scan_qr) + btn_row.addWidget(scan_btn) + + lay.addLayout(btn_row) + + close_btn = QPushButton("Close") + close_btn.setObjectName("secondaryBtn") + close_btn.clicked.connect(self.accept) + lay.addWidget(close_btn) + + def _generate_qr_pixmap(self, data: bytes) -> QPixmap | None: + """Generate a QR code QPixmap from raw bytes (base64-encoded for scanner compat).""" + try: + import qrcode + import base64 + from io import BytesIO + # Encode as base64 — raw binary gets corrupted by QR readers (UTF-8 re-encoding) + qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=4, border=2) + qr.add_data(base64.b64encode(data).decode("ascii")) + qr.make(fit=True) + img = qr.make_image(fill_color="black", back_color="white") + buf = BytesIO() + img.save(buf, format="PNG") + buf.seek(0) + qimg = QImage() + qimg.loadFromData(buf.getvalue()) + if qimg.isNull(): + return None + return QPixmap.fromImage(qimg) + except ImportError: + return None + except Exception: + return None + + def _save_qr(self, pixmap: QPixmap): + """Save QR code image to file.""" + path, _ = QFileDialog.getSaveFileName( + self, "Save QR Code", "verification_qr.png", + "PNG Images (*.png);;All Files (*)", + ) + if path: + pixmap.save(path, "PNG") + + def _on_verify(self): + cached = self.bridge.client._user_cache.get(self.peer_user_id) + if cached and cached.get("identity_key_bytes"): + self.bridge.client.verify_contact( + self.peer_user_id, cached["identity_key_bytes"], method="safety_number" + ) + t = c() + self._status_label.setText("\u2705 Verified") + self._status_label.setStyleSheet(f"font-size: 11pt; font-weight: bold; color: {t.success};") + + def _on_unverify(self): + self.bridge.client.unverify_contact(self.peer_user_id) + t = c() + self._status_label.setText("\U0001f512 Trusted (TOFU)") + self._status_label.setStyleSheet(f"font-size: 11pt; font-weight: bold; color: {t.warning};") + + def _on_scan_qr(self): + """Open file picker for QR code image, decode and verify.""" + path, _ = QFileDialog.getOpenFileName( + self, "Select QR Code Image", "", + "Images (*.png *.jpg *.jpeg *.bmp);;All Files (*)", + ) + if not path: + return + try: + from PIL import Image + pil_img = Image.open(path) + except Exception as e: + QMessageBox.warning(self, "Error", f"Failed to open image: {e}") + return + # Try pyzbar first, fall back to manual decode + qr_text = None + try: + from pyzbar.pyzbar import decode as pyzbar_decode + results = pyzbar_decode(pil_img) + if results: + qr_text = results[0].data + except ImportError: + pass + if qr_text is None: + QMessageBox.information( + self, "QR Scan", + "Could not decode QR code. Install 'pyzbar' for QR scanning support, " + "or verify manually using the safety number above." + ) + return + # QR contains base64-encoded binary payload + import base64 + try: + qr_data = base64.b64decode(qr_text) + except Exception: + QMessageBox.warning(self, "Error", "Invalid QR code format.") + return + ok, user_id, message = self.bridge.client.verify_qr_code(qr_data) + if ok: + t = c() + self._status_label.setText("\u2705 Verified") + self._status_label.setStyleSheet(f"font-size: 11pt; font-weight: bold; color: {t.success};") + QMessageBox.information(self, "Verification", message) + else: + QMessageBox.warning(self, "Verification Failed", message) + + +class LoginWindow(QWidget): + def __init__(self, bridge: AsyncBridge): + super().__init__() + self.bridge = bridge + self.setWindowTitle("Encrypted Chat - Login") + self.setFixedSize(500, 540) + self._pair_email = "" + self._pair_password = "" + self._build_ui() + tm().on_change(self._apply_theme) + + def _login_card_qss(self): + t = c() + return ( + f"#loginCard {{ background-color: {t.bg_primary}; border-radius: 16px; }}" + f"#loginCard QWidget {{ background: transparent; }}" + f"#loginCard QLabel {{ background: transparent; color: {t.text_primary}; }}" + f"#loginCard QLineEdit {{" + f" background-color: {t.bg_secondary}; color: {t.text_primary};" + f" border: 1px solid {t.border}; border-radius: 6px; padding: 8px;" + f"}}" + f"#loginCard QLineEdit:focus {{ border: 1px solid {t.border_focus}; }}" + f"#loginCard QPushButton {{" + f" background-color: {t.accent}; color: {t.accent_text};" + f" border: none; border-radius: 6px; padding: 8px 16px; font-weight: bold;" + f"}}" + f"#loginCard QPushButton:hover {{ background-color: {t.accent_hover}; }}" + ) + + def _tab_bar_qss(self): + t = c() + return ( + f"QTabBar {{ background: transparent; border: none; }}" + f"QTabBar::tab {{ background: transparent; color: {t.text_muted}; " + f"padding: 10px 24px; font-size: 10pt; border: none; " + f"border-bottom: 2px solid transparent; }}" + f"QTabBar::tab:selected {{ color: {t.accent}; font-weight: bold; " + f"border-bottom: 2px solid {t.accent}; }}" + f"QTabBar::tab:hover {{ color: {t.text_primary}; }}" + f"QTabWidget::pane {{ border: none; background: transparent; }}" + ) + + def _apply_theme(self): + QApplication.instance().setStyleSheet(qss()) + t = c() + self.setStyleSheet(f"background-color: {t.bg_tertiary};") + self._card.setStyleSheet(self._login_card_qss()) + self._subtitle.setStyleSheet(f"color: {t.text_muted}; font-size: 9pt; margin-bottom: 8px;") + self._theme_btn.setText("\u2600" if tm().is_dark else "\U0001f319") + self._theme_btn.setStyleSheet( + f"QPushButton {{ background: transparent; color: {t.text_muted}; " + f"border: none; font-size: 14pt; }}" + f"QPushButton:hover {{ color: {t.accent}; }}" + ) + self._tabs.setStyleSheet(self._tab_bar_qss()) + # Verification page + self._step_label.setStyleSheet(f"color: {t.accent}; font-weight: bold; font-size: 10pt;") + self._info_label.setStyleSheet(f"color: {t.text_primary}; font-size: 10pt;") + self.code_input.setStyleSheet( + f"QLineEdit {{ font-size: 16pt; letter-spacing: 8px; text-align: center; " + f"background-color: {t.bg_secondary}; border: 1px solid {t.border}; " + f"border-radius: 6px; padding: 12px; color: {t.text_primary}; }}" + f"QLineEdit:focus {{ border: 1px solid {t.border_focus}; }}" + ) + + def _build_ui(self): + from PyQt6.QtWidgets import QTabWidget + outer = QVBoxLayout(self) + outer.setContentsMargins(0, 0, 0, 0) + t = c() + + # Background fills entire window + self.setStyleSheet(f"background-color: {t.bg_tertiary};") + + # Theme toggle in top-right corner + top_row = QHBoxLayout() + top_row.setContentsMargins(12, 8, 12, 0) + top_row.addStretch() + self._theme_btn = QPushButton("\u2600" if tm().is_dark else "\U0001f319") + self._theme_btn.setFixedSize(36, 36) + self._theme_btn.setToolTip("Toggle light/dark mode") + self._theme_btn.setStyleSheet( + f"QPushButton {{ background: transparent; color: {t.text_muted}; " + f"border: none; font-size: 14pt; }}" + f"QPushButton:hover {{ color: {t.accent}; }}" + ) + self._theme_btn.clicked.connect(tm().toggle) + top_row.addWidget(self._theme_btn) + outer.addLayout(top_row) + + self.stack = QStackedWidget() + outer.addWidget(self.stack) + + # --- Page 0: Login / Register form (card) --- + page0 = QWidget() + page0_layout = QVBoxLayout(page0) + page0_layout.setContentsMargins(40, 0, 40, 20) + page0_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self._card = QWidget() + self._card.setObjectName("loginCard") + self._card.setStyleSheet(self._login_card_qss()) + card_layout = QVBoxLayout(self._card) + card_layout.setSpacing(10) + card_layout.setContentsMargins(36, 28, 36, 28) + + title = QLabel("Encrypted Chat") + title.setObjectName("title") + title.setAlignment(Qt.AlignmentFlag.AlignCenter) + card_layout.addWidget(title) + + self._subtitle = QLabel("End-to-end encrypted messaging") + self._subtitle.setAlignment(Qt.AlignmentFlag.AlignCenter) + self._subtitle.setStyleSheet(f"color: {t.text_muted}; font-size: 9pt; margin-bottom: 8px;") + card_layout.addWidget(self._subtitle) + + # --- Tabs: Login | Register | Link Device --- + self._tabs = QTabWidget() + self._tabs.setMinimumHeight(220) + self._tabs.setStyleSheet(self._tab_bar_qss()) + + # == Login tab == + login_tab = QWidget() + login_lay = QVBoxLayout(login_tab) + login_lay.setSpacing(12) + login_lay.setContentsMargins(0, 12, 0, 4) + + self.email_input = QLineEdit() + self.email_input.setPlaceholderText("Email") + login_lay.addWidget(self.email_input) + + self.password_input = QLineEdit() + self.password_input.setPlaceholderText("Password") + self.password_input.setEchoMode(QLineEdit.EchoMode.Password) + self.password_input.returnPressed.connect(self._on_login) + login_lay.addWidget(self.password_input) + + login_lay.addStretch() + self.login_btn = QPushButton("Login") + self.login_btn.setMinimumHeight(40) + self.login_btn.clicked.connect(self._on_login) + login_lay.addWidget(self.login_btn) + + self._tabs.addTab(login_tab, "Login") + + # == Register tab == + reg_tab = QWidget() + reg_lay = QVBoxLayout(reg_tab) + reg_lay.setSpacing(12) + reg_lay.setContentsMargins(0, 12, 0, 4) + + self.username_input = QLineEdit() + self.username_input.setPlaceholderText("Username (display name)") + reg_lay.addWidget(self.username_input) + + self._reg_email_input = QLineEdit() + self._reg_email_input.setPlaceholderText("Email") + reg_lay.addWidget(self._reg_email_input) + + self._reg_password_input = QLineEdit() + self._reg_password_input.setPlaceholderText("Password") + self._reg_password_input.setEchoMode(QLineEdit.EchoMode.Password) + self._reg_password_input.returnPressed.connect(self._on_register) + reg_lay.addWidget(self._reg_password_input) + + reg_lay.addStretch() + self.register_btn = QPushButton("Register") + self.register_btn.setMinimumHeight(40) + self.register_btn.clicked.connect(self._on_register) + reg_lay.addWidget(self.register_btn) + + self._tabs.addTab(reg_tab, "Register") + + # == Link Device tab == + link_tab = QWidget() + link_lay = QVBoxLayout(link_tab) + link_lay.setSpacing(12) + link_lay.setContentsMargins(0, 12, 0, 4) + + self._link_email_input = QLineEdit() + self._link_email_input.setPlaceholderText("Email") + link_lay.addWidget(self._link_email_input) + + self._link_password_input = QLineEdit() + self._link_password_input.setPlaceholderText("Password") + self._link_password_input.setEchoMode(QLineEdit.EchoMode.Password) + self._link_password_input.returnPressed.connect(self._on_link_device) + link_lay.addWidget(self._link_password_input) + + link_lay.addStretch() + self.link_btn = QPushButton("Link Device") + self.link_btn.setMinimumHeight(40) + self.link_btn.clicked.connect(self._on_link_device) + link_lay.addWidget(self.link_btn) + + self._tabs.addTab(link_tab, "Link Device") + + card_layout.addWidget(self._tabs) + + self.status_label = QLabel("") + self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.status_label.setWordWrap(True) + card_layout.addWidget(self.status_label) + + page0_layout.addWidget(self._card) + self.stack.addWidget(page0) + + # --- Page 1: Verification code form --- + page1 = QWidget() + vl = QVBoxLayout(page1) + vl.setSpacing(14) + vl.setContentsMargins(50, 40, 50, 40) + + self._step_label = QLabel("Step 2 of 2") + self._step_label.setStyleSheet(f"color: {t.accent}; font-weight: bold; font-size: 10pt;") + self._step_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + vl.addWidget(self._step_label) + + self._info_label = QLabel("Enter the 6-digit verification code sent to your email") + self._info_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self._info_label.setWordWrap(True) + self._info_label.setStyleSheet(f"color: {t.text_primary}; font-size: 10pt;") + vl.addWidget(self._info_label) + + vl.addSpacing(12) + + self.code_input = QLineEdit() + self.code_input.setPlaceholderText("000000") + self.code_input.setMaxLength(6) + self.code_input.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.code_input.setStyleSheet( + f"QLineEdit {{ font-size: 16pt; letter-spacing: 8px; text-align: center; " + f"background-color: {t.bg_secondary}; border: 1px solid {t.border}; " + f"border-radius: 6px; padding: 12px; color: {t.text_primary}; }}" + f"QLineEdit:focus {{ border: 1px solid {t.border_focus}; }}" + ) + self.code_input.returnPressed.connect(self._on_confirm_code) + vl.addWidget(self.code_input) + + vl.addSpacing(8) + + self.code_status_label = QLabel("") + self.code_status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.code_status_label.setWordWrap(True) + vl.addWidget(self.code_status_label) + + code_btn_row = QHBoxLayout() + self.back_btn = QPushButton("Back") + self.back_btn.setObjectName("secondaryBtn") + self.back_btn.clicked.connect(self._on_back_to_login) + code_btn_row.addWidget(self.back_btn) + + self.confirm_btn = QPushButton("Confirm") + self.confirm_btn.clicked.connect(self._on_confirm_code) + code_btn_row.addWidget(self.confirm_btn) + vl.addLayout(code_btn_row) + + vl.addStretch() + self.stack.addWidget(page1) + + def show_verification_page(self, message=""): + """Switch to verification code page.""" + self.code_input.clear() + self.code_status_label.setText(message) + self.code_status_label.setStyleSheet(f"color: {c().success};") + self.stack.setCurrentIndex(1) + self.code_input.setFocus() + + def _on_confirm_code(self): + code = self.code_input.text().strip() + if not code: + self.code_status_label.setText("Please enter the code.") + self.code_status_label.setStyleSheet(f"color: {c().error};") + return + self.code_status_label.setText("Confirming...") + self.code_status_label.setStyleSheet(f"color: {c().success};") + self.confirm_btn.setEnabled(False) + self.back_btn.setEnabled(False) + # Callback set by main() to handle confirmation + if hasattr(self, '_confirm_callback'): + self._confirm_callback(code) + + def _on_back_to_login(self): + self.stack.setCurrentIndex(0) + self._set_enabled(True) + self.status_label.setText("") + self.status_label.setStyleSheet("") + + def _on_register(self): + username = self.username_input.text().strip() + password = self._reg_password_input.text() + email = self._reg_email_input.text().strip() + if not username: + self.show_error("Username required.") + return + if not email or not password: + self.show_error("Email and password required.") + return + self.status_label.setText("Registering...") + self._set_enabled(False) + self.bridge.do_register(username, password, email) + + def _on_login(self): + email = self.email_input.text().strip() + password = self.password_input.text() + if not email or not password: + self.show_error("Email and password required.") + return + self.status_label.setText("Logging in...") + self._set_enabled(False) + self.bridge.do_login(email, password) + + def _on_link_device(self): + email = self._link_email_input.text().strip() + password = self._link_password_input.text() + if not email or not password: + self.show_error("Email and password required.") + return + self._pair_email = email + self._pair_password = password + self.status_label.setText("Generating pairing code...") + self._set_enabled(False) + self.bridge.link_device(email, password) + + def _set_enabled(self, enabled): + self._tabs.setEnabled(enabled) + self.username_input.setEnabled(enabled) + self.email_input.setEnabled(enabled) + self.password_input.setEnabled(enabled) + self._reg_email_input.setEnabled(enabled) + self._reg_password_input.setEnabled(enabled) + self._link_email_input.setEnabled(enabled) + self._link_password_input.setEnabled(enabled) + self.register_btn.setEnabled(enabled) + self.login_btn.setEnabled(enabled) + self.link_btn.setEnabled(enabled) + + def show_error(self, msg): + if self.stack.currentIndex() == 1: + self.code_status_label.setText(msg) + self.code_status_label.setStyleSheet(f"color: {c().error};") + self.confirm_btn.setEnabled(True) + self.back_btn.setEnabled(True) + else: + self.status_label.setText(msg) + self.status_label.setStyleSheet(f"color: {c().error};") + self._set_enabled(True) + + def show_success(self, msg): + if self.stack.currentIndex() == 1: + self.code_status_label.setText(msg) + self.code_status_label.setStyleSheet(f"color: {c().success};") + else: + self.status_label.setText(msg) + self.status_label.setStyleSheet(f"color: {c().success};") + + def reset(self): + self.stack.setCurrentIndex(0) + self._tabs.setCurrentIndex(0) + self.status_label.setText("") + self.status_label.setStyleSheet("") + self.code_status_label.setText("") + self.code_status_label.setStyleSheet("") + self.username_input.clear() + self.email_input.clear() + self.password_input.clear() + self._reg_email_input.clear() + self._reg_password_input.clear() + self._link_email_input.clear() + self._link_password_input.clear() + self.code_input.clear() + self._set_enabled(True) + self.confirm_btn.setEnabled(True) + self.back_btn.setEnabled(True) + + +class MessageBubble(QFrame): + """Chat message bubble with rounded corners drawn via QPainter.""" + + def __init__(self, bg_color: str, parent=None): + super().__init__(parent) + self._bg_color = QColor(bg_color) + self.setStyleSheet("background: transparent; border: none;") + self.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu) + + def set_bg_color(self, color: str): + self._bg_color = QColor(color) + self.update() + + def paintEvent(self, event): + p = QPainter(self) + p.setRenderHint(QPainter.RenderHint.Antialiasing) + p.setBrush(QBrush(self._bg_color)) + p.setPen(Qt.PenStyle.NoPen) + p.drawRoundedRect(self.rect(), 14, 14) + p.end() + + def contextMenuEvent(self, event): + # Walk up to find _msg_index, then call main window handler + idx = getattr(self, '_msg_index', None) + if idx is None: + p = self.parentWidget() + while p: + idx = getattr(p, '_msg_index', None) + if idx is not None: + break + p = p.parentWidget() + main_win = self.window() + if idx is not None and hasattr(main_win, '_show_msg_context_menu'): + main_win._show_msg_context_menu(idx, event.globalPos()) + event.accept() + + +class MainWindow(QWidget): + _AVATAR_REFRESH_BATCH = 8 + _GROUP_AVATAR_REFRESH_BATCH = 4 + _show_verification_dialog_signal = pyqtSignal(str, str) # peer_uid, peer_name + + def __init__(self, bridge: AsyncBridge, on_logout): + super().__init__() + self.bridge = bridge + self._on_logout_cb = on_logout + self.setWindowTitle(f"Encrypted Chat - {bridge.client.username}") + self.resize(900, 600) + + self.conversations: list[dict] = [] + self.current_conv_id: str | None = None + self.current_messages: list[dict] = [] + self.reply_to_id: str | None = None + self._unread_counts: dict[str, int] = {} + self._has_more_messages: bool = True + self._pending_image_download: dict | None = None # {file_id, image_info} + self._is_dm: bool = False + self._online_users: set[str] = set() + self._is_logout = False + self._avatar_cache = _LRUPixmapCache() # user_id -> pixmap + self._group_avatar_cache = _LRUPixmapCache() # conv_id -> pixmap + self._avatar_requested: set[str] = set() + self._group_avatar_requested: set[str] = set() + self._avatar_refresh_cursor = 0 + self._group_avatar_refresh_cursor = 0 + self._pending_invitations: list[dict] = [] + self._favorites: set[str] = self._load_favorites() + # Search state + self._search_results: list[int] = [] # indices into current_messages + self._search_current: int = -1 + self._search_query: str = "" + self._search_active: bool = False + + self._privacy_enabled: bool = True # Privacy overlay on/off + self._last_message_cache: dict[str, tuple[str, str, str]] = {} # conv_id -> (text, ts, receipt) + + self._build_ui() + self._connect_signals() + self._setup_tray_icon() + self._setup_privacy_overlay() + + # Keyboard shortcuts + QShortcut(QKeySequence("Ctrl+F"), self).activated.connect(self._toggle_search) + QShortcut(QKeySequence("Ctrl+Shift+P"), self).activated.connect(self._toggle_privacy) + + self.bridge.load_conversations() + self.bridge.list_invitations() + + # Periodic refresh: re-download avatars and conversation data + self._refresh_timer = QTimer(self) + self._refresh_timer.timeout.connect(self._on_periodic_refresh) + self._refresh_timer.start(120_000) # every 2 minutes + + tm().on_change(self._apply_theme) + + # -- Theme switching ------------------------------------------------------- + + def _apply_theme(self): + """Re-apply theme colours to all widgets after theme toggle.""" + app = QApplication.instance() + if app: + app.setStyleSheet(qss()) + t = c() + # Sidebar + self._sidebar_panel.setStyleSheet(f"#sidebarPanel {{ background-color: {t.bg_tertiary}; }}") + self._conv_label.setStyleSheet(f"font-weight: bold; font-size: 12pt; color: {t.accent}; background: transparent;") + self._settings_btn.setStyleSheet( + f"QPushButton {{ background: transparent; color: {t.text_secondary}; border: none; border-radius: 6px; padding: 8px 16px; }}" + f"QPushButton:hover {{ background-color: {t.bg_hover}; }}" + ) + self.conv_list.setStyleSheet( + f"QListWidget {{ background-color: {t.bg_tertiary}; border: none; padding: 0px; }}" + f"QListWidget::item {{ padding: 0px; border: none; }}" + f"QListWidget::item:selected {{ background: transparent; border: none; }}" + f"QListWidget::item:hover {{ background: transparent; }}" + ) + # Invitation list + self.inv_label.setStyleSheet(f"font-weight: bold; font-size: 9pt; color: {t.warning}; margin-top: 4px;") + self.inv_list.setStyleSheet( + f"QListWidget {{ background-color: {t.bg_primary}; border: 1px solid {t.warning}; border-radius: 6px; padding: 2px; }}" + f"QListWidget::item {{ padding: 6px; color: {t.text_primary}; }}" + f"QListWidget::item:hover {{ background-color: {t.bg_hover}; color: {t.text_primary}; }}" + ) + # Chat header + self._chat_header_widget.setStyleSheet( + f"#chatHeader {{ border-bottom: 1px solid {t.separator}; }}" + f"#chatHeader QLabel, #chatHeader QPushButton {{ border: none; }}" + ) + self.chat_header.setStyleSheet(f"font-weight: bold; font-size: 12pt; color: {t.accent};") + self._chat_header_status.setStyleSheet(f"color: {t.text_muted}; font-size: 8pt;") + self._e2e_label.setStyleSheet(f"font-size: 8pt; color: {t.text_muted}; background: transparent;") + self.connection_dot.setStyleSheet(f"color: {t.success}; font-size: 11pt;") + self._logout_btn.setStyleSheet( + f"QPushButton {{ background: transparent; color: {t.error}; border: none; " + f"border-radius: 16px; font-size: 13pt; font-weight: bold; }}" + f"QPushButton:hover {{ background-color: {t.error}; color: {t.accent_text}; }}" + ) + self.delete_conv_btn.setStyleSheet( + f"QPushButton {{ background: transparent; border: none; border-radius: 4px; padding: 4px; }}" + f"QPushButton:hover {{ background-color: {t.error}; }}" + ) + # Search bar + self.search_input.setStyleSheet( + f"QLineEdit {{ background-color: {t.bg_secondary}; color: {t.text_primary}; " + f"border: 1px solid {t.border}; border-radius: 4px; padding: 4px 8px; font-size: 10pt; }}" + ) + self.search_count_label.setStyleSheet(f"color: {t.text_muted}; font-size: 9pt; min-width: 40px;") + # Pin banner + self._pin_banner.setStyleSheet(f"background-color:{t.border}; border-bottom:2px solid {t.pin_color};") + self._pin_banner_label.setStyleSheet(f"color:{t.text_primary}; font-size:9pt; background:transparent; border:none;") + # Jump button + self.jump_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.accent}; color: {t.accent_text}; border-radius: 18px; " + f"font-size: 14pt; font-weight: bold; }}" + f"QPushButton:hover {{ background-color: {t.accent_hover}; }}" + ) + # Reply label + self.reply_label.setStyleSheet( + f"color: {t.accent}; font-style: italic; font-size: 9pt; " + f"padding: 2px 4px; background: transparent;" + ) + # Input area + self._attach_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.bg_secondary}; border: none; " + f"border-radius: 20px; font-size: 14pt; }}" + f"QPushButton:hover {{ background-color: {t.bg_hover}; }}" + ) + self._send_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.accent}; color: {t.accent_text}; " + f"border: none; border-radius: 20px; font-size: 14pt; font-weight: bold; }}" + f"QPushButton:hover {{ background-color: {t.accent_hover}; }}" + ) + self.msg_input.setStyleSheet(self.msg_input._style_normal()) + # Counters + self.char_counter.setStyleSheet(f"color: {t.text_muted}; font-size: 8pt; padding: 0 4px;") + self.reencrypt_label.setStyleSheet( + f"background-color: {t.bg_secondary}; border-radius: 6px; " + f"padding: 8px 12px; color: {t.success}; font-weight: bold;" + ) + # Status bar + self.status_bar.setStyleSheet( + f"background-color: {t.bg_tertiary}; border-radius: 0px; " + f"padding: 0 8px; color: {t.success}; font-size: 8pt;" + ) + # Privacy overlay + if hasattr(self, "_privacy_overlay"): + self._privacy_overlay.setStyleSheet(f"background-color: {t.overlay};") + self._lock_hint.setStyleSheet(f"font-size: 12pt; color: {t.text_muted}; background: transparent;") + self._lock_input.setStyleSheet( + f"QLineEdit {{ font-size: 11pt; background-color: {t.bg_secondary}; " + f"border: 1px solid {t.border}; border-radius: 6px; padding: 8px; " + f"color: {t.text_primary}; }}" + f"QLineEdit:focus {{ border: 1px solid {t.border_focus}; }}" + ) + self._lock_error.setStyleSheet(f"font-size: 9pt; color: {t.error}; background: transparent;") + # Mention popup + if hasattr(self, "_mention_popup"): + self._mention_popup.setStyleSheet( + f"QListWidget {{ background:{t.bg_secondary}; color:{t.text_primary}; border:1px solid {t.border}; " + f"border-radius:4px; font-size:10pt; }}" + f"QListWidget::item {{ padding:4px 8px; }}" + f"QListWidget::item:selected {{ background:{t.border}; }}" + ) + # Message scroll area + if hasattr(self, '_msg_scroll_area'): + self._msg_scroll_area.setStyleSheet( + f"QScrollArea {{ background-color: {t.bg_primary}; border: none; }}" + ) + self._msg_container.setStyleSheet( + f"background-color: {t.bg_primary};" + ) + # Re-render messages and conversation list + self._rebuild_conv_list() + if self.current_messages: + self._render_messages(scroll_to_bottom=False) + + # -- Privacy Overlay (lock screen) ---------------------------------------- + + _LOCK_TIMEOUT_MS = 30_000 # 30 s unfocused → lock (require password) + + def _setup_privacy_overlay(self): + """Create overlay that hides content on focus loss; locks after timeout.""" + self._privacy_locked = False + # Check if identity key is password-encrypted (ECP1 format) + # If not, lock feature is disabled (no password to verify against) + self._lock_capable = False + try: + from chat_core import get_key_dir + key_path = get_key_dir(self.bridge.client.email) / "identity_private.bin" + if key_path.exists(): + self._lock_capable = key_path.read_bytes()[:4] == b"ECP1" + except Exception: + pass + + t = c() + # -- overlay widget -- + self._privacy_overlay = QWidget(self) + self._privacy_overlay.setStyleSheet( + f"background-color: {t.overlay};" + ) + self._privacy_overlay.hide() + + overlay_layout = QVBoxLayout(self._privacy_overlay) + overlay_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + + lock_icon = QLabel("\U0001f512") + lock_icon.setStyleSheet("font-size: 36pt; background: transparent;") + lock_icon.setAlignment(Qt.AlignmentFlag.AlignCenter) + overlay_layout.addWidget(lock_icon) + + self._lock_hint = QLabel("Encrypted Chat") + self._lock_hint.setStyleSheet( + f"font-size: 12pt; color: {t.text_muted}; background: transparent;" + ) + self._lock_hint.setAlignment(Qt.AlignmentFlag.AlignCenter) + overlay_layout.addWidget(self._lock_hint) + + # Password input (hidden until locked) + self._lock_input = QLineEdit() + self._lock_input.setPlaceholderText("Enter password to unlock") + self._lock_input.setEchoMode(QLineEdit.EchoMode.Password) + self._lock_input.setMaximumWidth(280) + self._lock_input.setStyleSheet( + f"QLineEdit {{ font-size: 11pt; background-color: {t.bg_secondary}; " + f"border: 1px solid {t.border}; border-radius: 6px; padding: 8px; " + f"color: {t.text_primary}; }}" + f"QLineEdit:focus {{ border: 1px solid {t.border_focus}; }}" + ) + self._lock_input.returnPressed.connect(self._on_unlock_attempt) + self._lock_input.hide() + overlay_layout.addWidget(self._lock_input, alignment=Qt.AlignmentFlag.AlignCenter) + + self._lock_error = QLabel("") + self._lock_error.setStyleSheet( + f"font-size: 9pt; color: {t.error}; background: transparent;" + ) + self._lock_error.setAlignment(Qt.AlignmentFlag.AlignCenter) + self._lock_error.hide() + overlay_layout.addWidget(self._lock_error) + + # Timer: after N seconds unfocused → require password + self._lock_timer = QTimer(self) + self._lock_timer.setSingleShot(True) + self._lock_timer.timeout.connect(self._on_lock_timeout) + + def _toggle_privacy(self): + """Toggle privacy overlay on/off (Ctrl+Shift+P).""" + self._privacy_enabled = not self._privacy_enabled + if not self._privacy_enabled: + self._privacy_locked = False + self._lock_timer.stop() + self._hide_privacy_overlay() + state = "ON" if self._privacy_enabled else "OFF" + base_title = f"Encrypted Chat - {self.bridge.client.username}" + self.setWindowTitle(f"{base_title} [Privacy: {state}]") + QTimer.singleShot(2000, lambda: self.setWindowTitle(base_title)) + + def _show_privacy_overlay(self): + if not self._privacy_enabled: + return + if not self._privacy_overlay.isVisible(): + self._privacy_overlay.setGeometry(self.rect()) + self._privacy_overlay.raise_() + self._privacy_overlay.show() + # Start lock countdown + self._lock_timer.start(self._LOCK_TIMEOUT_MS) + + def _hide_privacy_overlay(self): + self._lock_timer.stop() + self._lock_input.hide() + self._lock_input.clear() + self._lock_error.hide() + self._lock_hint.setText("Encrypted Chat") + if self._privacy_overlay.isVisible(): + self._privacy_overlay.hide() + + def _on_lock_timeout(self): + """Window unfocused too long — require password.""" + if self._privacy_overlay.isVisible() and self._lock_capable: + self._privacy_locked = True + self._lock_hint.setText("Session locked") + self._lock_input.show() + self._lock_input.setFocus() + + def _on_unlock_attempt(self): + """Verify password by decrypting identity key from disk.""" + from chat_core import get_key_dir, _check_lockout, _record_failed_attempt, _clear_lockout + from crypto_utils import _decrypt_private_key + pwd = self._lock_input.text() + if not pwd: + return + email = self.bridge.client.email + remaining = _check_lockout(email) + if remaining > 0: + self._lock_error.setText(f"Too many attempts. Wait {remaining:.0f}s.") + self._lock_error.show() + self._lock_input.clear() + self._lock_input.setFocus() + return + try: + key_path = get_key_dir(email) / "identity_private.bin" + data = key_path.read_bytes() + _decrypt_private_key(data, pwd.encode("utf-8")) + # Success — unlock + _clear_lockout(email) + self._privacy_locked = False + self._hide_privacy_overlay() + except Exception: + _record_failed_attempt(email) + remaining = _check_lockout(email) + if remaining > 0: + self._lock_error.setText(f"Wrong password. Wait {remaining:.0f}s.") + else: + self._lock_error.setText("Wrong password") + self._lock_error.show() + self._lock_input.clear() + self._lock_input.setFocus() + + def changeEvent(self, event): + """Handle window state changes — tray minimize + privacy overlay.""" + from PyQt6.QtCore import QEvent + if event.type() == QEvent.Type.WindowStateChange: + if self.isMinimized() and self._tray_icon is not None: + event.ignore() + self.hide() + return + if event.type() == QEvent.Type.ActivationChange: + if self.isActiveWindow(): + if not self._privacy_locked: + self._hide_privacy_overlay() + else: + # Locked — keep overlay, focus password input + self._lock_input.setFocus() + else: + self._show_privacy_overlay() + super().changeEvent(event) + + def resizeEvent(self, event): + """Keep privacy overlay sized to window.""" + super().resizeEvent(event) + if hasattr(self, "_privacy_overlay"): + self._privacy_overlay.setGeometry(self.rect()) + + # -- System Tray ---------------------------------------------------------- + + def _make_tray_icon(self) -> QIcon: + """Create a simple app icon (blue chat bubble) for the system tray.""" + px = QPixmap(64, 64) + px.fill(QColor(0, 0, 0, 0)) + p = QPainter(px) + p.setRenderHint(QPainter.RenderHint.Antialiasing) + p.setBrush(QBrush(QColor(c().accent))) + p.setPen(Qt.PenStyle.NoPen) + p.drawRoundedRect(4, 4, 56, 48, 12, 12) + # small triangle (tail) + from PyQt6.QtGui import QPolygon + from PyQt6.QtCore import QPoint + p.drawPolygon(QPolygon([QPoint(14, 52), QPoint(24, 44), QPoint(30, 52)])) + # lock icon (E2E indicator) + p.setBrush(QBrush(QColor(c().accent_text))) + p.drawRoundedRect(24, 16, 16, 14, 3, 3) + p.setPen(QPen(QColor(c().accent_text), 3)) + p.setBrush(Qt.BrushStyle.NoBrush) + p.drawArc(27, 10, 10, 12, 0, 180 * 16) + p.end() + return QIcon(px) + + def _setup_tray_icon(self): + """Initialize the system tray icon with context menu.""" + if not QSystemTrayIcon.isSystemTrayAvailable(): + self._tray_icon = None + return + self._tray_icon = QSystemTrayIcon(self._make_tray_icon(), self) + self._tray_icon.setToolTip(f"Encrypted Chat - {self.bridge.client.username}") + self._tray_icon.activated.connect(self._on_tray_activated) + + tray_menu = QMenu() + show_action = tray_menu.addAction("Show") + show_action.triggered.connect(self._restore_from_tray) + tray_menu.addSeparator() + quit_action = tray_menu.addAction("Quit") + quit_action.triggered.connect(self._quit_from_tray) + self._tray_icon.setContextMenu(tray_menu) + self._tray_icon.show() + + def _on_tray_activated(self, reason): + """Handle tray icon click — restore window on double-click or single click.""" + if reason in (QSystemTrayIcon.ActivationReason.Trigger, + QSystemTrayIcon.ActivationReason.DoubleClick): + self._restore_from_tray() + + def _restore_from_tray(self): + """Restore window from system tray.""" + self.showNormal() + self.activateWindow() + self.raise_() + + def _quit_from_tray(self): + """Quit the application from tray menu.""" + if self._tray_icon: + self._tray_icon.hide() + self._is_logout = False + self.close() + + def _show_tray_notification(self, title: str, text: str): + """Show a system tray toast notification if the window is not in the foreground.""" + if not self._tray_icon: + logger.debug("Tray notification skipped: no tray icon") + return + if self.isVisible() and self.isActiveWindow() and not self._privacy_locked: + return # user is looking at the app (and it's not locked) + if len(text) > 120: + text = text[:117] + "..." + logger.info("Tray notification: %s — %s", title, text[:50]) + self._tray_icon.showMessage( + title, text, + QSystemTrayIcon.MessageIcon.Information, 4000, + ) + + # -- End Tray --------------------------------------------------------------- + + def _bold_font(self) -> QFont: + """Return a bold font with a valid size (avoids QFont pointSize=-1 warnings).""" + f = QFont(self.conv_list.font()) + f.setBold(True) + # Stylesheet sets font-size in px so pointSize is -1; fix by using pixelSize + if f.pointSize() <= 0: + px = f.pixelSize() + if px > 0: + f.setPixelSize(px) + else: + f.setPointSize(10) + return f + + def _make_circular_avatar(self, pixmap: QPixmap, size: int = 32) -> QPixmap: + """Crop a pixmap into a circle.""" + scaled = pixmap.scaled(size, size, Qt.AspectRatioMode.KeepAspectRatioByExpanding, + Qt.TransformationMode.SmoothTransformation) + result = QPixmap(size, size) + result.fill(QColor(0, 0, 0, 0)) + painter = QPainter(result) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + painter.setBrush(QBrush(scaled)) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawEllipse(0, 0, size, size) + painter.end() + return result + + def _make_default_avatar(self, username: str, size: int = 32) -> QPixmap: + """Generate a colored circle with the first letter of the username.""" + # Deterministic color from username — higher saturation in light mode + hue = (hash(username) % 360) + sat = 160 if not tm().is_dark else 120 + val = 180 if not tm().is_dark else 200 + color = QColor.fromHsv(hue, sat, val) + result = QPixmap(size, size) + result.fill(QColor(0, 0, 0, 0)) + painter = QPainter(result) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + painter.setBrush(QBrush(color)) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawEllipse(0, 0, size, size) + # Draw letter + painter.setPen(QColor(255, 255, 255)) + font = QFont("Segoe UI Variable", int(size * 0.4)) + if not font.exactMatch(): + font = QFont("Segoe UI", int(size * 0.4)) + font.setBold(True) + painter.setFont(font) + letter = username[0].upper() if username else "?" + painter.drawText(0, 0, size, size, Qt.AlignmentFlag.AlignCenter, letter) + painter.end() + return result + + def _add_online_dot(self, avatar: QPixmap) -> QPixmap: + """Overlay a green dot on the bottom-right of an avatar pixmap.""" + result = QPixmap(avatar) + painter = QPainter(result) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + dot_size = max(8, avatar.width() // 4) + x = avatar.width() - dot_size + y = avatar.height() - dot_size + # Border ring (matches sidebar background) + painter.setBrush(QBrush(QColor(c().online_dot_border))) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawEllipse(x - 1, y - 1, dot_size + 2, dot_size + 2) + # Green dot + painter.setBrush(QBrush(QColor(c().online_dot))) + painter.drawEllipse(x, y, dot_size, dot_size) + painter.end() + return result + + def _get_conv_avatar(self, conv: dict) -> QIcon: + """Get avatar icon for a conversation list item.""" + is_dm = len(conv["members"]) == 2 and not conv.get("name") + if is_dm: + other = None + for m in conv["members"]: + if m.get("email") != self.bridge.client.email: + other = m + break + if other: + uid = other.get("user_id") or other.get("id") or "" + uname = other.get("username") or other.get("email") or "?" + if uid in self._avatar_cache: + avatar = self._make_circular_avatar(self._avatar_cache[uid]) + else: + avatar = self._make_default_avatar(uname) + # Request avatar download if not yet requested + if uid and uid not in self._avatar_requested: + self._avatar_requested.add(uid) + self.bridge.get_avatar(uid) + if uid in self._online_users: + avatar = self._add_online_dot(avatar) + return QIcon(avatar) + # Group: use group avatar if available + conv_id = conv.get("conversation_id") or "" + if conv_id in self._group_avatar_cache: + return QIcon(self._make_circular_avatar(self._group_avatar_cache[conv_id])) + gname = conv.get("name") or "G" + # Request group avatar download if has avatar_file + if conv.get("avatar_file") and conv_id and conv_id not in self._group_avatar_requested: + self._group_avatar_requested.add(conv_id) + self.bridge.get_group_avatar(conv_id) + return QIcon(self._make_default_avatar(gname)) + + def _get_conv_avatar_pixmap(self, conv: dict, size: int = 44) -> QPixmap: + """Get avatar QPixmap for delegate painting.""" + is_dm = len(conv["members"]) == 2 and not conv.get("name") + if is_dm: + other = None + for m in conv["members"]: + if m.get("email") != self.bridge.client.email: + other = m + break + if other: + uid = other.get("user_id") or other.get("id") or "" + uname = other.get("username") or other.get("email") or "?" + if uid in self._avatar_cache: + avatar = self._make_circular_avatar(self._avatar_cache[uid], size) + else: + avatar = self._make_default_avatar(uname, size) + if uid and uid not in self._avatar_requested: + self._avatar_requested.add(uid) + self.bridge.get_avatar(uid) + if uid in self._online_users: + avatar = self._add_online_dot(avatar) + return avatar + conv_id = conv.get("conversation_id") or "" + if conv_id in self._group_avatar_cache: + return self._make_circular_avatar(self._group_avatar_cache[conv_id], size) + gname = conv.get("name") or "G" + if conv.get("avatar_file") and conv_id and conv_id not in self._group_avatar_requested: + self._group_avatar_requested.add(conv_id) + self.bridge.get_group_avatar(conv_id) + return self._make_default_avatar(gname, size) + + def _build_ui(self): + main_layout = QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + splitter = QSplitter(Qt.Orientation.Horizontal) + + # Left panel - conversations + left = QWidget() + left_layout = QVBoxLayout(left) + left_layout.setContentsMargins(8, 8, 4, 8) + + t = c() + left.setObjectName("sidebarPanel") + left.setStyleSheet(f"#sidebarPanel {{ background-color: {t.bg_tertiary}; }}") + self._sidebar_panel = left + + header_row = QHBoxLayout() + self._conv_label = QLabel("Conversations") + self._conv_label.setStyleSheet(f"font-weight: bold; font-size: 12pt; color: {t.accent}; background: transparent;") + header_row.addWidget(self._conv_label) + header_row.addStretch() + + new_chat_btn = QPushButton("") + new_chat_btn.setFixedSize(32, 32) + new_chat_btn.setObjectName("toolBtn") + new_chat_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogNewFolder)) + new_chat_btn.setToolTip("New Chat") + new_chat_btn.clicked.connect(self._on_new_chat) + header_row.addWidget(new_chat_btn) + + group_btn = QPushButton("") + group_btn.setFixedSize(32, 32) + group_btn.setObjectName("toolBtn") + group_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_DirIcon)) + group_btn.setToolTip("New Group") + group_btn.clicked.connect(self._on_new_group) + header_row.addWidget(group_btn) + + profile_btn = QPushButton("") + profile_btn.setFixedSize(32, 32) + profile_btn.setObjectName("toolBtn") + profile_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogInfoView)) + profile_btn.setToolTip("My Profile") + profile_btn.clicked.connect(self._on_my_profile) + header_row.addWidget(profile_btn) + + left_layout.addLayout(header_row) + + # Invitation section (hidden when empty) + self.inv_label = QLabel("Pending Invitations") + self.inv_label.setStyleSheet(f"font-weight: bold; font-size: 9pt; color: {t.warning}; margin-top: 4px;") + self.inv_label.setVisible(False) + left_layout.addWidget(self.inv_label) + + self.inv_list = QListWidget() + self.inv_list.setMaximumHeight(120) + self.inv_list.setVisible(False) + self.inv_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + self.inv_list.customContextMenuRequested.connect(self._on_inv_context_menu) + self.inv_list.setStyleSheet( + f"QListWidget {{ background-color: {t.bg_primary}; border: 1px solid {t.warning}; border-radius: 6px; padding: 2px; }}" + f"QListWidget::item {{ padding: 6px; color: {t.text_primary}; }}" + f"QListWidget::item:hover {{ background-color: {t.bg_hover}; color: {t.text_primary}; }}" + ) + left_layout.addWidget(self.inv_list) + + self.conv_list = QListWidget() + self.conv_list.setIconSize(QSize(44, 44)) + # Override global QSS item styles — delegate handles all painting + self.conv_list.setStyleSheet( + f"QListWidget {{ background-color: {t.bg_tertiary}; border: none; padding: 0px; }}" + f"QListWidget::item {{ padding: 0px; border: none; }}" + f"QListWidget::item:selected {{ background: transparent; border: none; }}" + f"QListWidget::item:hover {{ background: transparent; }}" + ) + self._conv_delegate = ConversationDelegate(self.conv_list) + self.conv_list.setItemDelegate(self._conv_delegate) + self.conv_list.setMouseTracking(True) # for hover painting + self.conv_list.currentRowChanged.connect(self._on_conv_selected) + self.conv_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + self.conv_list.customContextMenuRequested.connect(self._on_conv_list_context_menu) + left_layout.addWidget(self.conv_list) + + # Bottom toolbar row: settings + logout + bottom_row = QHBoxLayout() + bottom_row.setContentsMargins(0, 4, 0, 0) + + settings_btn = QPushButton("\u2699 Settings") + settings_btn.setObjectName("sidebarBtn") + settings_btn.setToolTip("Open settings") + settings_btn.setStyleSheet( + f"QPushButton {{ background: transparent; color: {t.text_secondary}; border: none; border-radius: 6px; padding: 8px 16px; }}" + f"QPushButton:hover {{ background-color: {t.bg_hover}; }}" + ) + settings_btn.clicked.connect(self._on_open_settings) + self._settings_btn = settings_btn + bottom_row.addWidget(settings_btn) + + logout_btn = QPushButton("\u2715") + logout_btn.setFixedSize(32, 32) + logout_btn.setStyleSheet( + f"QPushButton {{ background: transparent; color: {t.error}; border: none; " + f"border-radius: 16px; font-size: 13pt; font-weight: bold; }}" + f"QPushButton:hover {{ background-color: {t.error}; color: {t.accent_text}; }}" + ) + logout_btn.setToolTip("Logout") + logout_btn.clicked.connect(self._on_logout) + self._logout_btn = logout_btn + bottom_row.addWidget(logout_btn) + + left_layout.addLayout(bottom_row) + + # Right panel - messages + right = QWidget() + right_layout = QVBoxLayout(right) + right_layout.setContentsMargins(4, 8, 8, 8) + + # Chat header bar (56px height) + chat_header_widget = QWidget() + chat_header_widget.setFixedHeight(56) + self._chat_header_widget = chat_header_widget + chat_header_widget.setObjectName("chatHeader") + chat_header_widget.setStyleSheet( + f"#chatHeader {{ border-bottom: 1px solid {t.separator}; }}" + f"#chatHeader QLabel, #chatHeader QPushButton {{ border: none; }}" + ) + chat_header_row = QHBoxLayout(chat_header_widget) + chat_header_row.setContentsMargins(12, 4, 8, 4) + + self.chat_header_avatar = QLabel() + self.chat_header_avatar.setFixedSize(40, 40) + self.chat_header_avatar.setStyleSheet("background: transparent;") + self.chat_header_avatar.setVisible(False) + chat_header_row.addWidget(self.chat_header_avatar) + + # Name + status text vertical stack + name_status_layout = QVBoxLayout() + name_status_layout.setSpacing(0) + name_status_layout.setContentsMargins(6, 0, 0, 0) + self.chat_header = QLabel("Select a conversation") + self.chat_header.setStyleSheet(f"font-weight: bold; font-size: 12pt; color: {t.accent};") + name_status_layout.addWidget(self.chat_header) + + self._chat_header_status = QLabel("") + self._chat_header_status.setStyleSheet(f"color: {t.text_muted}; font-size: 8pt;") + self._chat_header_status.setVisible(False) + name_status_layout.addWidget(self._chat_header_status) + chat_header_row.addLayout(name_status_layout) + + # E2E lock indicator + self._e2e_label = QLabel("\U0001f512 End-to-end encrypted") + self._e2e_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self._e2e_label.setToolTip("End-to-end encrypted") + self._e2e_label.setStyleSheet(f"font-size: 8pt; color: {t.text_muted}; background: transparent;") + self._e2e_label.setCursor(Qt.CursorShape.PointingHandCursor) + self._e2e_label.mousePressEvent = self._on_e2e_label_clicked + self._e2e_label.setVisible(False) + chat_header_row.addWidget(self._e2e_label) + + self.connection_dot = QLabel("\u25cf") + self.connection_dot.setFixedSize(16, 16) + self.connection_dot.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.connection_dot.setStyleSheet(f"color: {t.success}; font-size: 11pt;") + self.connection_dot.setToolTip("Connected") + chat_header_row.addWidget(self.connection_dot) + chat_header_row.addStretch() + + self.group_info_btn = QPushButton("") + self.group_info_btn.setFixedSize(32, 32) + self.group_info_btn.setObjectName("toolBtn") + self.group_info_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MessageBoxInformation)) + self.group_info_btn.setToolTip("Group Info") + self.group_info_btn.clicked.connect(self._on_group_info) + self.group_info_btn.setVisible(False) + chat_header_row.addWidget(self.group_info_btn) + + self.user_info_btn = QPushButton("") + self.user_info_btn.setFixedSize(32, 32) + self.user_info_btn.setObjectName("toolBtn") + self.user_info_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogInfoView)) + self.user_info_btn.setToolTip("User Info") + self.user_info_btn.clicked.connect(self._on_dm_user_info) + self.user_info_btn.setVisible(False) + chat_header_row.addWidget(self.user_info_btn) + + self.delete_conv_btn = QPushButton("") + self.delete_conv_btn.setFixedSize(32, 32) + self.delete_conv_btn.setStyleSheet( + f"QPushButton {{ background: transparent; border: none; border-radius: 4px; padding: 4px; }}" + f"QPushButton:hover {{ background-color: {t.error}; }}" + ) + self.delete_conv_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_TrashIcon)) + self.delete_conv_btn.setToolTip("Delete conversation") + self.delete_conv_btn.clicked.connect(self._on_delete_conv_btn) + self.delete_conv_btn.setVisible(False) + chat_header_row.addWidget(self.delete_conv_btn) + + self.add_member_btn = QPushButton("") + self.add_member_btn.setFixedSize(32, 32) + self.add_member_btn.setObjectName("toolBtn") + self.add_member_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogNewFolder)) + self.add_member_btn.setToolTip("Add Member") + self.add_member_btn.clicked.connect(self._on_add_member) + self.add_member_btn.setVisible(False) + chat_header_row.addWidget(self.add_member_btn) + + self.pin_list_btn = QPushButton("\U0001f4cc") + self.pin_list_btn.setFixedSize(32, 32) + self.pin_list_btn.setObjectName("toolBtn") + self.pin_list_btn.setToolTip("Pinned messages") + self.pin_list_btn.clicked.connect(self._show_pinned_messages) + self.pin_list_btn.setVisible(False) + chat_header_row.addWidget(self.pin_list_btn) + + self.search_btn = QPushButton("") + self.search_btn.setFixedSize(32, 32) + self.search_btn.setObjectName("toolBtn") + self.search_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogContentsView)) + self.search_btn.setToolTip("Search messages (Ctrl+F)") + self.search_btn.clicked.connect(self._toggle_search) + self.search_btn.setVisible(False) + chat_header_row.addWidget(self.search_btn) + + right_layout.addWidget(chat_header_widget) + + # Search bar (hidden by default) + self.search_widget = QWidget() + search_row = QHBoxLayout(self.search_widget) + search_row.setContentsMargins(0, 2, 0, 2) + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("Search messages...") + self.search_input.setStyleSheet( + f"QLineEdit {{ background-color: {t.bg_secondary}; color: {t.text_primary}; " + f"border: 1px solid {t.border}; border-radius: 4px; padding: 4px 8px; font-size: 10pt; }}" + ) + self.search_input.textChanged.connect(self._on_search_text_changed) + self.search_input.returnPressed.connect(self._on_search_next) + # Escape in search input closes search + QShortcut(QKeySequence("Escape"), self.search_input).activated.connect(self._close_search) + search_row.addWidget(self.search_input, stretch=1) + self.search_prev_btn = QPushButton("\u25b2") + self.search_prev_btn.setFixedSize(28, 28) + self.search_prev_btn.setObjectName("toolBtn") + self.search_prev_btn.setToolTip("Previous match") + self.search_prev_btn.clicked.connect(self._on_search_prev) + search_row.addWidget(self.search_prev_btn) + self.search_next_btn = QPushButton("\u25bc") + self.search_next_btn.setFixedSize(28, 28) + self.search_next_btn.setObjectName("toolBtn") + self.search_next_btn.setToolTip("Next match") + self.search_next_btn.clicked.connect(self._on_search_next) + search_row.addWidget(self.search_next_btn) + self.search_count_label = QLabel("0/0") + self.search_count_label.setStyleSheet(f"color: {t.text_muted}; font-size: 9pt; min-width: 40px;") + self.search_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + search_row.addWidget(self.search_count_label) + self.search_close_btn = QPushButton("\u2715") + self.search_close_btn.setFixedSize(28, 28) + self.search_close_btn.setObjectName("toolBtn") + self.search_close_btn.setToolTip("Close search") + self.search_close_btn.clicked.connect(self._close_search) + search_row.addWidget(self.search_close_btn) + self.search_widget.setVisible(False) + right_layout.addWidget(self.search_widget) + + # --- Pinned message banner --- + self._pin_banner = QWidget() + self._pin_banner.setStyleSheet( + f"background-color:{t.border}; border-bottom:2px solid {t.pin_color};" + ) + pin_banner_layout = QHBoxLayout(self._pin_banner) + pin_banner_layout.setContentsMargins(10, 6, 10, 6) + pin_icon = QLabel("\U0001f4cc") + pin_icon.setStyleSheet("font-size:12pt; background:transparent; border:none;") + pin_banner_layout.addWidget(pin_icon) + self._pin_banner_label = QLabel("") + self._pin_banner_label.setStyleSheet( + f"color:{t.text_primary}; font-size:9pt; background:transparent; border:none;" + ) + self._pin_banner_label.setCursor(Qt.CursorShape.PointingHandCursor) + self._pin_banner_label.setWordWrap(False) + pin_banner_layout.addWidget(self._pin_banner_label, stretch=1) + pin_banner_close = QPushButton("\u2715") + pin_banner_close.setFixedSize(20, 20) + pin_banner_close.setStyleSheet( + f"QPushButton {{ background:transparent; color:{t.text_muted}; border:none; font-size:10pt; }}" + f"QPushButton:hover {{ color:{t.text_primary}; }}" + ) + pin_banner_close.clicked.connect(lambda: self._pin_banner.setVisible(False)) + pin_banner_layout.addWidget(pin_banner_close) + self._pin_banner.setVisible(False) + self._pin_banner.setCursor(Qt.CursorShape.PointingHandCursor) + self._pin_banner.mousePressEvent = self._on_pin_banner_clicked + self._pin_banner_msg_id = None + right_layout.addWidget(self._pin_banner) + + self.load_more_btn = QPushButton("Load older messages") + self.load_more_btn.setObjectName("secondaryBtn") + self.load_more_btn.clicked.connect(self._on_load_more) + self.load_more_btn.setVisible(False) + right_layout.addWidget(self.load_more_btn) + + # Message display area — QScrollArea with widget-based bubbles + self._msg_scroll_area = QScrollArea() + self._msg_scroll_area.setWidgetResizable(True) + self._msg_scroll_area.setHorizontalScrollBarPolicy( + Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + self._msg_scroll_area.setStyleSheet( + f"QScrollArea {{ background-color: {t.bg_primary}; border: none; }}" + ) + self._msg_scroll_area.setAcceptDrops(True) + self._msg_scroll_area.installEventFilter(self) + self._msg_scroll_area.viewport().setContextMenuPolicy( + Qt.ContextMenuPolicy.NoContextMenu + ) + self._msg_container = QWidget() + self._msg_container.setStyleSheet(f"background-color: {t.bg_primary};") + self._msg_layout = QVBoxLayout(self._msg_container) + self._msg_layout.setAlignment(Qt.AlignmentFlag.AlignTop) + self._msg_layout.setContentsMargins(0, 8, 0, 8) + self._msg_layout.setSpacing(2) + self._msg_scroll_area.setWidget(self._msg_container) + self._msg_widgets = [] + self.message_area = self._msg_scroll_area # alias for scroll/jump/drop + right_layout.addWidget(self._msg_scroll_area, stretch=1) + + # Smart scroll: track if user is near bottom + self._is_near_bottom = True + self._msg_scroll_area.verticalScrollBar().valueChanged.connect( + self._on_scroll_changed + ) + + # Scroll-to-bottom floating button (hidden by default) + self.jump_btn = QPushButton("\u2193") + self.jump_btn.setParent(self.message_area) + self.jump_btn.setVisible(False) + self.jump_btn.setFixedSize(36, 36) + self.jump_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.accent}; color: {t.accent_text}; border-radius: 18px; " + f"font-size: 14pt; font-weight: bold; }}" + f"QPushButton:hover {{ background-color: {t.accent_hover}; }}" + ) + self.jump_btn.clicked.connect(self._scroll_to_bottom) + + # Reply preview (above input, blue left bar) + self._reply_widget = QWidget() + self._reply_widget.setVisible(False) + reply_row = QHBoxLayout(self._reply_widget) + reply_row.setContentsMargins(8, 4, 8, 0) + reply_row.setSpacing(4) + reply_bar = QFrame() + reply_bar.setFixedWidth(3) + reply_bar.setStyleSheet(f"background-color: {t.accent}; border-radius: 1px;") + reply_row.addWidget(reply_bar) + self.reply_label = QLabel("") + self.reply_label.setStyleSheet( + f"color: {t.accent}; font-style: italic; font-size: 9pt; " + f"padding: 2px 4px; background: transparent;" + ) + self.reply_label.setWordWrap(True) + reply_row.addWidget(self.reply_label, stretch=1) + reply_dismiss = QPushButton("\u2715") + reply_dismiss.setFixedSize(20, 20) + reply_dismiss.setStyleSheet( + f"QPushButton {{ background:transparent; color:{t.text_muted}; border:none; font-size:10pt; }}" + f"QPushButton:hover {{ color:{t.text_primary}; }}" + ) + reply_dismiss.clicked.connect(self._cancel_reply) + reply_row.addWidget(reply_dismiss) + right_layout.addWidget(self._reply_widget) + + # Input row: [attach] [input] [send] + input_row = QHBoxLayout() + input_row.setSpacing(8) + input_row.setContentsMargins(8, 4, 8, 4) + + self._attach_btn = QPushButton("\U0001f4ce") + attach_btn = self._attach_btn + attach_btn.setFixedSize(40, 40) + attach_btn.setCursor(Qt.CursorShape.PointingHandCursor) + attach_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.bg_secondary}; border: none; " + f"border-radius: 20px; font-size: 14pt; }}" + f"QPushButton:hover {{ background-color: {t.bg_hover}; }}" + ) + self._attach_menu = QMenu(attach_btn) + self._attach_menu.addAction("\U0001f5bc Image", self._on_attach_image) + self._attach_menu.addAction("\U0001f4c4 File", self._on_attach_file) + attach_btn.clicked.connect(lambda: self._attach_menu.exec( + attach_btn.mapToGlobal(attach_btn.rect().topLeft() - QPoint(0, self._attach_menu.sizeHint().height())) + )) + input_row.addWidget(attach_btn) + + self.msg_input = MessageInput() + self.msg_input.send_requested.connect(self._on_send) + self.msg_input.textChanged.connect(self._on_input_changed) + self.msg_input.file_dropped.connect(self._on_file_dropped) + input_row.addWidget(self.msg_input, stretch=1) + + self._send_btn = QPushButton("\u27a4") + send_btn = self._send_btn + send_btn.setFixedSize(40, 40) + send_btn.setCursor(Qt.CursorShape.PointingHandCursor) + send_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.accent}; color: {t.accent_text}; " + f"border: none; border-radius: 20px; font-size: 14pt; font-weight: bold; }}" + f"QPushButton:hover {{ background-color: {t.accent_hover}; }}" + ) + send_btn.clicked.connect(self._on_send) + input_row.addWidget(send_btn) + right_layout.addLayout(input_row) + + self.char_counter = QLabel(f"0 / {MAX_INPUT_CHARS}") + self.char_counter.setStyleSheet(f"color: {t.text_muted}; font-size: 8pt; padding: 0 4px;") + self.char_counter.setAlignment(Qt.AlignmentFlag.AlignRight) + right_layout.addWidget(self.char_counter) + + self.reencrypt_label = QLabel("") + self.reencrypt_label.setStyleSheet( + f"background-color: {t.bg_secondary}; border-radius: 6px; " + f"padding: 8px 12px; color: {t.success}; font-weight: bold;" + ) + self.reencrypt_label.setVisible(False) + right_layout.addWidget(self.reencrypt_label) + + splitter.addWidget(left) + splitter.addWidget(right) + splitter.setStretchFactor(0, 1) + splitter.setStretchFactor(1, 3) + + # Wrap splitter + status bar in vertical layout for full-width status bar + wrapper = QVBoxLayout() + wrapper.setContentsMargins(0, 0, 0, 0) + wrapper.setSpacing(0) + wrapper.addWidget(splitter) + + # Status bar (permanent, fixed height, full width — no layout jumping) + self.status_bar = QLabel("") + self.status_bar.setFixedHeight(24) + self.status_bar.setStyleSheet( + f"background-color: {t.bg_tertiary}; border-radius: 0px; " + f"padding: 0 8px; color: {t.success}; font-size: 8pt;" + ) + self.status_bar.setCursor(Qt.CursorShape.PointingHandCursor) + self.status_bar.mousePressEvent = self._on_status_bar_click + self._status_bar_conv_id = None + wrapper.addWidget(self.status_bar) + + main_layout.addLayout(wrapper) + + def _connect_signals(self): + self.bridge.conversations_loaded.connect(self._on_conversations_loaded) + self.bridge.messages_loaded.connect(self._on_messages_loaded) + self.bridge.older_messages_loaded.connect(self._on_older_messages_loaded) + self.bridge.message_sent.connect(self._on_message_sent) + self.bridge.message_sent_payload.connect(self._on_message_sent_payload) + self.bridge.new_notification.connect(self._on_notification) + self.bridge.add_member_result.connect(self._on_add_member_result) + self.bridge.authorize_result.connect(self._on_authorize_result) + self.bridge.rotate_result.connect(self._on_rotate_result) + self.bridge.password_changed.connect(self._on_password_changed) + self.bridge.username_changed.connect(self._on_username_changed) + self.bridge.reencrypt_status.connect(self._on_reencrypt_status) + self.bridge.messages_read_notification.connect(self._on_messages_read) + self.bridge.message_delivered_notification.connect(self._on_message_delivered) + self.bridge.remove_member_result.connect(self._on_remove_member_result) + self.bridge.message_deleted_notification.connect(self._on_message_deleted) + self.bridge.delete_message_result.connect(self._on_delete_message_result) + self.bridge.image_sent.connect(self._on_image_sent) + self.bridge.image_downloaded.connect(self._on_image_downloaded) + self.bridge.file_sent.connect(self._on_file_sent) + self.bridge.file_downloaded.connect(self._on_file_downloaded) + self.bridge.conversation_updated.connect(self._on_conversation_updated) + self.bridge.connection_state_changed.connect(self._on_connection_state_changed) + self.bridge.group_left.connect(self._on_group_left) + self.bridge.group_renamed.connect(self._on_group_renamed) + self.bridge.conversation_deleted.connect(self._on_conversation_deleted) + self.bridge.avatar_loaded.connect(self._on_avatar_for_conv_list) + self.bridge.invitations_loaded.connect(self._on_invitations_loaded) + self.bridge.invitation_result.connect(self._on_invitation_result) + self.bridge.invitation_received.connect(self._on_invitation_received) + self.bridge.online_status_changed.connect(self._on_online_status_changed) + self.bridge.online_users_loaded.connect(self._on_online_users_loaded) + self.bridge.group_avatar_loaded.connect(self._on_group_avatar_for_conv_list) + self.bridge.group_avatar_updated.connect(self._on_group_avatar_updated) + self.bridge.session_reset_notification.connect(self._on_session_reset) + self.bridge.reaction_notification.connect(self._on_reaction_notification) + self.bridge.pin_notification.connect(self._on_pin_notification) + self.bridge.unpin_notification.connect(self._on_unpin_notification) + self.bridge.pinned_messages_loaded.connect(self._on_pinned_messages_loaded) + self.bridge.forward_result.connect(self._on_forward_result) + self.bridge.key_change_warning.connect(self._on_key_change_warning) + self._show_verification_dialog_signal.connect(self._show_verification_dialog) + + # ------------------------------------------------------------------ + # Favorites + # ------------------------------------------------------------------ + + def _favorites_path(self): + from chat_core import get_key_dir + return get_key_dir(self.bridge.client.email) / "favorites.json" + + def _load_favorites(self) -> set[str]: + try: + p = self._favorites_path() + if p.exists(): + return set(json.loads(p.read_text())) + except Exception: + pass + return set() + + def _save_favorites(self): + try: + self._favorites_path().write_text(json.dumps(list(self._favorites))) + except Exception: + pass + + def _on_conv_list_context_menu(self, pos): + item = self.conv_list.itemAt(pos) + if not item: + return + conv_id = item.data(Qt.ItemDataRole.UserRole) + if not conv_id: + return + from PyQt6.QtWidgets import QMenu + menu = QMenu(self) + is_fav = conv_id in self._favorites + action = menu.addAction("Odebrat z oblibených" if is_fav else "Přidat do oblíbených") + result = menu.exec(self.conv_list.mapToGlobal(pos)) + if result == action: + if is_fav: + self._favorites.discard(conv_id) + else: + self._favorites.add(conv_id) + self._save_favorites() + self._rebuild_conv_list() + + # ------------------------------------------------------------------ + # Conversation list helpers + # ------------------------------------------------------------------ + + def _get_conv_display_name(self, conv: dict) -> str: + """Get display name for a conversation (used for sorting and labels).""" + others = [m.get("username") or m.get("email") or "?" for m in conv["members"] + if m.get("email") != self.bridge.client.email] + return conv.get("name") or (", ".join(others) if others else self.bridge.client.username) + + def _get_conv_other_user_id(self, conv: dict) -> str: + """Get the other user's ID in a DM conversation (empty string for groups).""" + is_dm = len(conv["members"]) == 2 and not conv.get("name") + if not is_dm: + return "" + for m in conv["members"]: + if m.get("email") != self.bridge.client.email: + return m.get("user_id") or m.get("id") or "" + return "" + + def _get_conv_sort_key(self, conv: dict) -> tuple: + """Sort key: favorites first, then online DMs, then rest — alphabetically within each.""" + conv_id = conv.get("conversation_id", "") + is_fav = 0 if conv_id in self._favorites else 1 + other_uid = self._get_conv_other_user_id(conv) + is_online = 0 if other_uid and other_uid in self._online_users else 1 + name = self._get_conv_display_name(conv).lower() + return (is_fav, is_online, name) + + def _on_conversations_loaded(self, convs): + self.conversations = convs + # Populate unread counts from server (covers messages received while offline) + for cv in convs: + cid = cv["conversation_id"] + server_unread = cv.get("unread_count", 0) + # Use the higher of server vs local (local may have newer real-time notifications) + if server_unread > self._unread_counts.get(cid, 0): + self._unread_counts[cid] = server_unread + self._rebuild_conv_list() + + def _rebuild_conv_list(self): + """Sort and rebuild the conversation list widget.""" + if not self.conversations: + return + # Sort: favorites first, then online DMs, then rest — alphabetically within each group + self.conversations.sort(key=self._get_conv_sort_key) + prev_id = self.current_conv_id + self.conv_list.blockSignals(True) + self.conv_list.clear() + select_row = -1 + for i, cv in enumerate(self.conversations): + conv_id = cv["conversation_id"] + name = self._get_conv_display_name(cv) + count = self._unread_counts.get(conv_id, 0) + is_fav = conv_id in self._favorites + # Preview + timestamp from last-message cache + preview_text, preview_ts, receipt_st = self._last_message_cache.get(conv_id, ("", "", "")) + rel_ts = _relative_time(preview_ts) + # Avatar pixmap + avatar_pix = self._get_conv_avatar_pixmap(cv) + + # Verification status (DMs only) + verified_status = "" + is_dm = len(cv["members"]) == 2 and not cv.get("name") + if is_dm: + peer_uid = self._get_conv_other_user_id(cv) + if peer_uid: + verified_status = self.bridge.client.get_verification_status(peer_uid) + + item = QListWidgetItem() + item.setSizeHint(QSize(0, ConversationDelegate.ITEM_HEIGHT)) + item.setData(ROLE_CONV_ID, conv_id) + item.setData(ROLE_DISPLAY_NAME, name) + item.setData(ROLE_PREVIEW, preview_text) + item.setData(ROLE_TIMESTAMP, rel_ts) + item.setData(ROLE_UNREAD, count) + item.setData(ROLE_IS_FAV, is_fav) + item.setData(ROLE_AVATAR, avatar_pix) + item.setData(ROLE_VERIFIED, verified_status) + item.setData(ROLE_RECEIPT, receipt_st) + self.conv_list.addItem(item) + if conv_id == prev_id: + select_row = i + self.conv_list.blockSignals(False) + if select_row >= 0: + self.conv_list.setCurrentRow(select_row) + + def _on_conversation_updated(self): + """Refresh conversation list when a conversation is created/member added/removed.""" + self.bridge.load_conversations() + + def _on_periodic_refresh(self): + """Periodic refresh: reload invitations and refresh avatars in small batches.""" + # Keep this first so invitations are not queued behind a large avatar burst. + self.bridge.list_invitations() + + uids = list(self._avatar_requested) + if uids: + n = len(uids) + batch = min(self._AVATAR_REFRESH_BATCH, n) + start = self._avatar_refresh_cursor % n + for i in range(batch): + uid = uids[(start + i) % n] + self.bridge.get_avatar(uid) + self._avatar_refresh_cursor = (start + batch) % n + + conv_ids = list(self._group_avatar_requested) + if conv_ids: + n = len(conv_ids) + batch = min(self._GROUP_AVATAR_REFRESH_BATCH, n) + start = self._group_avatar_refresh_cursor % n + for i in range(batch): + conv_id = conv_ids[(start + i) % n] + self.bridge.get_group_avatar(conv_id) + self._group_avatar_refresh_cursor = (start + batch) % n + + def _on_online_users_loaded(self, user_ids): + self._online_users = set(user_ids) + self._rebuild_conv_list() + + def _on_online_status_changed(self, user_id, is_online): + if is_online: + self._online_users.add(user_id) + else: + self._online_users.discard(user_id) + self._rebuild_conv_list() + + def _on_avatar_for_conv_list(self, user_id, data): + """Cache downloaded avatar and refresh conversation list icons + chat header.""" + qimg = _safe_load_image(data) + if qimg is not None: + self._avatar_cache[user_id] = QPixmap.fromImage(qimg) + self._update_conv_list_styles() + # Refresh chat header avatar if current conv uses this user's avatar + self._refresh_chat_header_avatar() + + def _on_group_avatar_for_conv_list(self, conv_id, data): + """Cache downloaded group avatar and refresh conversation list icons + chat header.""" + qimg = _safe_load_image(data) + if qimg is not None: + self._group_avatar_cache[conv_id] = QPixmap.fromImage(qimg) + self._update_conv_list_styles() + # Refresh chat header avatar if current conv is this group + self._refresh_chat_header_avatar() + + def _on_group_avatar_updated(self, ok, msg): + if not ok: + QMessageBox.warning(self, "Group Avatar", msg) + + def _on_invitations_loaded(self, invitations): + self._pending_invitations = invitations + self.inv_list.clear() + if not invitations: + self.inv_label.setVisible(False) + self.inv_list.setVisible(False) + return + self.inv_label.setVisible(True) + self.inv_list.setVisible(True) + for inv in invitations: + conv_name = inv.get("conversation_name") or "Unnamed group" + inviter = inv.get("invited_by_username", "someone") + label = f"{conv_name} (from {inviter})" + item = QListWidgetItem(label) + item.setData(Qt.ItemDataRole.UserRole, inv["conversation_id"]) + self.inv_list.addItem(item) + + def _on_invitation_result(self, ok, msg): + if not ok: + QMessageBox.warning(self, "Invitation", msg) + + def _on_invitation_received(self, data): + """New invitation received via push notification.""" + self.bridge.list_invitations() + conv_name = data.get("conversation_name") or "a group" + inviter = data.get("invited_by_username", "Someone") + t = c() + self.status_bar.setText(f"{inviter} invited you to {conv_name}") + self.status_bar.setStyleSheet( + f"background-color: {t.bg_tertiary}; border-radius: 0px; " + f"padding: 0 8px; color: {t.warning}; font-size: 8pt; font-weight: bold;" + ) + self._status_bar_conv_id = None + QTimer.singleShot(5000, self._clear_status_bar) + + def _on_inv_context_menu(self, pos): + item = self.inv_list.itemAt(pos) + if not item: + return + conv_id = item.data(Qt.ItemDataRole.UserRole) + if not conv_id: + return + menu = QMenu(self) + accept_action = menu.addAction("Accept") + decline_action = menu.addAction("Decline") + chosen = menu.exec(self.inv_list.mapToGlobal(pos)) + if chosen == accept_action: + self.bridge.accept_invitation(conv_id) + elif chosen == decline_action: + self.bridge.decline_invitation(conv_id) + + def _on_connection_state_changed(self, state): + t = c() + if state == "connected": + self.connection_dot.setStyleSheet(f"color: {t.success}; font-size: 11pt;") + self.connection_dot.setToolTip("Connected") + self.status_bar.setText("Connected") + self.status_bar.setStyleSheet( + f"background-color: {t.bg_tertiary}; border-radius: 0px; " + f"padding: 0 8px; color: {t.success}; font-size: 8pt;" + ) + QTimer.singleShot(3000, self._clear_status_bar) + elif state == "disconnected": + self.connection_dot.setStyleSheet(f"color: {t.error}; font-size: 11pt;") + self.connection_dot.setToolTip("Disconnected") + self.status_bar.setText("Disconnected from server") + self.status_bar.setStyleSheet( + f"background-color: {t.bg_tertiary}; border-radius: 0px; " + f"padding: 0 8px; color: {t.error}; font-size: 8pt; font-weight: bold;" + ) + self._status_bar_conv_id = None + elif state == "reconnecting": + self.connection_dot.setStyleSheet(f"color: {t.warning}; font-size: 11pt;") + self.connection_dot.setToolTip("Reconnecting...") + self.status_bar.setText("Reconnecting...") + self.status_bar.setStyleSheet( + f"background-color: {t.bg_tertiary}; border-radius: 0px; " + f"padding: 0 8px; color: {t.warning}; font-size: 8pt;" + ) + self._status_bar_conv_id = None + elif state == "revoked": + self.connection_dot.setStyleSheet(f"color: {t.error}; font-size: 11pt;") + self.connection_dot.setToolTip("Access revoked") + # Clear conversation list + self.conv_list.clear() + self.conversations = [] + self._unread_counts.clear() + # Clear open conversation + self.current_conv_id = None + self.msg_input.drop_enabled = False + self.chat_header.setText("Select a conversation") + self.chat_header_avatar.setVisible(False) + self._clear_message_area() + self.msg_input.setEnabled(False) + self.group_info_btn.setVisible(False) + self.user_info_btn.setVisible(False) + self.add_member_btn.setVisible(False) + self.delete_conv_btn.setVisible(False) + QMessageBox.warning(self, "Access Revoked", + "Your keys were rotated on another device. " + "This session is no longer valid.") + + def _on_scroll_changed(self, value): + sb = self.message_area.verticalScrollBar() + self._is_near_bottom = (sb.maximum() - value) < 60 + if self._is_near_bottom: + self.jump_btn.setVisible(False) + elif sb.maximum() > 0: + self.jump_btn.setText("\u2193") + self.jump_btn.setVisible(True) + self._position_jump_btn() + + def _scroll_to_bottom(self): + sb = self.message_area.verticalScrollBar() + sb.setValue(sb.maximum()) + self.jump_btn.setVisible(False) + + def _position_jump_btn(self): + w = self.message_area.width() + self.jump_btn.move((w - 36) // 2, self.message_area.height() - 48) + + def _clear_status_bar(self): + self.status_bar.setText("") + t = c() + self.status_bar.setStyleSheet( + f"background-color: {t.bg_tertiary}; border-radius: 0px; " + f"padding: 0 8px; color: {t.success}; font-size: 8pt;" + ) + self._status_bar_conv_id = None + + def _on_status_bar_click(self, event): + conv_id = self._status_bar_conv_id + if conv_id: + for i, c in enumerate(self.conversations): + if c["conversation_id"] == conv_id: + self.conv_list.setCurrentRow(i) + self._clear_status_bar() + break + + def _update_chat_header_avatar(self, conv): + """Set the circular avatar next to the conversation name in the chat header.""" + is_dm = len(conv["members"]) == 2 and not conv.get("name") + size = 40 + t = c() + if is_dm: + other = None + for m in conv["members"]: + if m.get("email") != self.bridge.client.email: + other = m + break + if other: + uid = other.get("user_id") or other.get("id") or "" + uname = other.get("username") or other.get("email") or "?" + if uid in self._avatar_cache: + avatar = self._make_circular_avatar(self._avatar_cache[uid], size) + else: + avatar = self._make_default_avatar(uname, size) + self.chat_header_avatar.setPixmap(avatar) + self.chat_header_avatar.setVisible(True) + # Online status text + if uid in self._online_users: + self._chat_header_status.setText("Online") + self._chat_header_status.setStyleSheet(f"color: {t.success}; font-size: 8pt;") + else: + self._chat_header_status.setText("Offline") + self._chat_header_status.setStyleSheet(f"color: {t.text_muted}; font-size: 8pt;") + self._chat_header_status.setVisible(True) + else: + self.chat_header_avatar.setVisible(False) + self._chat_header_status.setVisible(False) + else: + conv_id = conv.get("conversation_id") or "" + gname = conv.get("name") or "G" + if conv_id in self._group_avatar_cache: + avatar = self._make_circular_avatar(self._group_avatar_cache[conv_id], size) + else: + avatar = self._make_default_avatar(gname, size) + self.chat_header_avatar.setPixmap(avatar) + self.chat_header_avatar.setVisible(True) + # Member count for groups + member_count = len(conv.get("members", [])) + self._chat_header_status.setText(f"{member_count} members") + self._chat_header_status.setStyleSheet(f"color: {t.text_muted}; font-size: 8pt;") + self._chat_header_status.setVisible(True) + # Show E2E indicator with verification status + if is_dm: + peer_uid = "" + for m in conv["members"]: + if m.get("email") != self.bridge.client.email: + peer_uid = m.get("user_id") or m.get("id") or "" + break + v_status = self.bridge.client.get_verification_status(peer_uid) if peer_uid else "" + if v_status == "verified": + self._e2e_label.setText("\u2705 Verified") + self._e2e_label.setStyleSheet(f"font-size: 8pt; color: {t.success}; background: transparent;") + self._e2e_label.setToolTip("Identity verified — click to view safety number") + elif v_status == "trusted": + self._e2e_label.setText("\U0001f512 Encrypted") + self._e2e_label.setStyleSheet(f"font-size: 8pt; color: {t.text_muted}; background: transparent;") + self._e2e_label.setToolTip("End-to-end encrypted (not verified) — click to verify") + else: + self._e2e_label.setText("\U0001f512 Encrypted") + self._e2e_label.setStyleSheet(f"font-size: 8pt; color: {t.text_muted}; background: transparent;") + self._e2e_label.setToolTip("End-to-end encrypted — click to verify") + else: + self._e2e_label.setText("\U0001f512 End-to-end encrypted") + self._e2e_label.setStyleSheet(f"font-size: 8pt; color: {t.text_muted}; background: transparent;") + self._e2e_label.setToolTip("End-to-end encrypted") + self._e2e_label.setVisible(True) + + def _refresh_chat_header_avatar(self): + """Re-render chat header avatar for the currently selected conversation.""" + if not self.current_conv_id: + return + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + self._update_chat_header_avatar(cv) + return + + def _update_conv_list_styles(self): + """Update delegate data roles for all items (after avatar/unread changes).""" + for i in range(self.conv_list.count()): + item = self.conv_list.item(i) + conv_id = item.data(ROLE_CONV_ID) + count = self._unread_counts.get(conv_id, 0) + conv = None + for cv in self.conversations: + if cv["conversation_id"] == conv_id: + conv = cv + break + if conv: + item.setData(ROLE_DISPLAY_NAME, self._get_conv_display_name(conv)) + item.setData(ROLE_AVATAR, self._get_conv_avatar_pixmap(conv)) + item.setData(ROLE_IS_FAV, conv_id in self._favorites) + item.setData(ROLE_UNREAD, count) + # Update preview/timestamp from cache + preview_text, preview_ts, receipt_st = self._last_message_cache.get(conv_id, ("", "", "")) + item.setData(ROLE_PREVIEW, preview_text) + item.setData(ROLE_TIMESTAMP, _relative_time(preview_ts)) + item.setData(ROLE_RECEIPT, receipt_st) + + def _on_conv_selected(self, row): + if row < 0 or row >= len(self.conversations): + return + conv = self.conversations[row] + self.current_conv_id = conv["conversation_id"] + self.msg_input.drop_enabled = True + others = [m.get("username") or m.get("email") or "?" for m in conv["members"] + if m.get("email") != self.bridge.client.email] + header = conv.get("name") or (", ".join(others) if others else self.bridge.client.username) + self.chat_header.setText(header) + # Set avatar in chat header + self._update_chat_header_avatar(conv) + is_group = len(conv["members"]) > 2 or conv.get("name") + self._is_dm = not is_group + self.add_member_btn.setVisible(bool(is_group)) + self.group_info_btn.setVisible(bool(is_group)) + self.user_info_btn.setVisible(self._is_dm) + # DMs: always show delete. Groups: only show for creator. + if self._is_dm: + self.delete_conv_btn.setVisible(True) + else: + my_user_id = self.bridge.client.session.get("user_id", "") if self.bridge.client.session else "" + self.delete_conv_btn.setVisible(conv.get("created_by") == my_user_id) + self.reply_to_id = None + self._reply_widget.setVisible(False) + self._has_more_messages = True + self.load_more_btn.setVisible(False) + self._unread_counts.pop(self.current_conv_id, None) + self._update_conv_list_styles() + self.search_btn.setVisible(True) + self.pin_list_btn.setVisible(True) + self._pin_banner.setVisible(False) + self._close_search() + self.bridge.load_messages(self.current_conv_id) + + def _on_e2e_label_clicked(self, event): + """Open VerificationDialog when E2E label is clicked (DMs only).""" + if not self.current_conv_id or not getattr(self, "_is_dm", False): + return + conv = None + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + conv = cv + break + if not conv: + return + peer_uid = self._get_conv_other_user_id(conv) + if not peer_uid: + return + peer_name = "" + for m in conv["members"]: + if m.get("email") != self.bridge.client.email: + peer_name = m.get("username") or m.get("email") or "?" + break + # Ensure identity key is in cache + self.bridge.schedule(self._ensure_and_show_verification(peer_uid, peer_name)) + + async def _ensure_and_show_verification(self, peer_uid: str, peer_name: str): + """Ensure we have the peer's identity key, then show verification dialog.""" + await self.bridge.client._get_user_info(user_id=peer_uid) + # Emit signal back to Qt thread + self._show_verification_dialog_signal.emit(peer_uid, peer_name) + + def _show_verification_dialog(self, peer_uid: str, peer_name: str): + dlg = VerificationDialog(self.bridge, peer_uid, peer_name, parent=self) + dlg.exec() + # Refresh conv list and header to reflect any verification changes + self._rebuild_conv_list() + if self.current_conv_id: + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + self._update_chat_header_avatar(cv) + break + + def _on_key_change_warning(self, user_id: str, username: str, old_key_hex: str, was_verified: bool, new_key_bytes: bytes = b""): + """Show warning dialog when a contact's identity key has changed.""" + t = c() + severity = "CRITICAL" if was_verified else "WARNING" + name = username or user_id[:8] + msg = ( + f"The identity key for {name} has changed!\n\n" + f"This could mean:\n" + f"- They re-installed the app or got a new device\n" + f"- Someone may be intercepting your messages\n\n" + ) + if was_verified: + msg += "This contact was previously verified. You should re-verify." + + dlg = QDialog(self) + dlg.setMinimumWidth(400) + lay = _make_frameless(dlg, f"Identity Key Changed ({severity})") + + warning_label = QLabel(msg) + warning_label.setWordWrap(True) + warning_label.setStyleSheet(f"color: {t.text_primary};") + lay.addWidget(warning_label) + + btn_row = QHBoxLayout() + accept_btn = QPushButton("Accept New Key") + accept_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.warning}; color: {t.bg_primary}; " + f"font-weight: bold; padding: 8px 16px; border-radius: 6px; }}" + ) + accept_btn.clicked.connect(lambda: self._accept_key_change(user_id, new_key_bytes, dlg)) + btn_row.addWidget(accept_btn) + + close_btn = QPushButton("Dismiss") + close_btn.setObjectName("secondaryBtn") + close_btn.clicked.connect(dlg.accept) + btn_row.addWidget(close_btn) + + lay.addLayout(btn_row) + dlg.exec() + + def _accept_key_change(self, user_id: str, new_key_bytes: bytes, dlg: QDialog): + if new_key_bytes: + self.bridge.client.accept_key_change(user_id, new_key_bytes) + dlg.accept() + self._rebuild_conv_list() + + def _on_messages_loaded(self, conv_id, messages): + if conv_id != self.current_conv_id: + return + self.current_messages = messages + # Update last-message cache for conversation list preview + if messages: + last = messages[-1] + preview = last.get("text", "") + if last.get("deleted"): + preview = "Message deleted" + elif last.get("image") and not preview: + preview = "Sent an image" + elif last.get("file") and not preview: + preview = "Sent a file" + receipt = self._compute_receipt_status(last) + self._last_message_cache[conv_id] = (preview[:60], last.get("created_at", ""), receipt) + self._update_conv_list_styles() + # Show "Load older" if we got a full batch (there may be more) + self._has_more_messages = len(messages) >= 50 + self.load_more_btn.setVisible(self._has_more_messages) + self._render_messages() + self._update_pin_banner() + + def _compute_receipt_status(self, m) -> str: + """Return receipt status for a message: 'read', 'delivered', 'sent', or ''.""" + my_uid = (self.bridge.client.session.get("user_id", "") + if self.bridge and self.bridge.client and self.bridge.client.session else "") + if not my_uid or m.get("sender_id") != my_uid: + return "" + read_by = m.get("read_by", []) + if any(r.get("user_id") != my_uid for r in read_by): + return "read" + delivered_to = m.get("delivered_to", []) + if any(d.get("user_id") != my_uid for d in delivered_to): + return "delivered" + return "sent" + + def _render_messages(self, scroll_to_bottom=True): + """Clear and rebuild all message bubble widgets.""" + self._msg_widgets = [] + while self._msg_layout.count() > 0: + item = self._msg_layout.takeAt(0) + w = item.widget() + if w: + w.deleteLater() + for i, m in enumerate(self.current_messages): + w = self._create_message_widget(m, i) + self._msg_layout.addWidget(w) + self._msg_widgets.append(w) + if scroll_to_bottom: + QTimer.singleShot(10, self._scroll_to_bottom) + + def _clear_message_area(self): + """Remove all message widgets from the scroll area.""" + self._msg_widgets = [] + self.current_messages = [] + while self._msg_layout.count() > 0: + item = self._msg_layout.takeAt(0) + w = item.widget() + if w: + w.deleteLater() + + def _decode_thumbnail(self, image_info): + """Decode base64 thumbnail to QPixmap.""" + thumbnail_b64 = image_info.get("thumbnail", "") + if not thumbnail_b64: + return None + from protocol import decode_binary + try: + thumb_bytes = decode_binary(thumbnail_b64) + qimg = _safe_load_image(thumb_bytes) + if qimg is not None: + return QPixmap.fromImage(qimg) + except Exception: + pass + return None + + def _create_message_widget(self, m, index): + """Create a widget tree for a single message bubble.""" + t = c() + my_uid = (self.bridge.client.session.get("user_id", "") + if self.bridge.client.session else "") + is_me = (m.get("sender_id") == my_uid if my_uid + else m.get("sender") == self.bridge.client.username) + + # -- Wrapper for left/right alignment -- + wrapper = QWidget() + wrapper.setStyleSheet("background: transparent;") + wrapper._msg_index = index + wlay = QHBoxLayout(wrapper) + wlay.setContentsMargins(12, 2, 12, 2) + wlay.setSpacing(0) + + # -- Deleted message -- + if m.get("deleted"): + ts = m.get("created_at", "") + time_str = _format_msg_time(ts) + del_bubble = MessageBubble(t.bg_secondary) + del_bubble._msg_index = index + dlay = QVBoxLayout(del_bubble) + dlay.setContentsMargins(14, 8, 14, 8) + dl = QLabel(f"\u00b7 {time_str} Message deleted") + dl.setStyleSheet( + f"color: {t.text_muted}; font-style: italic; " + f"font-size: 10pt; background: transparent;" + ) + dlay.addWidget(dl) + if is_me: + wlay.addStretch(1) + wlay.addWidget(del_bubble) + if not is_me: + wlay.addStretch(1) + return wrapper + + sender = m.get("sender", "???") + text = m.get("text", "") + + # Determine colours + if is_me: + bubble_bg = t.bubble_sent_bg + text_color = t.bubble_sent_text + meta_color = t.bubble_sent_meta + sender_color = t.accent + else: + bubble_bg = t.bubble_recv_bg + text_color = t.bubble_recv_text + meta_color = t.bubble_recv_meta + sender_color = t.sender_name_other + + if is_me: + wlay.addStretch(1) + + # -- Bubble -- + bubble = MessageBubble(bubble_bg) + bubble._msg_index = index + bubble.setSizePolicy( + QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum + ) + bubble.setMaximumWidth(600) + bubble.setMinimumWidth(80) + + blay = QVBoxLayout(bubble) + blay.setContentsMargins(14, 8, 14, 8) + blay.setSpacing(3) + + # -- Forwarded header -- + fwd = m.get("forwarded_from") + if fwd: + fwd_sender = fwd.get("sender", "???") + fwd_esc = (fwd_sender.replace("&", "&") + .replace("<", "<").replace(">", ">")) + fwd_label = QLabel( + f'' + f'Forwarded from {fwd_esc}' + f'' + ) + fwd_label.setTextFormat(Qt.TextFormat.RichText) + fwd_label.setStyleSheet( + f"background: transparent; border-left: 2px solid {t.info}; " + f"padding-left: 6px; margin-bottom: 2px;" + ) + blay.addWidget(fwd_label) + + # -- Reply context -- + if m.get("reply_to"): + for orig in self.current_messages: + if orig.get("message_id") == m.get("reply_to"): + orig_sender = orig.get("sender", "???") + orig_esc = (orig_sender.replace("&", "&") + .replace("<", "<").replace(">", ">")) + orig_text = orig.get("text", "")[:50] + orig_text_esc = (orig_text.replace("&", "&") + .replace("<", "<").replace(">", ">")) + reply_lbl = QLabel( + f'{orig_esc}
' + f'' + f'{orig_text_esc}' + ) + reply_lbl.setTextFormat(Qt.TextFormat.RichText) + reply_lbl.setWordWrap(True) + reply_lbl.setStyleSheet( + f"background: transparent; " + f"border-left: 2px solid {t.scrollbar}; " + f"padding-left: 6px; margin-bottom: 2px;" + ) + blay.addWidget(reply_lbl) + break + + # -- Header (sender name + pin — groups only) -- + timestamp = m.get("created_at", "") + sender_esc = (sender.replace("&", "&") + .replace("<", "<").replace(">", ">")) + pin_html = "" + if m.get("pinned_at"): + pin_html = f' \U0001f4cc' + + is_dm = self._is_dm + if not is_dm: + header_html = ( + f'{sender_esc}' + f'{pin_html}' + ) + header_label = QLabel(header_html) + header_label.setTextFormat(Qt.TextFormat.RichText) + header_label.setStyleSheet("background: transparent;") + blay.addWidget(header_label) + elif pin_html: + pin_label = QLabel(pin_html) + pin_label.setTextFormat(Qt.TextFormat.RichText) + pin_label.setStyleSheet("background: transparent;") + blay.addWidget(pin_label) + + # -- Suppress image placeholder text -- + image_info = m.get("image") + if image_info: + fname_raw = image_info.get("filename", "image") + if text == f"[Image: {fname_raw}]": + text = "" + + # -- Message text -- + if text: + text_html = _linkify_urls(text) + text_html = text_html.replace("\n", "
") + # Search highlighting + if (self._search_active and self._search_query + and index in self._search_results): + is_current = ( + 0 <= self._search_current < len(self._search_results) + and self._search_results[self._search_current] == index + ) + bg = t.search_current if is_current else t.search_highlight + text_html = self._highlight_search_text( + text_html, self._search_query, bg + ) + # @Mentions highlighting + import re as _re + mention_c = t.mention + text_html = _re.sub( + r'@(\w+)', + lambda mt: ( + f'' + f'@{mt.group(1)}' + ), + text_html, + ) + + text_label = QLabel(text_html) + text_label.setTextFormat(Qt.TextFormat.RichText) + text_label.setWordWrap(True) + text_label.setStyleSheet( + f"color: {text_color}; background: transparent; " + f"font-size: 11pt;" + ) + text_label.setTextInteractionFlags( + Qt.TextInteractionFlag.LinksAccessibleByMouse + ) + text_label.linkActivated.connect(self._on_link_clicked) + blay.addWidget(text_label) + + # -- Image thumbnail -- + if image_info: + thumb_pixmap = self._decode_thumbnail(image_info) + file_id = image_info.get("file_id", "") + filename = image_info.get("filename", "image") + size_bytes = image_info.get("size", 0) + size_str = self._human_file_size(size_bytes) + if thumb_pixmap: + img_label = QLabel() + scaled = thumb_pixmap.scaledToWidth( + min(200, thumb_pixmap.width()), + Qt.TransformationMode.SmoothTransformation, + ) + img_label.setPixmap(scaled) + img_label.setStyleSheet("background: transparent;") + img_label.setCursor(Qt.CursorShape.PointingHandCursor) + img_label.mousePressEvent = ( + lambda e, fid=file_id: self._on_image_click(fid) + ) + blay.addWidget(img_label) + + link_color = text_color if is_me else t.accent + fname_esc = (filename.replace("&", "&") + .replace("<", "<").replace(">", ">")) + info_label = QLabel( + f'' + f'{fname_esc} ({size_str}) \u2014 Click to view' + ) + info_label.setTextFormat(Qt.TextFormat.RichText) + info_label.setStyleSheet( + f"font-size: 9pt; background: transparent;" + ) + info_label.setTextInteractionFlags( + Qt.TextInteractionFlag.LinksAccessibleByMouse + ) + info_label.linkActivated.connect(self._on_link_clicked) + blay.addWidget(info_label) + + # -- File card -- + file_info = m.get("file") + if file_info: + fname = file_info.get("filename", "file") + fname_esc = (fname.replace("&", "&") + .replace("<", "<").replace(">", ">")) + fsize = file_info.get("size", 0) + size_str = self._human_file_size(fsize) + f_id = file_info.get("file_id", "") + icon = self._file_icon(fname) + file_link_color = text_color if is_me else t.accent + + file_frame = QFrame() + file_frame.setStyleSheet( + f"QFrame {{ background: transparent; " + f"border: 1px solid {meta_color}; " + f"border-radius: 6px; padding: 8px; }}" + ) + flay = QHBoxLayout(file_frame) + flay.setContentsMargins(0, 0, 0, 0) + file_label = QLabel( + f'{icon} {fname_esc}' + f' ({size_str})' + ) + file_label.setTextFormat(Qt.TextFormat.RichText) + file_label.setStyleSheet("background: transparent; border: none;") + file_label.setTextInteractionFlags( + Qt.TextInteractionFlag.LinksAccessibleByMouse + ) + file_label.linkActivated.connect(self._on_link_clicked) + flay.addWidget(file_label) + blay.addWidget(file_frame) + + # -- Reaction badges -- + reactions = m.get("reactions", []) + if reactions: + _REACTION_EMOJI = { + "thumbsup": "\U0001f44d", "heart": "\u2764\ufe0f", + "laugh": "\U0001f602", "surprised": "\U0001f62e", + "sad": "\U0001f622", "thumbsdown": "\U0001f44e", + } + grouped = {} + for r in reactions: + grouped.setdefault(r["reaction"], []).append(r["user_id"]) + my_id = (self.bridge.client.session.get("user_id", "") + if self.bridge.client.session else "") + react_widget = QWidget() + react_widget.setStyleSheet("background: transparent;") + react_lay = QHBoxLayout(react_widget) + react_lay.setContentsMargins(0, 2, 0, 0) + react_lay.setSpacing(4) + for rkey, uids in grouped.items(): + emoji = _REACTION_EMOJI.get(rkey, rkey) + count = len(uids) + is_mine = my_id in uids + bg = t.reaction_bg_own if is_mine else t.reaction_bg + bdr = t.reaction_border_own if is_mine else t.reaction_border + badge = QLabel(f"{emoji} {count}") + badge.setStyleSheet( + f"background-color: {bg}; color: {t.text_primary}; " + f"border: 1px solid {bdr}; " + f"border-radius: 10px; padding: 2px 6px; font-size: 10pt;" + ) + react_lay.addWidget(badge) + react_lay.addStretch() + blay.addWidget(react_widget) + + # -- Footer (timestamp + receipt, right-aligned, below content) -- + time_str = _format_msg_time(timestamp) + receipt_status = "" + if is_me: + read_by = m.get("read_by", []) + others_read = [r for r in read_by if r.get("user_id") != my_uid] + delivered_to = m.get("delivered_to", []) + others_delivered = [d for d in delivered_to if d.get("user_id") != my_uid] + if others_read: + receipt_status = "read" + elif others_delivered: + receipt_status = "delivered" + else: + receipt_status = "sent" + footer_w = _ReceiptFooter(time_str, receipt_status, meta_color, + t.bubble_sent_text, t.success) + footer_w.setStyleSheet("background: transparent;") + blay.addWidget(footer_w, alignment=Qt.AlignmentFlag.AlignRight) + + wlay.addWidget(bubble) + if not is_me: + wlay.addStretch(1) + + # Install event filter on all child widgets for context menu propagation + for child in bubble.findChildren(QWidget): + child.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) + child.installEventFilter(self) + bubble.installEventFilter(self) + wrapper.installEventFilter(self) + + return wrapper + + def _on_image_click(self, file_id): + """Handle click on an image thumbnail in a message bubble.""" + for msg in self.current_messages: + image_info = msg.get("image") + if image_info and image_info.get("file_id") == file_id: + self._view_image(msg) + return + + def _find_msg_index_at_widget(self, widget): + """Walk up the widget tree to find the _msg_index attribute.""" + while widget: + if hasattr(widget, '_msg_index'): + return widget._msg_index + widget = widget.parentWidget() + return None + + def _on_older_messages_loaded(self, conv_id, messages): + if conv_id != self.current_conv_id: + return + if not messages: + self._has_more_messages = False + self.load_more_btn.setVisible(False) + return + self._has_more_messages = len(messages) >= 50 + self.load_more_btn.setVisible(self._has_more_messages) + # Prepend older messages and re-render + self.current_messages = messages + self.current_messages + self._render_messages(scroll_to_bottom=False) + + def _on_load_more(self): + if not self.current_conv_id or not self._has_more_messages: + return + offset = len(self.current_messages) + self.bridge.load_older_messages(self.current_conv_id, offset) + + def _on_message_context_menu(self, pos): + if not self.current_messages: + return + global_pos = self._msg_scroll_area.viewport().mapToGlobal(pos) + widget = QApplication.widgetAt(global_pos) + idx = self._find_msg_index_at_widget(widget) + if idx is not None: + self._show_msg_context_menu(idx, global_pos) + + def _show_msg_context_menu(self, idx, global_pos): + if idx < 0 or idx >= len(self.current_messages): + return + m = self.current_messages[idx] + if m.get("deleted"): + return + + my_user_id = self.bridge.client.session.get("user_id", "") if self.bridge.client.session else "" + menu = QMenu(self) + + reply_icon = self.style().standardIcon(QStyle.StandardPixmap.SP_ArrowBack) + reply_action = menu.addAction(reply_icon, "Reply") + + # Reaction submenu + react_menu = menu.addMenu("React") + _REACTION_LABELS = { + "thumbsup": "\U0001f44d +1", + "heart": "\u2764\ufe0f Heart", + "laugh": "\U0001f602 Haha", + "surprised": "\U0001f62e Wow", + "sad": "\U0001f622 Sad", + "thumbsdown": "\U0001f44e -1", + } + react_actions = {} + for rkey, rlabel in _REACTION_LABELS.items(): + act = react_menu.addAction(rlabel) + react_actions[act] = rkey + + # Forward + fwd_action = menu.addAction("Forward") + + # Pin / Unpin + pin_action = None + if m.get("pinned_at"): + pin_action = menu.addAction("\U0001f4cc Unpin") + else: + pin_action = menu.addAction("\U0001f4cc Pin") + + menu.addSeparator() + + # Delete option for own messages + del_action = None + if m.get("sender_id") == my_user_id: + del_icon = self.style().standardIcon(QStyle.StandardPixmap.SP_TrashIcon) + del_action = menu.addAction(del_icon, "Delete") + + # View image option + img_action = None + if m.get("image"): + img_icon = self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogContentsView) + img_action = menu.addAction(img_icon, "View image") + + # Download file option + file_action = None + if m.get("file"): + file_icon = self.style().standardIcon(QStyle.StandardPixmap.SP_DialogSaveButton) + file_action = menu.addAction(file_icon, "Download file") + + # Reset session option for undecryptable messages + reset_action = None + if m.get("text", "").startswith("[Decryption failed"): + reset_icon = self.style().standardIcon(QStyle.StandardPixmap.SP_BrowserReload) + reset_action = menu.addAction(reset_icon, "Reset session with sender") + + chosen = menu.exec(global_pos) + if not chosen: + return + if chosen == reply_action: + self.reply_to_id = m["message_id"] + sender = m.get("sender", "???") + preview = m.get("text", "")[:40] + self.reply_label.setText(f"Replying to {sender}: {preview}") + self._reply_widget.setVisible(True) + self.msg_input.setFocus() + elif chosen in react_actions: + rkey = react_actions[chosen] + existing = m.get("reactions", []) + has_it = any(r["user_id"] == my_user_id and r["reaction"] == rkey for r in existing) + if has_it: + m["reactions"] = [r for r in existing if r["user_id"] != my_user_id] + self.bridge.react_message(m["message_id"], rkey, "remove") + else: + new_reactions = [r for r in existing if r["user_id"] != my_user_id] + new_reactions.append({"user_id": my_user_id, "reaction": rkey, "created_at": ""}) + m["reactions"] = new_reactions + self.bridge.react_message(m["message_id"], rkey, "add") + # Persist to local cache so reactions survive conversation switch + self.bridge.client.update_message_in_cache( + self.current_conv_id, m["message_id"], + {"reactions": m["reactions"]}) + self._render_messages(scroll_to_bottom=self._is_near_bottom) + elif chosen == fwd_action: + self._show_forward_dialog(m) + elif chosen == pin_action: + if m.get("pinned_at"): + m.pop("pinned_at", None) + m.pop("pinned_by", None) + self.bridge.pin_message(m["message_id"], self.current_conv_id, "unpin") + self.bridge.client.update_message_in_cache( + self.current_conv_id, m["message_id"], + {"pinned_at": None, "pinned_by": None}) + else: + my_user_id_pin = self.bridge.client.session.get("user_id", "") if self.bridge.client.session else "" + m["pinned_at"] = "now" + m["pinned_by"] = my_user_id_pin + self.bridge.pin_message(m["message_id"], self.current_conv_id, "pin") + self.bridge.client.update_message_in_cache( + self.current_conv_id, m["message_id"], + {"pinned_at": "now", "pinned_by": my_user_id_pin}) + self._render_messages(scroll_to_bottom=self._is_near_bottom) + self._update_pin_banner() + elif chosen == del_action: + if self._confirm_dialog("Delete Message", + "Delete this message? This cannot be undone."): + # Apply locally immediately (server notification only goes to others) + m["deleted"] = True + m["text"] = "" + m["image"] = None + m["file"] = None + self.bridge.client.update_message_in_cache( + self.current_conv_id, m["message_id"], {"deleted": True}) + self._render_messages(scroll_to_bottom=self._is_near_bottom) + self.bridge.delete_message(m["message_id"]) + elif chosen == img_action: + self._view_image(m) + elif chosen == file_action: + file_info = m.get("file") + if file_info: + self.bridge.download_file(file_info["file_id"], file_info) + elif chosen == reset_action: + sender_id = m.get("sender_id", "") + if sender_id: + if self._confirm_dialog("Reset Session", + "Reset encryption session with this sender? " + "A new session will be created on the next message."): + self.bridge.reset_session(sender_id) + + # ------------------------------------------------------------------ + # Search + # ------------------------------------------------------------------ + + def _toggle_search(self): + if self._search_active: + self._close_search() + else: + if not self.current_conv_id: + return + self._search_active = True + self.search_widget.setVisible(True) + self.search_input.setFocus() + self.search_input.selectAll() + + def _close_search(self): + self._search_active = False + self._search_query = "" + self._search_results = [] + self._search_current = -1 + self.search_widget.setVisible(False) + self.search_input.clear() + self.search_count_label.setText("0/0") + # Re-render to remove highlights + if self.current_messages: + self._render_messages(scroll_to_bottom=False) + + def _on_search_text_changed(self, text): + self._search_query = text.strip() + if not self._search_query: + self._search_results = [] + self._search_current = -1 + self.search_count_label.setText("0/0") + self._render_messages(scroll_to_bottom=False) + return + query_lower = self._search_query.lower() + self._search_results = [] + for i, m in enumerate(self.current_messages): + if m.get("deleted"): + continue + msg_text = m.get("text", "") + if query_lower in msg_text.lower(): + self._search_results.append(i) + if self._search_results: + self._search_current = 0 + self.search_count_label.setText(f"1/{len(self._search_results)}") + else: + self._search_current = -1 + self.search_count_label.setText("0/0") + self._render_messages(scroll_to_bottom=False) + if self._search_results: + self._scroll_to_message(self._search_results[self._search_current]) + + def _on_search_next(self): + if not self._search_results: + return + self._search_current = (self._search_current + 1) % len(self._search_results) + self.search_count_label.setText(f"{self._search_current + 1}/{len(self._search_results)}") + self._render_messages(scroll_to_bottom=False) + self._scroll_to_message(self._search_results[self._search_current]) + + def _on_search_prev(self): + if not self._search_results: + return + self._search_current = (self._search_current - 1) % len(self._search_results) + self.search_count_label.setText(f"{self._search_current + 1}/{len(self._search_results)}") + self._render_messages(scroll_to_bottom=False) + self._scroll_to_message(self._search_results[self._search_current]) + + def _scroll_to_message(self, index): + if 0 <= index < len(self._msg_widgets): + self._msg_scroll_area.ensureWidgetVisible( + self._msg_widgets[index], 0, 50 + ) + + @staticmethod + def _highlight_search_text(html_text: str, query: str, bg_color: str) -> str: + """Highlight matching text in HTML, skipping content inside tags.""" + query_esc = query.replace("&", "&").replace("<", "<").replace(">", ">") + if not query_esc: + return html_text + result = [] + i = 0 + q_lower = query_esc.lower() + q_len = len(query_esc) + while i < len(html_text): + if html_text[i] == '<': + # Skip HTML tags + end = html_text.find('>', i) + if end == -1: + result.append(html_text[i:]) + break + result.append(html_text[i:end + 1]) + i = end + 1 + else: + # Look for match in text content + chunk_end = html_text.find('<', i) + if chunk_end == -1: + chunk_end = len(html_text) + chunk = html_text[i:chunk_end] + # Case-insensitive replace within this chunk + chunk_lower = chunk.lower() + out = [] + j = 0 + while j < len(chunk): + pos = chunk_lower.find(q_lower, j) + if pos == -1: + out.append(chunk[j:]) + break + out.append(chunk[j:pos]) + matched = chunk[pos:pos + q_len] + out.append(f'{matched}') + j = pos + q_len + result.append("".join(out)) + i = chunk_end + return "".join(result) + + # ------------------------------------------------------------------ + # Session reset + # ------------------------------------------------------------------ + + def _on_session_reset(self, from_user_id, from_device_id): + # Find username for the user + username = from_user_id[:8] + for cv in self.conversations: + for m in cv.get("members", []): + uid = m.get("user_id") or m.get("id") + if uid == from_user_id: + username = m.get("username") or m.get("email") or username + break + self.status_bar.setText(f"Session with {username} was reset. New session will be created on next message.") + t = c() + self.status_bar.setStyleSheet( + f"background-color: {t.bg_tertiary}; border-radius: 0px; " + f"padding: 0 8px; color: {t.warning}; font-size: 8pt; font-weight: bold;" + ) + QTimer.singleShot(8000, self._clear_status_bar) + + # ------------------------------------------------------------------ + # Reactions / Pins / Forward notification handlers + # ------------------------------------------------------------------ + + def _on_reaction_notification(self, data): + """Handle message_reacted push notification.""" + conv_id = data.get("conversation_id", "") + msg_id = data.get("message_id", "") + user_id = data.get("user_id", "") + reaction = data.get("reaction", "") + action = data.get("action", "add") + + if conv_id != self.current_conv_id: + return + for msg in self.current_messages: + if msg.get("message_id") == msg_id: + reactions = msg.get("reactions", []) + if action == "add": + reactions = [r for r in reactions if r["user_id"] != user_id] + reactions.append({"user_id": user_id, "reaction": reaction, "created_at": ""}) + else: + reactions = [r for r in reactions if r["user_id"] != user_id] + msg["reactions"] = reactions + self.bridge.client.update_message_in_cache( + conv_id, msg_id, {"reactions": reactions}) + break + self._render_messages(scroll_to_bottom=self._is_near_bottom) + + def _on_pin_notification(self, data): + """Handle message_pinned push notification.""" + conv_id = data.get("conversation_id", "") + msg_id = data.get("message_id", "") + user_id = data.get("user_id", "") + if conv_id != self.current_conv_id: + return + for msg in self.current_messages: + if msg.get("message_id") == msg_id: + msg["pinned_at"] = "now" + msg["pinned_by"] = user_id + self.bridge.client.update_message_in_cache( + conv_id, msg_id, {"pinned_at": "now", "pinned_by": user_id}) + break + self._render_messages(scroll_to_bottom=self._is_near_bottom) + self._update_pin_banner() + username = data.get("username", user_id[:8] if user_id else "?") + self.status_bar.setText(f"{username} pinned a message") + QTimer.singleShot(3000, self._clear_status_bar) + + def _on_unpin_notification(self, data): + """Handle message_unpinned push notification.""" + conv_id = data.get("conversation_id", "") + msg_id = data.get("message_id", "") + if conv_id != self.current_conv_id: + return + for msg in self.current_messages: + if msg.get("message_id") == msg_id: + msg.pop("pinned_at", None) + msg.pop("pinned_by", None) + self.bridge.client.update_message_in_cache( + conv_id, msg_id, {"pinned_at": None, "pinned_by": None}) + break + self._render_messages(scroll_to_bottom=self._is_near_bottom) + self._update_pin_banner() + + def _on_pinned_messages_loaded(self, conv_id, pinned): + """Show pinned messages dialog.""" + if conv_id != self.current_conv_id: + return + if not pinned: + dlg = QDialog(self) + dlg.setMinimumWidth(300) + lay = _make_frameless(dlg, "Pinned Messages") + t = c() + lbl = QLabel("No pinned messages in this conversation.") + lbl.setStyleSheet(f"color: {t.text_primary}; font-size: 10pt;") + lbl.setWordWrap(True) + lay.addWidget(lbl) + ok_btn = QPushButton("OK") + ok_btn.clicked.connect(dlg.accept) + lay.addWidget(ok_btn) + dlg.exec() + return + dlg = QDialog(self) + dlg.setMinimumSize(360, 300) + t = c() + layout = _make_frameless(dlg, "Pinned Messages") + lw = QListWidget() + lw.setStyleSheet( + f"QListWidget {{ background-color:{t.bg_secondary}; border:1px solid {t.border}; border-radius:6px; }}" + f"QListWidget::item {{ padding:8px; color:{t.text_primary}; border-bottom:1px solid {t.border}; }}" + f"QListWidget::item:selected {{ background-color:{t.bg_hover}; }}" + ) + # Build a sender map from current messages + sender_map = {} + for m in self.current_messages: + sid = m.get("sender_id", "") + if sid and sid not in sender_map: + sender_map[sid] = m.get("sender", sid[:8]) + for p in pinned: + sender = sender_map.get(p.get("sender_id", ""), p.get("sender_id", "?")[:8]) + # Find message text from current_messages + text_preview = "" + for m in self.current_messages: + if m.get("message_id") == p.get("message_id"): + text_preview = m.get("text", "")[:60] + if not sender_map.get(p.get("sender_id", "")): + sender = m.get("sender", sender) + break + ts = p.get("pinned_at", "")[:16] if p.get("pinned_at") else "" + item = QListWidgetItem(f"\U0001f4cc {sender}: {text_preview}\n Pinned: {ts}") + item.setData(Qt.ItemDataRole.UserRole, p.get("message_id")) + lw.addItem(item) + layout.addWidget(lw) + close_btn = QPushButton("Close") + close_btn.setObjectName("secondaryBtn") + close_btn.clicked.connect(dlg.accept) + layout.addWidget(close_btn) + + def _on_item_clicked(item): + target_id = item.data(Qt.ItemDataRole.UserRole) + if target_id: + for i, msg in enumerate(self.current_messages): + if msg.get("message_id") == target_id: + self._scroll_to_message(i) + break + dlg.accept() + lw.itemDoubleClicked.connect(_on_item_clicked) + dlg.exec() + + def _update_pin_banner(self): + """Update the pinned message banner from current_messages.""" + pinned = [m for m in self.current_messages if m.get("pinned_at")] + if not pinned: + self._pin_banner.setVisible(False) + self._pin_banner_msg_id = None + return + # Show the most recently pinned message + latest = pinned[-1] + sender = latest.get("sender", "?") + text = latest.get("text", "") + if len(text) > 80: + text = text[:80] + "..." + text = text.replace("\n", " ") + # HTML-escape user-controlled data to prevent injection + sender_esc = sender.replace("&", "&").replace("<", "<").replace(">", ">") + text_esc = text.replace("&", "&").replace("<", "<").replace(">", ">") + count_str = f" ({len(pinned)} pinned)" if len(pinned) > 1 else "" + self._pin_banner_label.setText(f"{sender_esc}: {text_esc}{count_str}") + self._pin_banner_msg_id = latest.get("message_id") + self._pin_banner.setVisible(True) + + def _on_pin_banner_clicked(self, event): + """Scroll to the pinned message when banner is clicked.""" + if self._pin_banner_msg_id: + for i, msg in enumerate(self.current_messages): + if msg.get("message_id") == self._pin_banner_msg_id: + self._scroll_to_message(i) + break + + def _show_pinned_messages(self): + """Fetch and show pinned messages for current conversation.""" + if self.current_conv_id: + self.bridge.get_pinned_messages(self.current_conv_id) + + def _on_forward_result(self, ok, msg): + if ok: + self.status_bar.setText("Message forwarded") + QTimer.singleShot(3000, self._clear_status_bar) + else: + self.status_bar.setText(f"Forward failed: {msg}") + QTimer.singleShot(5000, self._clear_status_bar) + + def _confirm_dialog(self, title: str, text: str) -> bool: + """Show a frameless confirmation dialog. Returns True if user confirmed.""" + dlg = QDialog(self) + dlg.setMinimumWidth(340) + layout = _make_frameless(dlg, title) + t = c() + label = QLabel(text) + label.setWordWrap(True) + label.setStyleSheet(f"color: {t.text_primary}; font-size: 10pt;") + layout.addWidget(label) + btn_lay = QHBoxLayout() + btn_lay.setSpacing(8) + cancel_btn = QPushButton("Cancel") + cancel_btn.setObjectName("secondaryBtn") + confirm_btn = QPushButton("Delete") + confirm_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.error}; color: {t.accent_text}; " + f"border: none; border-radius: 6px; padding: 8px 16px; font-weight: bold; }}" + f"QPushButton:hover {{ background-color: {t.warning}; }}" + ) + btn_lay.addStretch() + btn_lay.addWidget(cancel_btn) + btn_lay.addWidget(confirm_btn) + layout.addLayout(btn_lay) + cancel_btn.clicked.connect(dlg.reject) + confirm_btn.clicked.connect(dlg.accept) + return dlg.exec() == QDialog.DialogCode.Accepted + + def _show_forward_dialog(self, msg): + """Show dialog to pick a conversation to forward a message to.""" + dlg = QDialog(self) + dlg.setMinimumSize(320, 360) + t = c() + layout = _make_frameless(dlg, "Forward message") + label = QLabel("Select conversation:") + label.setStyleSheet(f"color:{t.text_primary}; font-size:10pt;") + layout.addWidget(label) + conv_list = QListWidget() + conv_list.setStyleSheet( + f"QListWidget {{ background-color:{t.bg_secondary}; border:1px solid {t.border}; border-radius:6px; }}" + f"QListWidget::item {{ padding:8px; color:{t.text_primary}; }}" + f"QListWidget::item:selected {{ background-color:{t.bg_hover}; }}" + ) + for cv in self.conversations: + if cv["conversation_id"] != self.current_conv_id: + name = cv.get("name") + if not name: + others = [m.get("username") or m.get("email") or "?" for m in cv["members"] + if m.get("email") != self.bridge.client.email] + name = ", ".join(others) if others else "?" + item = QListWidgetItem(name) + item.setData(Qt.ItemDataRole.UserRole, cv) + conv_list.addItem(item) + layout.addWidget(conv_list) + fwd_btn = QPushButton("Forward") + fwd_btn.setObjectName("secondaryBtn") + layout.addWidget(fwd_btn) + + def _do_forward(): + sel = conv_list.currentItem() + if not sel: + return + target_conv = sel.data(Qt.ItemDataRole.UserRole) + if target_conv: + fwd_msg = dict(msg) + fwd_msg["conversation_id"] = self.current_conv_id + self.bridge.forward_message( + target_conv["conversation_id"], fwd_msg, target_conv["members"] + ) + dlg.accept() + + fwd_btn.clicked.connect(_do_forward) + conv_list.itemDoubleClicked.connect(lambda _: _do_forward()) + dlg.exec() + + # ------------------------------------------------------------------ + # @Mentions autocomplete + # ------------------------------------------------------------------ + + def _setup_mention_completer(self): + """Set up the mention autocomplete popup for msg_input.""" + self._mention_popup = QListWidget(self) + self._mention_popup.setWindowFlags(Qt.WindowType.Popup) + t = c() + self._mention_popup.setStyleSheet( + f"QListWidget {{ background:{t.bg_secondary}; color:{t.text_primary}; border:1px solid {t.border}; " + f"border-radius:4px; font-size:10pt; }}" + f"QListWidget::item {{ padding:4px 8px; }}" + f"QListWidget::item:selected {{ background:{t.bg_hover}; }}" + ) + self._mention_popup.setMaximumHeight(150) + self._mention_popup.itemClicked.connect(self._on_mention_selected) + self._mention_popup.hide() + self._mention_active = False + + def _check_mention_trigger(self): + """Check if user is typing @mention and show autocomplete.""" + if not hasattr(self, '_mention_popup'): + self._setup_mention_completer() + text = self.msg_input.toPlainText() + cursor_pos = self.msg_input.textCursor().position() + # Find @word at cursor position + before = text[:cursor_pos] + import re as _re + match = _re.search(r'@(\w*)$', before) + if not match: + self._mention_popup.hide() + self._mention_active = False + return + prefix = match.group(1).lower() + self._mention_start = match.start() + + # Get members of current conversation + members = [] + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + members = cv.get("members", []) + break + my_email = self.bridge.client.email if self.bridge.client else "" + candidates = [] + for m in members: + uname = m.get("username") or m.get("email") or "" + if m.get("email") == my_email: + continue + if prefix == "" or uname.lower().startswith(prefix): + candidates.append(uname) + + if not candidates: + self._mention_popup.hide() + self._mention_active = False + return + + self._mention_popup.clear() + for cand in candidates[:6]: + self._mention_popup.addItem(cand) + # Position popup above the input + cursor_rect = self.msg_input.cursorRect() + global_pos = self.msg_input.mapToGlobal(cursor_rect.bottomLeft()) + self._mention_popup.move(global_pos.x(), global_pos.y() - self._mention_popup.sizeHint().height() - 5) + self._mention_popup.setFixedWidth(max(200, self.msg_input.width() // 2)) + self._mention_popup.show() + self._mention_active = True + + def _on_mention_selected(self, item): + """Insert the selected @mention into msg_input.""" + username = item.text() + text = self.msg_input.toPlainText() + cursor_pos = self.msg_input.textCursor().position() + # Replace @prefix with @username + new_text = text[:self._mention_start] + f"@{username} " + text[cursor_pos:] + self.msg_input.setPlainText(new_text) + cursor = self.msg_input.textCursor() + cursor.setPosition(self._mention_start + len(username) + 2) + self.msg_input.setTextCursor(cursor) + self._mention_popup.hide() + self._mention_active = False + self.msg_input.setFocus() + + def _on_input_changed(self): + text = self.msg_input.toPlainText() + count = len(text) + if count > MAX_INPUT_CHARS: + cursor = self.msg_input.textCursor() + self.msg_input.setPlainText(text[:MAX_INPUT_CHARS]) + cursor.movePosition(cursor.MoveOperation.End) + self.msg_input.setTextCursor(cursor) + count = MAX_INPUT_CHARS + color = c().error if count > MAX_INPUT_CHARS * 0.9 else c().text_muted + self.char_counter.setStyleSheet(f"color: {color}; font-size: 8pt; padding: 0 4px;") + self.char_counter.setText(f"{count} / {MAX_INPUT_CHARS}") + # @Mention autocomplete check + if self.current_conv_id: + self._check_mention_trigger() + + def _cancel_reply(self): + """Dismiss the reply preview.""" + self.reply_to_id = None + self.reply_label.setText("") + self._reply_widget.setVisible(False) + + def _on_send(self): + text = self.msg_input.toPlainText().strip() + if not text or not self.current_conv_id: + return + if len(text) > MAX_INPUT_CHARS: + QMessageBox.warning(self, "Message Too Long", + f"Message too long (max {MAX_INPUT_CHARS} characters).") + return + conv = None + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + conv = cv + break + if not conv: + return + self.msg_input.clear() + self.bridge.send_message( + self.current_conv_id, text, conv["members"], + reply_to=self.reply_to_id, + ) + self.reply_to_id = None + self._reply_widget.setVisible(False) + + def _on_message_sent(self, ok, msg): + if not ok: + QMessageBox.warning(self, "Error", msg) + + def _on_message_sent_payload(self, conv_id, payload): + """Append the just-sent message locally without re-fetching from server.""" + # Update last-message cache + preview = payload.get("text", "") + if payload.get("image") and not preview: + preview = "Sent an image" + elif payload.get("file") and not preview: + preview = "Sent a file" + self._last_message_cache[conv_id] = (preview[:60], payload.get("created_at", ""), "sent") + self._update_conv_list_styles() + + if conv_id != self.current_conv_id: + return + # Avoid duplicate if notification arrived first (race) + msg_id = payload.get("message_id", "") + if msg_id: + for m in self.current_messages: + if m.get("message_id") == msg_id: + return + self.current_messages.append(payload) + idx = len(self.current_messages) - 1 + w = self._create_message_widget(payload, idx) + self._msg_layout.addWidget(w) + self._msg_widgets.append(w) + if self._is_near_bottom: + QTimer.singleShot(10, self._scroll_to_bottom) + + def _on_new_chat(self): + dlg = QDialog(self) + dlg.setMinimumWidth(400) + t = c() + lay = _make_frameless(dlg, "New Chat") + lay.setSpacing(12) + + email_label = QLabel("Recipient email") + email_label.setStyleSheet(f"color: {t.text_secondary}; font-size: 9pt;") + lay.addWidget(email_label) + email_input = QLineEdit() + email_input.setPlaceholderText("user@example.com") + email_input.setMinimumHeight(36) + lay.addWidget(email_input) + + msg_label = QLabel("First message") + msg_label.setStyleSheet(f"color: {t.text_secondary}; font-size: 9pt;") + lay.addWidget(msg_label) + msg_input = QLineEdit() + msg_input.setPlaceholderText("Type a message...") + msg_input.setMinimumHeight(36) + lay.addWidget(msg_input) + + btn_row = QHBoxLayout() + btn_row.addStretch() + cancel_btn = QPushButton("Cancel") + cancel_btn.setObjectName("secondaryBtn") + cancel_btn.clicked.connect(dlg.reject) + btn_row.addWidget(cancel_btn) + send_btn = QPushButton("Send") + send_btn.clicked.connect(dlg.accept) + btn_row.addWidget(send_btn) + lay.addLayout(btn_row) + + msg_input.returnPressed.connect(dlg.accept) + email_input.returnPressed.connect(lambda: msg_input.setFocus()) + + if dlg.exec() != QDialog.DialogCode.Accepted: + return + email = email_input.text().strip() + text = msg_input.text().strip() + if not email or not text: + return + if len(text) > MAX_INPUT_CHARS: + QMessageBox.warning(self, "Message Too Long", + f"Message too long (max {MAX_INPUT_CHARS} characters).") + return + self.bridge.send_new_chat(email, text) + + def _on_new_group(self): + dlg = QDialog(self) + dlg.setMinimumWidth(400) + t = c() + lay = _make_frameless(dlg, "New Group") + lay.setSpacing(12) + + name_label = QLabel("Group name") + name_label.setStyleSheet(f"color: {t.text_secondary}; font-size: 9pt;") + lay.addWidget(name_label) + name_input = QLineEdit() + name_input.setPlaceholderText("My Group") + name_input.setMinimumHeight(36) + lay.addWidget(name_input) + + members_label = QLabel("Member emails (comma-separated)") + members_label.setStyleSheet(f"color: {t.text_secondary}; font-size: 9pt;") + lay.addWidget(members_label) + members_input = QLineEdit() + members_input.setPlaceholderText("alice@example.com, bob@example.com") + members_input.setMinimumHeight(36) + lay.addWidget(members_input) + + btn_row = QHBoxLayout() + btn_row.addStretch() + cancel_btn = QPushButton("Cancel") + cancel_btn.setObjectName("secondaryBtn") + cancel_btn.clicked.connect(dlg.reject) + btn_row.addWidget(cancel_btn) + create_btn = QPushButton("Create") + create_btn.clicked.connect(dlg.accept) + btn_row.addWidget(create_btn) + lay.addLayout(btn_row) + + members_input.returnPressed.connect(dlg.accept) + name_input.returnPressed.connect(lambda: members_input.setFocus()) + + if dlg.exec() != QDialog.DialogCode.Accepted: + return + members = members_input.text().strip() + if not members: + return + member_list = [m.strip() for m in members.split(",") if m.strip()] + if member_list: + self.bridge.create_group(member_list, name=name_input.text().strip() or None) + + def _on_add_member(self): + if not self.current_conv_id: + return + dlg = QDialog(self) + dlg.setMinimumWidth(360) + t = c() + lay = _make_frameless(dlg, "Add Member") + lay.setSpacing(12) + lbl = QLabel("Email to invite") + lbl.setStyleSheet(f"color: {t.text_secondary}; font-size: 9pt;") + lay.addWidget(lbl) + email_input = QLineEdit() + email_input.setPlaceholderText("user@example.com") + email_input.setMinimumHeight(36) + email_input.returnPressed.connect(dlg.accept) + lay.addWidget(email_input) + btn_row = QHBoxLayout() + btn_row.addStretch() + cancel_btn = QPushButton("Cancel") + cancel_btn.setObjectName("secondaryBtn") + cancel_btn.clicked.connect(dlg.reject) + btn_row.addWidget(cancel_btn) + add_btn = QPushButton("Add") + add_btn.clicked.connect(dlg.accept) + btn_row.addWidget(add_btn) + lay.addLayout(btn_row) + if dlg.exec() != QDialog.DialogCode.Accepted: + return + email = email_input.text().strip() + if not email: + return + self.bridge.add_member(self.current_conv_id, email) + + def _on_add_member_result(self, ok, msg): + if ok: + QMessageBox.information(self, "Add Member", "Invitation sent.") + else: + QMessageBox.warning(self, "Add Member", msg) + + def _on_group_info(self): + if not self.current_conv_id: + return + conv = None + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + conv = cv + break + if not conv: + return + my_user_id = self.bridge.client.session.get("user_id", "") if self.bridge.client.session else "" + is_creator = conv.get("created_by") == my_user_id + group_name = conv.get("name") or "Group" + members = conv["members"] + + dlg = QDialog(self) + dlg.setMinimumWidth(380) + t = c() + dlg_layout = _make_frameless(dlg, "Group Info") + + # Group avatar + avatar_row = QHBoxLayout() + avatar_label = QLabel() + avatar_label.setFixedSize(64, 64) + avatar_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + conv_id = conv["conversation_id"] + if conv_id in self._group_avatar_cache: + avatar_pix = self._make_circular_avatar(self._group_avatar_cache[conv_id], size=64) + else: + avatar_pix = self._make_default_avatar(group_name, size=64) + avatar_label.setPixmap(avatar_pix) + avatar_row.addWidget(avatar_label) + + group_name_esc = group_name.replace("&", "&").replace("<", "<").replace(">", ">") + title = QLabel(f"{group_name_esc}") + avatar_row.addWidget(title, stretch=1) + + if is_creator: + change_avatar_btn = QPushButton("Change Avatar") + change_avatar_btn.setObjectName("secondaryBtn") + change_avatar_btn.clicked.connect(lambda: self._do_change_group_avatar(conv_id, dlg)) + avatar_row.addWidget(change_avatar_btn) + + rename_btn = QPushButton("Rename") + rename_btn.setObjectName("secondaryBtn") + rename_btn.clicked.connect(lambda: self._do_rename_group(conv_id, group_name, dlg)) + avatar_row.addWidget(rename_btn) + + dlg_layout.addLayout(avatar_row) + + count_label = QLabel(f"Members ({len(members)}):") + count_label.setStyleSheet("margin-top: 8px;") + dlg_layout.addWidget(count_label) + + for mem in members: + uname = mem.get("username") or mem.get("email") or "?" + email = mem.get("email", "") + uid = mem.get("user_id") or mem.get("id") or "" + is_mem_creator = uid == conv.get("created_by") + + row = QHBoxLayout() + uname_esc = uname.replace("&", "&").replace("<", "<").replace(">", ">") + email_esc = email.replace("&", "&").replace("<", "<").replace(">", ">") + is_online = uid in self._online_users + online_dot = "\U0001f7e2 " if is_online else "" + verified_dot = "" + my_uid = self.bridge.client.session.get("user_id", "") if self.bridge.client.session else "" + if uid and uid != my_uid and self.bridge.client.get_verification_status(uid) == "verified": + verified_dot = " \u2705" + name_text = f"{online_dot}{uname_esc}{verified_dot}" + if email: + name_text += f" {email_esc}" + if is_mem_creator: + name_text += f" creator" + name_label = QLabel(name_text) + name_label.setWordWrap(True) + row.addWidget(name_label, stretch=1) + + info_btn = QPushButton("") + info_btn.setFixedSize(28, 28) + info_btn.setObjectName("secondaryBtn") + info_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MessageBoxInformation)) + info_btn.setToolTip(f"View profile of {uname}") + info_btn.clicked.connect(lambda checked, u=uid, d=dlg: (d.accept(), self._show_user_profile(u))) + row.addWidget(info_btn) + + # Remove button (only for creator, not on self) + if is_creator and uid != my_user_id: + remove_btn = QPushButton("") + remove_btn.setFixedSize(28, 28) + remove_btn.setObjectName("secondaryBtn") + remove_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_DialogCloseButton)) + remove_btn.setToolTip(f"Remove {uname}") + remove_btn.clicked.connect(lambda checked, u=uid, n=uname, d=dlg: self._do_remove_member_action(u, n, d)) + row.addWidget(remove_btn) + + dlg_layout.addLayout(row) + + dlg_layout.addSpacing(12) + + # Leave Group button + leave_btn = QPushButton("Leave Group") + leave_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.error}; color: {t.accent_text}; font-weight: bold; }}" + f"QPushButton:hover {{ opacity: 0.8; }}" + ) + leave_btn.clicked.connect(lambda: self._do_leave_group_action(dlg)) + dlg_layout.addWidget(leave_btn) + + close_btn = QPushButton("Close") + close_btn.clicked.connect(dlg.accept) + dlg_layout.addWidget(close_btn) + dlg.exec() + + def _do_remove_member_action(self, user_id, username, dialog): + if not self.current_conv_id: + return + confirm = QMessageBox.question( + self, "Remove Member", + f"Remove {username} from the group?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + ) + if confirm == QMessageBox.StandardButton.Yes: + dialog.accept() + self.bridge.remove_member(self.current_conv_id, user_id) + + def _do_change_group_avatar(self, conv_id, dialog): + path, _ = QFileDialog.getOpenFileName( + dialog, "Select Group Avatar", "", + "Images (*.png *.jpg *.jpeg);;All Files (*)", + ) + if not path: + return + try: + with open(path, "rb") as f: + image_data = f.read() + if len(image_data) > 2 * 1024 * 1024: + QMessageBox.warning(dialog, "Too Large", "Avatar must be under 2 MB.") + return + dialog.accept() + self.bridge.update_group_avatar(conv_id, image_data) + except Exception as e: + QMessageBox.warning(dialog, "Error", f"Failed to read image: {e}") + + def _do_rename_group(self, conv_id, current_name, dialog): + from PyQt6.QtWidgets import QInputDialog + new_name, ok = QInputDialog.getText( + dialog, "Rename Group", "New group name:", + text=current_name, + ) + if ok and new_name.strip(): + new_name = new_name.strip() + if new_name != current_name: + dialog.accept() + self.bridge.rename_conversation(conv_id, new_name) + + def _on_group_renamed(self, ok, msg): + if not ok: + QMessageBox.warning(self, "Rename Group", msg) + + def _do_leave_group_action(self, dialog): + if not self.current_conv_id: + return + confirm = QMessageBox.question( + self, "Leave Group", + "Leave this group? You will no longer receive messages.", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + ) + if confirm == QMessageBox.StandardButton.Yes: + dialog.accept() + self.bridge.leave_group(self.current_conv_id) + + def _on_group_left(self, ok, msg): + if ok: + self.current_conv_id = None + self.msg_input.drop_enabled = False + self.chat_header.setText("Select a conversation") + self.chat_header_avatar.setVisible(False) + self._clear_message_area() + self.group_info_btn.setVisible(False) + self.user_info_btn.setVisible(False) + self.add_member_btn.setVisible(False) + self.delete_conv_btn.setVisible(False) + else: + QMessageBox.warning(self, "Leave Group", msg) + + def _on_delete_conv_btn(self): + if not self.current_conv_id: + return + confirm = QMessageBox.question( + self, "Delete Conversation", + "Delete this conversation? This cannot be undone.", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + ) + if confirm == QMessageBox.StandardButton.Yes: + self.bridge.delete_conversation(self.current_conv_id) + + def _on_conversation_deleted(self, ok, msg): + if ok: + self.current_conv_id = None + self.msg_input.drop_enabled = False + self.chat_header.setText("Select a conversation") + self.chat_header_avatar.setVisible(False) + self._clear_message_area() + self.group_info_btn.setVisible(False) + self.user_info_btn.setVisible(False) + self.add_member_btn.setVisible(False) + self.delete_conv_btn.setVisible(False) + else: + QMessageBox.warning(self, "Delete Conversation", msg) + + def _on_remove_member_result(self, ok, msg): + if ok: + QMessageBox.information(self, "Remove Member", "Member removed.") + if self.current_conv_id: + self.bridge.load_messages(self.current_conv_id) + else: + QMessageBox.warning(self, "Remove Member", msg) + + def _on_my_profile(self): + my_user_id = self.bridge.client.session.get("user_id", "") if self.bridge.client.session else "" + if not my_user_id: + return + dlg = UserProfileDialog(self.bridge, my_user_id, editable=True, parent=self) + dlg.exec() + + def _on_dm_user_info(self): + """Show profile of the other user in a DM conversation.""" + if not self.current_conv_id: + return + conv = None + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + conv = cv + break + if not conv: + return + my_email = self.bridge.client.email + for m in conv["members"]: + if m.get("email") != my_email: + uid = m.get("user_id") or m.get("id") + if uid: + self._show_user_profile(uid) + return + + def _show_user_profile(self, user_id): + dlg = UserProfileDialog(self.bridge, user_id, editable=False, parent=self) + dlg.exec() + + def _on_open_settings(self): + t = c() + dlg = QDialog(self) + dlg.setMinimumWidth(360) + lay = _make_frameless(dlg, "Settings") + lay.setSpacing(16) + + # -- Appearance section -- + sec_appearance = QLabel("Appearance") + sec_appearance.setStyleSheet( + f"font-size: 10pt; font-weight: bold; color: {t.text_secondary}; " + f"margin-top: 4px;" + ) + lay.addWidget(sec_appearance) + + theme_row = QHBoxLayout() + theme_label = QLabel("Theme") + theme_label.setStyleSheet(f"font-size: 11pt; color: {t.text_primary};") + theme_row.addWidget(theme_label) + theme_row.addStretch() + theme_btn = QPushButton( + "\u2600 Light mode" if tm().is_dark else "\U0001f319 Dark mode" + ) + theme_btn.setObjectName("secondaryBtn") + theme_btn.setFixedWidth(140) + theme_row.addWidget(theme_btn) + lay.addLayout(theme_row) + + # Separator + sep1 = QFrame() + sep1.setFrameShape(QFrame.Shape.HLine) + sep1.setStyleSheet(f"background-color: {t.separator}; max-height: 1px;") + lay.addWidget(sep1) + + # -- Security section -- + sec_security = QLabel("Security") + sec_security.setStyleSheet( + f"font-size: 10pt; font-weight: bold; color: {t.text_secondary};" + ) + lay.addWidget(sec_security) + + # Rotate Keys + rotate_row = QHBoxLayout() + rotate_info = QVBoxLayout() + rotate_title = QLabel("Rotate Keys") + rotate_title.setStyleSheet(f"font-size: 11pt; color: {t.text_primary};") + rotate_info.addWidget(rotate_title) + rotate_desc = QLabel("Generate new RSA keys. Revokes other devices.") + rotate_desc.setStyleSheet(f"font-size: 8pt; color: {t.text_muted};") + rotate_desc.setWordWrap(True) + rotate_info.addWidget(rotate_desc) + rotate_row.addLayout(rotate_info, stretch=1) + rotate_btn = QPushButton("Rotate") + rotate_btn.setObjectName("secondaryBtn") + rotate_btn.setFixedWidth(100) + rotate_btn.clicked.connect(lambda: (dlg.close(), self._on_rotate_keys())) + rotate_row.addWidget(rotate_btn) + lay.addLayout(rotate_row) + + # Change Username + chun_row = QHBoxLayout() + chun_info = QVBoxLayout() + chun_title = QLabel("Change Username") + chun_title.setStyleSheet(f"font-size: 11pt; color: {t.text_primary};") + chun_info.addWidget(chun_title) + chun_desc = QLabel("Change your display name.") + chun_desc.setStyleSheet(f"font-size: 8pt; color: {t.text_muted};") + chun_desc.setWordWrap(True) + chun_info.addWidget(chun_desc) + chun_row.addLayout(chun_info, stretch=1) + chun_btn = QPushButton("Change") + chun_btn.setObjectName("secondaryBtn") + chun_btn.setFixedWidth(100) + chun_btn.clicked.connect(lambda: (dlg.close(), self._on_change_username())) + chun_row.addWidget(chun_btn) + lay.addLayout(chun_row) + + # Change Password + chpw_row = QHBoxLayout() + chpw_info = QVBoxLayout() + chpw_title = QLabel("Change Password") + chpw_title.setStyleSheet(f"font-size: 11pt; color: {t.text_primary};") + chpw_info.addWidget(chpw_title) + chpw_desc = QLabel("Change password for local key encryption.") + chpw_desc.setStyleSheet(f"font-size: 8pt; color: {t.text_muted};") + chpw_desc.setWordWrap(True) + chpw_info.addWidget(chpw_desc) + chpw_row.addLayout(chpw_info, stretch=1) + chpw_btn = QPushButton("Change") + chpw_btn.setObjectName("secondaryBtn") + chpw_btn.setFixedWidth(100) + chpw_btn.clicked.connect(lambda: (dlg.close(), self._on_change_password())) + chpw_row.addWidget(chpw_btn) + lay.addLayout(chpw_row) + + # Separator + sep2 = QFrame() + sep2.setFrameShape(QFrame.Shape.HLine) + sep2.setStyleSheet(f"background-color: {t.separator}; max-height: 1px;") + lay.addWidget(sep2) + + # -- Devices section -- + sec_devices = QLabel("Devices") + sec_devices.setStyleSheet( + f"font-size: 10pt; font-weight: bold; color: {t.text_secondary};" + ) + lay.addWidget(sec_devices) + + # Link Device (new) + link_row = QHBoxLayout() + link_info = QVBoxLayout() + link_title = QLabel("Link New Device") + link_title.setStyleSheet(f"font-size: 11pt; color: {t.text_primary};") + link_info.addWidget(link_title) + link_desc = QLabel("Authorize another device to access your account.") + link_desc.setStyleSheet(f"font-size: 8pt; color: {t.text_muted};") + link_desc.setWordWrap(True) + link_info.addWidget(link_desc) + link_row.addLayout(link_info, stretch=1) + link_btn = QPushButton("Link") + link_btn.setObjectName("secondaryBtn") + link_btn.setFixedWidth(100) + link_btn.clicked.connect(lambda: (dlg.close(), self._on_authorize_device())) + link_row.addWidget(link_btn) + lay.addLayout(link_row) + + # Wire up theme toggle now that all widgets exist + _s_labels = [sec_appearance, sec_security, sec_devices] + _t_labels = [theme_label, rotate_title, chpw_title, link_title] + _d_labels = [rotate_desc, chpw_desc, link_desc] + _seps = [sep1, sep2] + + def _toggle_and_update(): + tm().toggle() + theme_btn.setText( + "\u2600 Light mode" if tm().is_dark else "\U0001f319 Dark mode" + ) + t2 = c() + dlg._frameless_container.setStyleSheet( + f"#_framelessContainer {{ background-color: {t2.bg_primary}; border-radius: 12px; }}" + ) + dlg._frameless_title_bar.setStyleSheet( + f"background-color: {t2.bg_secondary}; " + f"border-top-left-radius: 12px; border-top-right-radius: 12px;" + ) + dlg._frameless_title_label.setStyleSheet( + f"font-weight: bold; font-size: 10pt; color: {t2.text_primary}; background: transparent;" + ) + for lbl in _s_labels: + lbl.setStyleSheet(f"font-size: 10pt; font-weight: bold; color: {t2.text_secondary};") + for lbl in _t_labels: + lbl.setStyleSheet(f"font-size: 11pt; color: {t2.text_primary};") + for lbl in _d_labels: + lbl.setStyleSheet(f"font-size: 8pt; color: {t2.text_muted};") + for s in _seps: + s.setStyleSheet(f"background-color: {t2.separator}; max-height: 1px;") + theme_btn.clicked.connect(_toggle_and_update) + + lay.addStretch() + + # Close button + close_btn = QPushButton("Close") + close_btn.clicked.connect(dlg.close) + lay.addWidget(close_btn) + + dlg.exec() + + def _on_authorize_device(self): + code, ok = QInputDialog.getText(self, "Authorize Device", "Pairing code:") + if not ok or not code.strip(): + return + self.bridge.authorize_device(code.strip()) + + def _on_rotate_keys(self): + confirm = QMessageBox.question( + self, + "Rotate Keys", + "This will revoke other devices. Continue?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + ) + if confirm != QMessageBox.StandardButton.Yes: + return + password, ok = QInputDialog.getText(self, "Rotate Keys", "Password:", QLineEdit.EchoMode.Password) + if not ok or not password: + return + self.bridge.rotate_keys(self.bridge.client.username, password) + + def _on_change_password(self): + t = c() + dlg = QDialog(self) + dlg.setMinimumWidth(360) + lay = _make_frameless(dlg, "Change Password") + + old_label = QLabel("Current Password") + old_label.setStyleSheet(f"color: {t.text_secondary}; font-size: 9pt;") + lay.addWidget(old_label) + old_input = QLineEdit() + old_input.setEchoMode(QLineEdit.EchoMode.Password) + old_input.setPlaceholderText("Enter current password") + old_input.setStyleSheet( + f"QLineEdit {{ font-size: 11pt; background-color: {t.bg_secondary}; " + f"border: 1px solid {t.border}; border-radius: 6px; padding: 8px; " + f"color: {t.text_primary}; }}" + f"QLineEdit:focus {{ border: 1px solid {t.border_focus}; }}" + ) + lay.addWidget(old_input) + + new_label = QLabel("New Password") + new_label.setStyleSheet(f"color: {t.text_secondary}; font-size: 9pt;") + lay.addWidget(new_label) + new_input = QLineEdit() + new_input.setEchoMode(QLineEdit.EchoMode.Password) + new_input.setPlaceholderText("Enter new password") + new_input.setStyleSheet(old_input.styleSheet()) + lay.addWidget(new_input) + + confirm_label = QLabel("Confirm New Password") + confirm_label.setStyleSheet(f"color: {t.text_secondary}; font-size: 9pt;") + lay.addWidget(confirm_label) + confirm_input = QLineEdit() + confirm_input.setEchoMode(QLineEdit.EchoMode.Password) + confirm_input.setPlaceholderText("Re-enter new password") + confirm_input.setStyleSheet(old_input.styleSheet()) + lay.addWidget(confirm_input) + + error_label = QLabel("") + error_label.setStyleSheet(f"color: {t.error}; font-size: 9pt;") + error_label.hide() + lay.addWidget(error_label) + + btn_lay = QHBoxLayout() + btn_lay.addStretch() + change_btn = QPushButton("Change Password") + change_btn.setStyleSheet( + f"QPushButton {{ background-color: {t.accent}; color: {t.accent_text}; " + f"border: none; border-radius: 6px; padding: 8px 16px; font-weight: bold; }}" + f"QPushButton:hover {{ opacity: 0.9; }}" + ) + btn_lay.addWidget(change_btn) + lay.addLayout(btn_lay) + + def _do_change(): + old_pw = old_input.text() + new_pw = new_input.text() + conf_pw = confirm_input.text() + if not old_pw: + error_label.setText("Current password is required.") + error_label.show() + return + if not new_pw: + error_label.setText("New password cannot be empty.") + error_label.show() + return + if new_pw != conf_pw: + error_label.setText("New passwords do not match.") + error_label.show() + return + dlg.accept() + self.bridge.change_password(old_pw, new_pw) + + change_btn.clicked.connect(_do_change) + confirm_input.returnPressed.connect(_do_change) + dlg.exec() + + def _on_logout(self): + confirm = QMessageBox.question( + self, + "Logout", + "Log out and return to the login screen?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + ) + if confirm != QMessageBox.StandardButton.Yes: + return + self._is_logout = True + self.bridge.logout() + self.close() + if self._on_logout_cb: + self._on_logout_cb() + + def _on_notification(self, payload): + sender = payload.get("sender", "???") + conv_id = payload.get("conversation_id", "") + + # Update last-message cache for conversation list preview + if conv_id: + preview = payload.get("text", "") + if payload.get("image") and not preview: + preview = "Sent an image" + elif payload.get("file") and not preview: + preview = "Sent a file" + if preview: + self._last_message_cache[conv_id] = ( + f"{sender}: {preview}"[:60], + payload.get("created_at", ""), + "", # incoming message — no receipt status for others' msgs + ) + + # Resolve conversation name for notifications + conv_name = sender + is_notif_dm = False + if conv_id: + for cv in self.conversations: + if cv["conversation_id"] == conv_id: + is_notif_dm = len(cv["members"]) == 2 and not cv.get("name") + if not is_notif_dm: + conv_name = cv.get("name") or sender + break + + # System tray toast when window is not visible or not focused + if conv_id: + if is_notif_dm: + notif_title = sender + notif_text = payload.get("text", "New message") + else: + notif_title = conv_name + notif_text = f"{sender}: {payload.get('text', 'New message')}" + if payload.get("image"): + notif_text = notif_text or "Sent an image" + elif payload.get("file"): + notif_text = notif_text or "Sent a file" + self._show_tray_notification(notif_title, notif_text) + + # Show notification in status bar (for non-current conversations) + if conv_id and conv_id != self.current_conv_id: + if is_notif_dm: + self.status_bar.setText(f"New message from {sender}") + else: + self.status_bar.setText(f"New message from {sender} in {conv_name}") + t = c() + self.status_bar.setStyleSheet( + f"background-color: {t.bg_tertiary}; border-radius: 0px; " + f"padding: 0 8px; color: {t.success}; font-size: 8pt; font-weight: bold;" + ) + self._status_bar_conv_id = conv_id + QTimer.singleShot(5000, self._clear_status_bar) + + # Confirm delivery for all incoming messages (always, regardless of current view) + msg_id = payload.get("message_id", "") + if conv_id and msg_id: + self.bridge.schedule( + self.bridge.client.confirm_delivery(conv_id, [msg_id]) + ) + + # Increment unread count if not currently viewing this conversation + # (or if privacy overlay is locked — user can't see messages) + viewing = conv_id == self.current_conv_id and not self._privacy_locked + if conv_id and not viewing: + self._unread_counts[conv_id] = self._unread_counts.get(conv_id, 0) + 1 + self._update_conv_list_styles() + + # Append directly to current conversation instead of re-fetching + if conv_id == self.current_conv_id: + # Avoid duplicate if local send already appended this message + if msg_id: + for m in self.current_messages: + if m.get("message_id") == msg_id: + return + self.current_messages.append(payload) + idx = len(self.current_messages) - 1 + w = self._create_message_widget(payload, idx) + self._msg_layout.addWidget(w) + self._msg_widgets.append(w) + if self._is_near_bottom: + QTimer.singleShot(10, self._scroll_to_bottom) + else: + self.jump_btn.setText("\u2193 New") + self.jump_btn.setVisible(True) + self._position_jump_btn() + # Mark as read (only if not locked) + if msg_id and not self._privacy_locked: + self.bridge.schedule( + self.bridge.client.mark_read(conv_id, [msg_id]) + ) + + def _on_messages_read(self, data): + conv_id = data.get("conversation_id", "") + user_id = data.get("user_id", "") + message_ids = set(data.get("message_ids", [])) + my_uid = self.bridge.client.session.get("user_id", "") if self.bridge.client.session else "" + + # Persist to cache for ALL conversations (not just current) + if conv_id == self.current_conv_id: + for msg in self.current_messages: + if message_ids and msg.get("message_id") not in message_ids: + continue + if not message_ids and msg.get("sender_id") != my_uid: + continue + read_by = msg.get("read_by", []) + if not any(r.get("user_id") == user_id for r in read_by): + read_by.append({"user_id": user_id}) + msg["read_by"] = read_by + self.bridge.client.update_message_in_cache( + conv_id, msg.get("message_id"), {"read_by": read_by}) + self._render_messages(scroll_to_bottom=self._is_near_bottom) + else: + # Non-current conversation: update cache only (no in-memory messages to update) + cached = self.bridge.client.load_message_cache(conv_id) + if cached: + for msg_id_key, entry in cached.items(): + if message_ids and msg_id_key not in message_ids: + continue + if not message_ids and entry.get("sender_id") != my_uid: + continue + read_by = entry.get("read_by", []) + if not any(r.get("user_id") == user_id for r in read_by): + read_by.append({"user_id": user_id}) + self.bridge.client.update_message_in_cache( + conv_id, msg_id_key, {"read_by": read_by}) + # Update conv list receipt status to "read" + if conv_id in self._last_message_cache: + prev_text, prev_ts, prev_receipt = self._last_message_cache[conv_id] + if prev_receipt in ("sent", "delivered"): + self._last_message_cache[conv_id] = (prev_text, prev_ts, "read") + self._update_conv_list_styles() + + def _on_message_delivered(self, data): + conv_id = data.get("conversation_id", "") + user_id = data.get("user_id", "") + message_ids = set(data.get("message_ids", [])) + + if conv_id == self.current_conv_id: + for msg in self.current_messages: + if msg.get("message_id") in message_ids: + delivered_to = msg.get("delivered_to", []) + if not any(d.get("user_id") == user_id for d in delivered_to): + delivered_to.append({"user_id": user_id}) + msg["delivered_to"] = delivered_to + self.bridge.client.update_message_in_cache( + conv_id, msg.get("message_id"), {"delivered_to": delivered_to}) + self._render_messages(scroll_to_bottom=self._is_near_bottom) + else: + # Non-current conversation: update cache only + cached = self.bridge.client.load_message_cache(conv_id) + if cached: + for msg_id_key, entry in cached.items(): + if msg_id_key in message_ids: + delivered_to = entry.get("delivered_to", []) + if not any(d.get("user_id") == user_id for d in delivered_to): + delivered_to.append({"user_id": user_id}) + self.bridge.client.update_message_in_cache( + conv_id, msg_id_key, {"delivered_to": delivered_to}) + # Update conv list receipt status to "delivered" + if conv_id in self._last_message_cache: + prev_text, prev_ts, prev_receipt = self._last_message_cache[conv_id] + if prev_receipt == "sent": + self._last_message_cache[conv_id] = (prev_text, prev_ts, "delivered") + self._update_conv_list_styles() + + def _on_link_clicked(self, url_str): + """Handle link clicks from message bubble labels.""" + if url_str.startswith("image://"): + file_id = url_str[len("image://"):] + for msg in self.current_messages: + image_info = msg.get("image") + if image_info and image_info.get("file_id") == file_id: + self._view_image(msg) + return + elif url_str.startswith("file://"): + file_id = url_str[len("file://"):] + for msg in self.current_messages: + file_info = msg.get("file") + if file_info and file_info.get("file_id") == file_id: + self.bridge.download_file(file_id, file_info) + return + elif url_str.startswith("https://"): + QDesktopServices.openUrl(QUrl(url_str)) + elif url_str.startswith("http://"): + reply = QMessageBox.warning( + self, + "Insecure link", + f"This link uses unencrypted HTTP.\n\n{url_str}\n\nContinue anyway?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if reply == QMessageBox.StandardButton.Yes: + QDesktopServices.openUrl(QUrl(url_str)) + + # -- Drag & drop -------------------------------------------------------- + + _IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"} + def _msg_area_normal_style(self): + return f"QScrollArea {{ background-color: {c().bg_primary}; border: none; }}" + + def eventFilter(self, obj, event): + """Handle drag-and-drop on message scroll area + context menu on messages.""" + from PyQt6.QtCore import QEvent + # Context menu from any child of message container + if event.type() == QEvent.Type.ContextMenu and obj is not self._msg_scroll_area: + widget = obj + idx = self._find_msg_index_at_widget(widget) + if idx is not None: + self._show_msg_context_menu(idx, event.globalPos()) + return True + if obj is self._msg_scroll_area: + if event.type() == QEvent.Type.DragEnter: + if not self.current_conv_id: + event.ignore() + return True + if event.mimeData().hasUrls() and any(u.isLocalFile() for u in event.mimeData().urls()): + event.acceptProposedAction() + self._msg_scroll_area.setStyleSheet( + f"QScrollArea {{ border: 2px dashed {c().accent}; }}" + ) + return True + elif event.type() == QEvent.Type.DragMove: + if event.mimeData().hasUrls(): + event.acceptProposedAction() + return True + elif event.type() == QEvent.Type.DragLeave: + self._msg_scroll_area.setStyleSheet(self._msg_area_normal_style()) + return True + elif event.type() == QEvent.Type.Drop: + self._msg_scroll_area.setStyleSheet(self._msg_area_normal_style()) + if event.mimeData().hasUrls(): + for url in event.mimeData().urls(): + if url.isLocalFile(): + self._on_file_dropped(url.toLocalFile()) + event.acceptProposedAction() + return True + return super().eventFilter(obj, event) + + def _on_file_dropped(self, path: str): + """Send a dropped file as image or file attachment.""" + if not self.current_conv_id: + return + conv = None + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + conv = cv + break + if not conv: + return + import os + ext = os.path.splitext(path)[1].lower() + if ext in self._IMAGE_EXTENSIONS: + self.bridge.send_image( + self.current_conv_id, path, conv["members"], + reply_to=self.reply_to_id, + ) + else: + self.bridge.send_file( + self.current_conv_id, path, conv["members"], + reply_to=self.reply_to_id, + ) + self.reply_to_id = None + self._reply_widget.setVisible(False) + + # -- Attach menu ------------------------------------------------------- + + def _on_attach_image(self): + if not self.current_conv_id: + return + path, _ = QFileDialog.getOpenFileName( + self, "Select Image", "", + "Images (*.png *.jpg *.jpeg *.gif *.bmp *.webp);;All Files (*)", + ) + if not path: + return + conv = None + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + conv = cv + break + if not conv: + return + self.bridge.send_image( + self.current_conv_id, path, conv["members"], + reply_to=self.reply_to_id, + ) + self.reply_to_id = None + self._reply_widget.setVisible(False) + + @staticmethod + def _human_file_size(size_bytes): + if size_bytes >= 1024 * 1024: + return f"{size_bytes / (1024 * 1024):.1f} MB" + elif size_bytes >= 1024: + return f"{size_bytes / 1024:.0f} KB" + return f"{size_bytes} B" + + @staticmethod + def _file_icon(filename: str) -> str: + """Return an emoji icon based on file extension.""" + ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else "" + _icons = { + "pdf": "\U0001f4d5", # red book + "doc": "\U0001f4d8", # blue book + "docx": "\U0001f4d8", + "odt": "\U0001f4d8", + "xls": "\U0001f4ca", # bar chart + "xlsx": "\U0001f4ca", + "ods": "\U0001f4ca", + "csv": "\U0001f4ca", + "ppt": "\U0001f4d9", # orange book + "pptx": "\U0001f4d9", + "odp": "\U0001f4d9", + "zip": "\U0001f4e6", # package + "rar": "\U0001f4e6", + "7z": "\U0001f4e6", + "tar": "\U0001f4e6", + "gz": "\U0001f4e6", + "mp3": "\U0001f3b5", # music note + "wav": "\U0001f3b5", + "flac": "\U0001f3b5", + "ogg": "\U0001f3b5", + "m4a": "\U0001f3b5", + "mp4": "\U0001f3ac", # clapper board + "mkv": "\U0001f3ac", + "avi": "\U0001f3ac", + "mov": "\U0001f3ac", + "webm": "\U0001f3ac", + "py": "\U0001f40d", # snake + "js": "\U0001f4dc", # scroll + "ts": "\U0001f4dc", + "html": "\U0001f310", # globe + "css": "\U0001f3a8", # palette + "json": "\U0001f4cb", # clipboard + "xml": "\U0001f4cb", + "yaml": "\U0001f4cb", + "yml": "\U0001f4cb", + "txt": "\U0001f4c4", # page facing up + "log": "\U0001f4c4", + "md": "\U0001f4c4", + } + return _icons.get(ext, "\U0001f4ce") # default: paperclip + + def _on_attach_file(self): + if not self.current_conv_id: + return + path, _ = QFileDialog.getOpenFileName( + self, "Select File", "", + "All Files (*)", + ) + if not path: + return + conv = None + for cv in self.conversations: + if cv["conversation_id"] == self.current_conv_id: + conv = cv + break + if not conv: + return + self.bridge.send_file( + self.current_conv_id, path, conv["members"], + reply_to=self.reply_to_id, + ) + self.reply_to_id = None + self._reply_widget.setVisible(False) + + def _on_file_sent(self, ok, msg): + if not ok: + QMessageBox.warning(self, "File Error", msg) + + def _on_file_downloaded(self, data, file_info): + filename = _safe_filename(file_info.get("filename", "file"), "file") + path, _ = QFileDialog.getSaveFileName(self, "Save File", filename) + if path: + try: + with open(path, "wb") as f: + f.write(data) + QMessageBox.information(self, "Saved", f"File saved to {path}") + except Exception as e: + QMessageBox.warning(self, "Error", f"Failed to save: {e}") + + def _on_image_sent(self, ok, msg): + if not ok: + QMessageBox.warning(self, "Image Error", msg) + + def _view_image(self, msg): + image_info = msg.get("image") + if not image_info: + return + file_id = image_info.get("file_id", "") + self._pending_image_download = {"file_id": file_id, "image_info": image_info} + self.bridge.download_image(file_id, image_info) + + def _on_image_downloaded(self, file_id, data): + if not self._pending_image_download or self._pending_image_download["file_id"] != file_id: + return + image_info = self._pending_image_download["image_info"] + self._pending_image_download = None + self._show_image_dialog(data, image_info) + + def _show_image_dialog(self, image_data, image_info): + dlg = QDialog(self) + dlg.setMinimumSize(400, 300) + img_title = _safe_filename(image_info.get("filename", "Image"), "Image") + layout = _make_frameless(dlg, img_title) + + qimg = _safe_load_image(image_data) + if qimg is None: + layout.addWidget(QLabel("Failed to load image.")) + else: + pixmap = QPixmap.fromImage(qimg) + label = QLabel() + # Scale down if larger than screen + screen_size = self.screen().availableSize() + max_w = int(screen_size.width() * 0.8) + max_h = int(screen_size.height() * 0.8) + if pixmap.width() > max_w or pixmap.height() > max_h: + pixmap = pixmap.scaled(max_w, max_h, Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation) + label.setPixmap(pixmap) + label.setAlignment(Qt.AlignmentFlag.AlignCenter) + + scroll = QScrollArea() + scroll.setWidget(label) + scroll.setWidgetResizable(True) + layout.addWidget(scroll) + + btn_row = QHBoxLayout() + save_btn = QPushButton("Save") + save_btn.clicked.connect(lambda: self._save_image(image_data, image_info, dlg)) + btn_row.addWidget(save_btn) + close_btn = QPushButton("Close") + close_btn.setObjectName("secondaryBtn") + close_btn.clicked.connect(dlg.accept) + btn_row.addWidget(close_btn) + layout.addLayout(btn_row) + + if qimg is not None and not qimg.isNull(): + dlg.resize(min(pixmap.width() + 40, max_w), + min(pixmap.height() + 80, max_h)) + dlg.exec() + + def _save_image(self, image_data, image_info, dialog): + filename = _safe_filename(image_info.get("filename", "image.jpg"), "image.jpg") + path, _ = QFileDialog.getSaveFileName(dialog, "Save Image", filename) + if path: + try: + with open(path, "wb") as f: + f.write(image_data) + QMessageBox.information(dialog, "Saved", f"Image saved to {path}") + except Exception as e: + QMessageBox.warning(dialog, "Error", f"Failed to save: {e}") + + def _on_message_deleted(self, data): + message_id = data.get("message_id", "") + conv_id = data.get("conversation_id", "") + if conv_id == self.current_conv_id: + for msg in self.current_messages: + if msg.get("message_id") == message_id: + msg["deleted"] = True + msg["text"] = "" + msg["image"] = None + break + self._render_messages() + + def _on_delete_message_result(self, ok, msg): + if not ok: + QMessageBox.warning(self, "Delete Error", msg) + return + # No need to reload — _on_message_deleted() already updates in-place via notification + + def _on_authorize_result(self, ok, msg): + if ok: + QMessageBox.information(self, "Authorize Device", msg) + else: + QMessageBox.warning(self, "Authorize Device", msg) + + def _on_rotate_result(self, ok, msg): + if ok: + QMessageBox.information(self, "Rotate Keys", msg) + else: + QMessageBox.warning(self, "Rotate Keys", msg) + + def _on_password_changed(self, ok, msg): + if ok: + QMessageBox.information(self, "Change Password", msg) + else: + QMessageBox.warning(self, "Change Password", msg) + + def _on_change_username(self): + t = c() + current = "" + if self.bridge and self.bridge.client: + current = getattr(self.bridge.client, "username", "") or "" + dlg = QDialog(self) + dlg.setMinimumWidth(360) + lay = _make_frameless(dlg, "Change Username") + + label = QLabel("New Username") + label.setStyleSheet(f"color: {t.text_secondary}; font-size: 9pt;") + lay.addWidget(label) + name_input = QLineEdit() + name_input.setText(current) + name_input.setPlaceholderText("Enter new username") + name_input.setMaxLength(100) + name_input.setStyleSheet( + f"QLineEdit {{ font-size: 11pt; background-color: {t.bg_secondary}; " + f"border: 1px solid {t.border}; border-radius: 6px; padding: 8px; " + f"color: {t.text_primary}; }}" + f"QLineEdit:focus {{ border: 1px solid {t.border_focus}; }}" + ) + lay.addWidget(name_input) + + btn_row = QHBoxLayout() + cancel_btn = QPushButton("Cancel") + cancel_btn.setObjectName("secondaryBtn") + cancel_btn.clicked.connect(dlg.reject) + btn_row.addWidget(cancel_btn) + save_btn = QPushButton("Save") + save_btn.setObjectName("primaryBtn") + save_btn.clicked.connect(dlg.accept) + btn_row.addWidget(save_btn) + lay.addLayout(btn_row) + + name_input.setFocus() + if dlg.exec() == QDialog.DialogCode.Accepted: + new_name = name_input.text().strip() + if new_name and new_name != current: + self.bridge.change_username(new_name) + + def _on_username_changed(self, ok, msg): + if ok: + QMessageBox.information(self, "Change Username", msg) + self.bridge.schedule(self.bridge._do_load_conversations()) + else: + QMessageBox.warning(self, "Change Username", msg) + + def _on_reencrypt_status(self, msg): + self.reencrypt_label.setText(msg) + self.reencrypt_label.setVisible(True) + if msg.lower().startswith("re-encryption complete"): + QTimer.singleShot(4000, lambda: self.reencrypt_label.setVisible(False)) + + def closeEvent(self, event): + if self._tray_icon: + self._tray_icon.hide() + if not self._is_logout: + self.bridge.stop() + self.bridge.wait(2000) + event.accept() + + +def main(): + setup_logging() + + # Suppress Qt screen enumeration warnings on Windows (monitor sleep/wake) + os.environ.setdefault("QT_LOGGING_RULES", "qt.qpa.screen=false") + + # Windows 10+ requires AppUserModelID for system tray notifications to work + if sys.platform == "win32": + try: + import ctypes + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("com.encrypted-chat.client") + except Exception: + pass + + app = QApplication(sys.argv) + app.setStyleSheet(qss()) + + bridge = AsyncBridge() + + login_win = LoginWindow(bridge) + main_win = [None] # mutable ref + + def on_connected(): + login_win.reset() + login_win.show() + + def on_conn_error(msg): + QMessageBox.critical(None, "Connection Error", f"Cannot connect to server:\n{msg}") + sys.exit(1) + + def on_register_result(ok, msg): + if ok: + # Show verification code page inline + hint = "" + if msg and len(msg) <= 6 and msg.isdigit(): + hint = f"Code: {msg}" + elif msg: + hint = msg + login_win.show_verification_page(hint) + + def do_confirm(code): + async def _confirm(): + okc, msgc = await bridge.client.confirm_registration( + login_win.email_input.text().strip(), + login_win.username_input.text().strip(), + code.strip(), + ) + if okc: + login_win.show_success(msgc) + bridge.do_login(login_win.email_input.text().strip(), login_win.password_input.text()) + else: + login_win.show_error(msgc) + bridge.schedule(_confirm()) + + login_win._confirm_callback = do_confirm + else: + login_win.show_error(msg) + + def on_pairing_code(code): + login_win.show_success(f"Pairing code: {code}") + + def on_pairing_complete(ok, msg): + if ok: + login_win.show_success(msg) + bridge.do_login(login_win._pair_email, login_win._pair_password) + else: + login_win.show_error(msg) + + def on_login_result(ok, msg): + if ok: + login_win.show_success(msg) + login_win.hide() + tm().set_email(bridge.client.email) + app.setStyleSheet(qss()) + main_win[0] = MainWindow(bridge, on_logout=lambda: (login_win.reset(), login_win.show())) + main_win[0].show() + else: + login_win.show_error(msg) + + bridge.connected.connect(on_connected) + bridge.connection_error.connect(on_conn_error) + bridge.register_result.connect(on_register_result) + bridge.login_result.connect(on_login_result) + bridge.pairing_code.connect(on_pairing_code) + bridge.pairing_complete.connect(on_pairing_complete) + bridge.reconnected.connect(lambda: (login_win.reset(), login_win.show())) + + bridge.start() + + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() diff --git a/ios_client/EncryptedChat/App/AppState.swift b/ios_client/EncryptedChat/App/AppState.swift new file mode 100644 index 0000000..b1aac68 --- /dev/null +++ b/ios_client/EncryptedChat/App/AppState.swift @@ -0,0 +1,18 @@ +import Foundation +import SwiftUI + +enum ConnectionStatus: Equatable { + case disconnected + case connecting + case connected +} + +@Observable +final class AppState { + var isLoggedIn = false + var currentUser: User? + var connectionStatus: ConnectionStatus = .disconnected + var email: String = "" + + let chatClient = ChatClient() +} diff --git a/ios_client/EncryptedChat/App/EncryptedChatApp.swift b/ios_client/EncryptedChat/App/EncryptedChatApp.swift new file mode 100644 index 0000000..8747fde --- /dev/null +++ b/ios_client/EncryptedChat/App/EncryptedChatApp.swift @@ -0,0 +1,36 @@ +import SwiftUI + +@main +struct EncryptedChatApp: App { + @State private var appState = AppState() + @State private var authViewModel = AuthViewModel() + + var body: some Scene { + WindowGroup { + if appState.isLoggedIn { + MainTabView(appState: appState) + } else { + LoginView(viewModel: authViewModel, appState: appState) + } + } + } +} + +struct MainTabView: View { + var appState: AppState + @State private var convListVM = ConversationListVM() + + var body: some View { + TabView { + ConversationListView(appState: appState, viewModel: convListVM) + .tabItem { + Label("Chats", systemImage: "bubble.left.and.bubble.right.fill") + } + + ProfileView(appState: appState, isOwnProfile: true) + .tabItem { + Label("Profile", systemImage: "person.fill") + } + } + } +} diff --git a/ios_client/EncryptedChat/Core/ChatClient.swift b/ios_client/EncryptedChat/Core/ChatClient.swift new file mode 100644 index 0000000..40de811 --- /dev/null +++ b/ios_client/EncryptedChat/Core/ChatClient.swift @@ -0,0 +1,1644 @@ +import Foundation +import CryptoKit + +/// Notification types from the server +enum ChatNotification { + case newMessage(data: [String: Any]) + case messagesRead(data: [String: Any]) + case messageDeleted(data: [String: Any]) + case conversationCreated(data: [String: Any]) + case memberAdded(data: [String: Any]) + case memberRemoved(data: [String: Any]) + case userOnline(userId: String) + case userOffline(userId: String) + case onlineUsers(userIds: [String]) + case groupInvitation(data: [String: Any]) + case conversationRenamed(data: [String: Any]) + case sessionReset(data: [String: Any]) + case connectionStateChanged(connected: Bool) +} + +/// Main chat client — handles all server communication and crypto operations. +/// Thread-safe via Swift actor isolation. +/// Port of Python ChatClient class from chat_core.py +actor ChatClient { + + // MARK: - Connection + + let connectionManager = ConnectionManager() + private(set) var isConnected = false + private(set) var sessionToken: String? + private(set) var userId: String? + private(set) var username: String = "" + private(set) var email: String = "" + private(set) var loginRejected = false + + // MARK: - Keys + + private var rsaPrivate: SecKey? + private var rsaPublic: SecKey? + private(set) var identityPrivate: Curve25519.Signing.PrivateKey? + private(set) var identityPublic: Curve25519.Signing.PublicKey? + private var spkPrivate: Curve25519.KeyAgreement.PrivateKey? + private var spkId: String = "" + private var prevSpkPrivate: Curve25519.KeyAgreement.PrivateKey? + private var prevSpkId: String = "" + private var opkPrivates: [String: Curve25519.KeyAgreement.PrivateKey] = [:] + + // MARK: - Sessions & Sender Keys + + private var sessions: [String: DoubleRatchet] = [:] // "userId:deviceId" -> ratchet + private var senderKeyStates: [String: SenderKeyState] = [:] // convId -> own sender key + private var recvSenderKeys: [String: SenderKeyState] = [:] // "convId:senderId:deviceId" -> their key + + // MARK: - Derived Keys + + private var cacheKey: Data? // for encrypting message cache + private var localKey: Data? // for encrypting session/sender key files + + // MARK: - Multi-Device + + private(set) var deviceId: String? + + // MARK: - Caches + + private var userCache: [String: User] = [:] + private var deviceBundleCache: [String: (timestamp: Date, bundles: [DeviceBundle])] = [:] + + // MARK: - Request/Response Tracking + + private var pendingRequests: [String: CheckedContinuation<[String: Any], Error>] = [:] + private var listenerTask: Task? + + // MARK: - Notification Stream + + private var notificationContinuation: AsyncStream.Continuation? + nonisolated let notifications: AsyncStream + + // MARK: - Init + + init() { + var continuation: AsyncStream.Continuation! + notifications = AsyncStream { cont in + continuation = cont + } + notificationContinuation = continuation + } + + // MARK: - Connection + + func connect(host: String = Constants.defaultHost, port: UInt16 = Constants.defaultPort, + useTLS: Bool = false, tlsInsecure: Bool = false) async throws { + try await connectionManager.connect(host: host, port: port, useTLS: useTLS, tlsInsecure: tlsInsecure) + isConnected = true + notificationContinuation?.yield(.connectionStateChanged(connected: true)) + } + + func disconnect() async { + listenerTask?.cancel() + listenerTask = nil + await connectionManager.disconnect() + isConnected = false + // Fail all pending requests + let pending = pendingRequests + pendingRequests.removeAll() + for (_, cont) in pending { + cont.resume(throwing: NetworkError.notConnected) + } + notificationContinuation?.yield(.connectionStateChanged(connected: false)) + } + + // MARK: - Send and Receive + + /// Send a request and wait for the matching response. + func sendAndReceive(type: String, timeout: TimeInterval = 30, params: [String: Any] = [:]) async -> [String: Any] { + let requestId = ProtocolHandler.newRequestId() + + do { + let response: [String: Any] = try await withCheckedThrowingContinuation { continuation in + pendingRequests[requestId] = continuation + + Task { + do { + try await connectionManager.sendMessage(type: type, requestId: requestId, params: params) + } catch { + if let cont = pendingRequests.removeValue(forKey: requestId) { + cont.resume(throwing: error) + } + } + } + } + return response + } catch { + pendingRequests.removeValue(forKey: requestId) + return [ + "type": type, + "status": "error", + "data": ["message": error.localizedDescription] + ] + } + } + + // MARK: - Background Listener + + func startBackgroundListener() { + listenerTask = Task { [weak self] in + guard let self = self else { return } + await self.backgroundListenerLoop() + } + } + + private func backgroundListenerLoop() async { + while !Task.isCancelled { + do { + guard let msg = try await connectionManager.readMessage() else { + // EOF — connection closed + await handleDisconnect() + break + } + await routeMessage(msg) + } catch { + await handleDisconnect() + break + } + } + } + + private func handleDisconnect() { + isConnected = false + // Fail all pending futures + let pending = pendingRequests + pendingRequests.removeAll() + for (_, cont) in pending { + cont.resume(throwing: NetworkError.notConnected) + } + notificationContinuation?.yield(.connectionStateChanged(connected: false)) + } + + private func routeMessage(_ msg: [String: Any]) { + let msgType = msg["type"] as? String ?? "" + + // Notification types (no request_id expected from client) + let notificationTypes = Set([ + "new_message", "messages_read", "message_deleted", + "conversation_created", "member_added", "member_removed", + "user_online", "user_offline", "online_users", + "group_invitation", "conversation_renamed", "session_reset" + ]) + + if notificationTypes.contains(msgType) { + let data = msg["data"] as? [String: Any] ?? msg + switch msgType { + case "new_message": + notificationContinuation?.yield(.newMessage(data: data)) + case "messages_read": + notificationContinuation?.yield(.messagesRead(data: data)) + case "message_deleted": + notificationContinuation?.yield(.messageDeleted(data: data)) + case "conversation_created": + notificationContinuation?.yield(.conversationCreated(data: data)) + case "member_added": + notificationContinuation?.yield(.memberAdded(data: data)) + case "member_removed": + notificationContinuation?.yield(.memberRemoved(data: data)) + case "user_online": + if let uid = data["user_id"] as? String { + notificationContinuation?.yield(.userOnline(userId: uid)) + } + case "user_offline": + if let uid = data["user_id"] as? String { + notificationContinuation?.yield(.userOffline(userId: uid)) + } + case "online_users": + if let uids = data["user_ids"] as? [String] { + notificationContinuation?.yield(.onlineUsers(userIds: uids)) + } + case "group_invitation": + notificationContinuation?.yield(.groupInvitation(data: data)) + case "conversation_renamed": + notificationContinuation?.yield(.conversationRenamed(data: data)) + case "session_reset": + notificationContinuation?.yield(.sessionReset(data: data)) + default: + break + } + } else { + // Response to a pending request + if let requestId = msg["request_id"] as? String, + let cont = pendingRequests.removeValue(forKey: requestId) { + cont.resume(returning: msg) + } + } + } + + // MARK: - User Info Cache + + func getUserInfo(userId: String = "", userEmail: String = "") async -> User? { + if !userId.isEmpty, let cached = userCache[userId] { + return cached + } + var params: [String: Any] = [:] + if !userId.isEmpty { params["user_id"] = userId } + else if !userEmail.isEmpty { params["email"] = userEmail } + else { return nil } + + let resp = await sendAndReceive(type: "get_user_info", params: params) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data") else { return nil } + + var ikData: Data? + if let ikB64 = data["identity_key"] as? String { + ikData = try? ProtocolHandler.decodeBinary(ikB64) + } + + let user = User( + id: data.string(for: "user_id") ?? "", + username: data.string(for: "username") ?? "", + email: data.string(for: "email") ?? "", + identityKey: ikData + ) + userCache[user.id] = user + return user + } + + // MARK: - Registration + + func register(username: String, password: String, email: String) async -> (success: Bool, message: String) { + self.username = username + self.email = email + var pwdBytes = Array(password.utf8) + defer { pwdBytes.withUnsafeMutableBytes { ptr in _ = memset(ptr.baseAddress!, 0, ptr.count) } } + + let pwdData = Data(pwdBytes) + + do { + // RSA keys + let (rsaPriv, rsaPub, err) = KeyStorage.loadRSAKeys(email: email, password: pwdData) + if let rsaPriv = rsaPriv, let rsaPub = rsaPub { + self.rsaPrivate = rsaPriv + self.rsaPublic = rsaPub + } else { + let (newPriv, newPub) = try RSACrypto.generateKeypair() + try KeyStorage.saveRSAKeys(email: email, privateKey: newPriv, publicKey: newPub, password: pwdData) + self.rsaPrivate = newPriv + self.rsaPublic = newPub + } + + // Ed25519 identity keys + let (edPriv, edPub) = KeyStorage.loadIdentityKeys(email: email, password: pwdData) + if let edPriv = edPriv, let edPub = edPub { + self.identityPrivate = edPriv + self.identityPublic = edPub + } else { + let (newPriv, newPub) = Ed25519Crypto.generateKeypair() + try KeyStorage.saveIdentityKeys(email: email, privateKey: newPriv, publicKey: newPub, password: pwdData) + self.identityPrivate = newPriv + self.identityPublic = newPub + } + + self.cacheKey = CryptoUtils.deriveSelfEncryptionKey(identityPrivateRaw: identityPrivate!.rawData) + self.localKey = CryptoUtils.deriveLocalStorageKey(identityPrivateRaw: identityPrivate!.rawData) + } catch { + return (false, "Key generation failed: \(error.localizedDescription)") + } + + // Send registration request + let pubPem = String(data: try! RSACrypto.serializePublicKey(rsaPublic!), encoding: .utf8)! + let ikB64 = ProtocolHandler.encodeBinary(Ed25519Crypto.serializePublic(identityPublic!)) + + let resp = await sendAndReceive(type: "register", params: [ + "username": username, + "public_key": pubPem, + "email": email, + "identity_key": ikB64, + ]) + + guard resp.string(for: "status") == "ok" else { + let msg = resp.dict(for: "data")?.string(for: "message") ?? "Registration failed" + return (false, msg) + } + + let data = resp.dict(for: "data") ?? [:] + if let code = data.string(for: "code") { + return (true, code) + } + return (true, data.string(for: "message") ?? "Check your email for the code.") + } + + func confirmRegistration(email: String, username: String, code: String) async -> (success: Bool, message: String) { + let resp = await sendAndReceive(type: "register_confirm", params: [ + "email": email, + "code": code, + ]) + + guard resp.string(for: "status") == "ok" else { + let msg = resp.dict(for: "data")?.string(for: "message") ?? "Confirmation failed" + return (false, msg) + } + + // Upload prekeys + await generateAndUploadPrekeys() + + let uid = resp.dict(for: "data")?.string(for: "user_id") ?? "" + return (true, "Registered as '\(username)' (ID: \(uid))") + } + + // MARK: - Prekeys + + private func generateAndUploadPrekeys(keepSPK: Bool = false) async { + guard let identityPrivate = identityPrivate else { return } + + do { + let spkData: [String: Any] + + if keepSPK, let spkPriv = spkPrivate, !spkId.isEmpty { + let spkPubBytes = X25519Crypto.serializePublic(spkPriv.publicKey) + let sig = try Ed25519Crypto.sign(identityPrivate, data: spkPubBytes) + spkData = [ + "id": spkId, + "public_key": ProtocolHandler.encodeBinary(spkPubBytes), + "signature": ProtocolHandler.encodeBinary(sig), + ] + } else { + // Save current as previous (grace period) + if let spkPriv = spkPrivate, !spkId.isEmpty { + prevSpkPrivate = spkPriv + prevSpkId = spkId + try? KeyStorage.savePrevSPK(email: email, privateKey: spkPriv, spkId: spkId) + } + + let spk = try X3DH.generateSignedPrekey(identityPrivate: identityPrivate) + self.spkPrivate = spk.privateKey + self.spkId = spk.id + try? KeyStorage.saveSPK(email: email, privateKey: spk.privateKey, spkId: spk.id) + + spkData = [ + "id": spk.id, + "public_key": ProtocolHandler.encodeBinary(X25519Crypto.serializePublic(spk.publicKey)), + "signature": ProtocolHandler.encodeBinary(spk.signature), + ] + } + + // Generate OPKs + let opks = X3DH.generateOneTimePrekeys(count: Constants.opkBatchSize) + for opk in opks { + opkPrivates[opk.id] = opk.privateKey + try? KeyStorage.saveOPKPrivate(email: email, opkId: opk.id, privateKey: opk.privateKey) + } + + let otpData = opks.map { opk -> [String: Any] in + [ + "id": opk.id, + "public_key": ProtocolHandler.encodeBinary(X25519Crypto.serializePublic(opk.publicKey)), + ] + } + + _ = await sendAndReceive(type: "upload_prekeys", params: [ + "signed_prekey": spkData, + "one_time_prekeys": otpData, + ]) + } catch { + // Log error but don't fail + print("Prekey generation error: \(error)") + } + } + + private func ensurePrekeys() async { + let resp = await sendAndReceive(type: "get_prekey_count") + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data") else { return } + + let count = data.int(for: "count") ?? 0 + let spkCreatedAt = data.string(for: "spk_created_at") ?? "" + + var needNewSPK = false + if !spkCreatedAt.isEmpty { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + if let created = formatter.date(from: spkCreatedAt) ?? ISO8601DateFormatter().date(from: spkCreatedAt) { + let ageDays = Calendar.current.dateComponents([.day], from: created, to: Date()).day ?? 0 + if ageDays >= Constants.spkRotationDays { + needNewSPK = true + } + } + } + + if count < Constants.opkReplenishThreshold || needNewSPK { + await generateAndUploadPrekeys() + } + } + + // MARK: - Login + + func login(email: String, password: String) async -> (success: Bool, message: String) { + self.email = email + var pwdBytes = Array(password.utf8) + defer { pwdBytes.withUnsafeMutableBytes { ptr in _ = memset(ptr.baseAddress!, 0, ptr.count) } } + let pwdData = Data(pwdBytes) + + // Load RSA keys + let (rsaPriv, rsaPub, err) = KeyStorage.loadRSAKeys(email: email, password: pwdData) + guard let rsaPriv = rsaPriv, let rsaPub = rsaPub else { + return (false, err ?? "No local keys found. Register first.") + } + self.rsaPrivate = rsaPriv + self.rsaPublic = rsaPub + + // Load identity keys + let (edPriv, edPub) = KeyStorage.loadIdentityKeys(email: email, password: pwdData) + if let edPriv = edPriv, let edPub = edPub { + self.identityPrivate = edPriv + self.identityPublic = edPub + self.cacheKey = CryptoUtils.deriveSelfEncryptionKey(identityPrivateRaw: edPriv.rawData) + self.localKey = CryptoUtils.deriveLocalStorageKey(identityPrivateRaw: edPriv.rawData) + } + + // Load SPK + let (spkP, spkI) = KeyStorage.loadSPK(email: email) + if let spkP = spkP { + self.spkPrivate = spkP + self.spkId = spkI ?? "" + } + + // Load previous SPK (grace period) + let (prevP, prevI) = KeyStorage.loadPrevSPK(email: email) + if let prevP = prevP { + self.prevSpkPrivate = prevP + self.prevSpkId = prevI ?? "" + } + + // Load device ID + self.deviceId = KeyStorage.loadDeviceId(email: email) + + // RSA challenge-response login + let startResp = await sendAndReceive(type: "login_start", params: ["email": email]) + guard startResp.string(for: "status") == "ok", + let startData = startResp.dict(for: "data"), + let challengeB64 = startData.string(for: "challenge") else { + let msg = startResp.dict(for: "data")?.string(for: "message") ?? "Login failed" + return (false, msg) + } + + let challengeData: Data + do { + challengeData = try ProtocolHandler.decodeBinary(challengeB64) + } catch { + return (false, "Invalid challenge data") + } + + let signature: Data + do { + signature = try RSACrypto.sign(rsaPriv, data: challengeData) + } catch { + return (false, "RSA signing failed: \(error.localizedDescription)") + } + + var finishParams: [String: Any] = [ + "email": email, + "signature": ProtocolHandler.encodeBinary(signature), + "client_version": Constants.version, + ] + if let deviceId = deviceId { + finishParams["device_id"] = deviceId + } + + let finishResp = await sendAndReceive(type: "login_finish", params: finishParams) + guard finishResp.string(for: "status") == "ok", + let finishData = finishResp.dict(for: "data") else { + let msg = finishResp.dict(for: "data")?.string(for: "message") ?? "Login failed" + loginRejected = true + return (false, msg) + } + + self.userId = finishData.string(for: "user_id") + self.username = finishData.string(for: "username") ?? "" + self.sessionToken = finishData.string(for: "session_token") + + // Save device ID from server + if let newDeviceId = finishData.string(for: "device_id") { + self.deviceId = newDeviceId + try? KeyStorage.saveDeviceId(email: email, deviceId: newDeviceId) + } + + // Start background listener + startBackgroundListener() + + // Handle online_users if included + if let onlineUserIds = finishData["online_user_ids"] as? [String] { + notificationContinuation?.yield(.onlineUsers(userIds: onlineUserIds)) + } + + // Ensure prekeys in background + Task { await ensurePrekeys() } + + return (true, "Logged in as \(username)") + } + + // MARK: - Reconnect + + func reconnect() async -> Bool { + guard rsaPrivate != nil else { return false } + + await disconnect() + + do { + try await connect() + } catch { + return false + } + + // RSA challenge-response with in-memory keys + let startResp = await sendAndReceive(type: "login_start", params: ["email": email]) + guard startResp.string(for: "status") == "ok", + let startData = startResp.dict(for: "data"), + let challengeB64 = startData.string(for: "challenge"), + let challengeData = try? ProtocolHandler.decodeBinary(challengeB64), + let signature = try? RSACrypto.sign(rsaPrivate!, data: challengeData) else { + return false + } + + var finishParams: [String: Any] = [ + "email": email, + "signature": ProtocolHandler.encodeBinary(signature), + "client_version": Constants.version, + ] + if let deviceId = deviceId { + finishParams["device_id"] = deviceId + } + + let finishResp = await sendAndReceive(type: "login_finish", params: finishParams) + guard finishResp.string(for: "status") == "ok" else { return false } + + startBackgroundListener() + return true + } + + // MARK: - Device Bundles + + private func getDeviceBundles(userId: String) async throws -> [DeviceBundle] { + // Check cache (5-min TTL) + if let cached = deviceBundleCache[userId], + Date().timeIntervalSince(cached.timestamp) < Constants.deviceBundleCacheTTL { + return cached.bundles + } + + let resp = await sendAndReceive(type: "get_key_bundle", params: ["user_id": userId]) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data") else { + throw ChatError.operationFailed("Failed to get key bundle") + } + + var bundles: [DeviceBundle] = [] + + // Per-device bundles (new format) + if let deviceBundlesRaw = data["device_bundles"] as? [[String: Any]] { + for bundleDict in deviceBundlesRaw { + if let bundle = try? DeviceBundle.fromDict(bundleDict) { + bundles.append(bundle) + } + } + } + // Legacy single bundle + else if let ikHex = data["identity_key"] as? String { + let bundle = try DeviceBundle.fromDict(data) + bundles.append(bundle) + } + + deviceBundleCache[userId] = (Date(), bundles) + return bundles + } + + // MARK: - Session Management + + private func getOrCreateSession( + peerUserId: String, + peerDeviceId: String, + bundle: DeviceBundle + ) async throws -> DoubleRatchet { + let sessionKey = "\(peerUserId):\(peerDeviceId)" + + // Check memory + if let session = sessions[sessionKey] { + return session + } + + // Check disk + if let session = KeyStorage.loadSession( + email: email, + peerUserId: peerUserId, + localKey: localKey, + peerDeviceId: peerDeviceId + ) { + sessions[sessionKey] = session + return session + } + + // Create new via X3DH + let remoteIkEd = try Ed25519Crypto.loadPublic(bundle.identityKey) + let spkRemote = try X25519Crypto.loadPublic(bundle.spk) + var opkRemote: Curve25519.KeyAgreement.PublicKey? + if let opkData = bundle.opk { + opkRemote = try X25519Crypto.loadPublic(opkData) + } + + let (sharedSecret, ekPriv, ekPub) = try X3DH.initiate( + ikPrivateEd: identityPrivate!, + ikPublicRemoteEd: remoteIkEd, + spkRemote: spkRemote, + spkSignature: bundle.spkSignature, + opkRemote: opkRemote + ) + + let ratchet = try DoubleRatchet.initAlice(sharedSecret: sharedSecret, bobSpkPub: spkRemote) + + // Build X3DH header for first message + var x3dhHeader: [String: Any] = [ + "ik": Ed25519Crypto.serializePublic(identityPublic!).hexString, + "ek": X25519Crypto.serializePublic(ekPub).hexString, + "spk_id": bundle.spkId, + ] + if let opkId = bundle.opkId { + x3dhHeader["opk_id"] = opkId + } + ratchet.x3dhHeader = x3dhHeader + + sessions[sessionKey] = ratchet + try? KeyStorage.saveSession(email: email, peerUserId: peerUserId, ratchet: ratchet, localKey: localKey, peerDeviceId: peerDeviceId) + + return ratchet + } + + // MARK: - X3DH Response (Bob Side) + + private func processX3DHHeader( + senderId: String, + x3dhHeader: [String: Any], + senderDeviceId: String, + spkOverride: Curve25519.KeyAgreement.PrivateKey? = nil + ) throws -> DoubleRatchet { + guard let ikHex = x3dhHeader["ik"] as? String, + let ikData = Data(hexString: ikHex), + let ekHex = x3dhHeader["ek"] as? String, + let ekData = Data(hexString: ekHex), + let spkIdStr = x3dhHeader["spk_id"] as? String else { + throw CryptoError.x3dhFailed("Invalid X3DH header") + } + + let remoteIkEd = try Ed25519Crypto.loadPublic(ikData) + let ekRemote = try X25519Crypto.loadPublic(ekData) + + // Determine which SPK to use + let spkToUse: Curve25519.KeyAgreement.PrivateKey + if let override = spkOverride { + spkToUse = override + } else if spkIdStr == spkId, let spk = spkPrivate { + spkToUse = spk + } else if spkIdStr == prevSpkId, let prevSpk = prevSpkPrivate { + spkToUse = prevSpk + } else { + throw CryptoError.x3dhFailed("SPK \(spkIdStr) not found") + } + + // OPK + var opkPriv: Curve25519.KeyAgreement.PrivateKey? + if let opkIdStr = x3dhHeader["opk_id"] as? String { + opkPriv = opkPrivates[opkIdStr] ?? KeyStorage.loadOPKPrivate(email: email, opkId: opkIdStr) + if opkPriv != nil { + opkPrivates.removeValue(forKey: opkIdStr) + KeyStorage.deleteOPKPrivate(email: email, opkId: opkIdStr) + } + } + + let sharedSecret = try X3DH.respond( + ikPrivateEd: identityPrivate!, + spkPrivate: spkToUse, + ikRemoteEd: remoteIkEd, + ekRemote: ekRemote, + opkPrivate: opkPriv + ) + + let ratchet = DoubleRatchet.initBob( + sharedSecret: sharedSecret, + spkPair: (spkToUse, spkToUse.publicKey) + ) + + let sessionKey = "\(senderId):\(senderDeviceId)" + sessions[sessionKey] = ratchet + try? KeyStorage.saveSession(email: email, peerUserId: senderId, ratchet: ratchet, localKey: localKey, peerDeviceId: senderDeviceId) + + return ratchet + } + + // MARK: - Send Message + + func sendMessage(convId: String, text: String, members: [ConversationMember], replyTo: String? = nil) async -> (success: Bool, message: String) { + let isGroup = members.count > 2 + + if isGroup { + return await sendGroupMessage(convId: convId, text: text, members: members, replyTo: replyTo) + } else { + return await sendDM(convId: convId, text: text, members: members, replyTo: replyTo) + } + } + + // MARK: - Send DM + + private func sendDM(convId: String, text: String, members: [ConversationMember], replyTo: String? = nil) async -> (success: Bool, message: String) { + guard let identityPrivate = identityPrivate else { + return (false, "Identity key not loaded") + } + + let plaintext = Data(text.utf8) + var payload: [String: Any] = ["text": text] + if let replyTo = replyTo { + payload["reply_to"] = replyTo + } + + var recipients: [[String: Any]] = [] + + // Encrypt for each member's devices + for member in members where member.userId != userId { + do { + let bundles = try await getDeviceBundles(userId: member.userId) + for bundle in bundles { + let ratchet = try await getOrCreateSession( + peerUserId: member.userId, + peerDeviceId: bundle.deviceId, + bundle: bundle + ) + + // Consume X3DH header if present (first message only) + let x3dhHeader = ratchet.x3dhHeader + ratchet.x3dhHeader = nil + + let encrypted = try ratchet.encrypt(plaintext) + try? KeyStorage.saveSession(email: email, peerUserId: member.userId, ratchet: ratchet, localKey: localKey, peerDeviceId: bundle.deviceId) + + var recipientEntry: [String: Any] = [ + "user_id": member.userId, + "device_id": bundle.deviceId, + "ciphertext": ProtocolHandler.encodeBinary(encrypted.ciphertext), + "nonce": ProtocolHandler.encodeBinary(encrypted.nonce), + "ratchet_header": encrypted.header, + ] + if let x3dh = x3dhHeader { + recipientEntry["x3dh_header"] = x3dh + } + recipients.append(recipientEntry) + } + } catch { + return (false, "Encryption failed for \(member.username): \(error.localizedDescription)") + } + } + + // Self-encrypted copy + let selfKey = CryptoUtils.deriveSelfEncryptionKey(identityPrivateRaw: identityPrivate.rawData) + if let (_, nonce, ct, tag) = try? CryptoUtils.aesEncrypt(plaintext, key: selfKey) { + let selfCiphertext = ct + tag + let dummyHeader: [String: Any] = [ + "dh_pub": String(repeating: "00", count: 32), + "n": 0, + "pn": 0, + ] + recipients.append([ + "user_id": userId!, + "device_id": Constants.selfDeviceId, + "ciphertext": ProtocolHandler.encodeBinary(selfCiphertext), + "nonce": ProtocolHandler.encodeBinary(nonce), + "ratchet_header": dummyHeader, + ]) + } + + // Build ratchet header for message table (use first recipient's or dummy) + let ratchetHeader: [String: Any] + if let first = recipients.first { + ratchetHeader = first["ratchet_header"] as? [String: Any] ?? [:] + } else { + ratchetHeader = ["dh_pub": String(repeating: "00", count: 32), "n": 0, "pn": 0] + } + + var params: [String: Any] = [ + "conversation_id": convId, + "ratchet_header": ratchetHeader, + "recipients": recipients, + ] + if let replyTo = replyTo { + params["reply_to"] = replyTo + } + + let resp = await sendAndReceive(type: "send_message", params: params) + guard resp.string(for: "status") == "ok" else { + let msg = resp.dict(for: "data")?.string(for: "message") ?? "Send failed" + return (false, msg) + } + + // Cache the sent message + if let msgData = resp.dict(for: "data"), let messageId = msgData.string(for: "message_id") { + var cacheEntry = payload + cacheEntry["sender_id"] = userId + cacheEntry["sender_username"] = username + cacheEntry["created_at"] = ISO8601DateFormatter().string(from: Date()) + try? MessageCache.save(email: email, convId: convId, messages: [cacheEntry], cacheKey: cacheKey) + } + + return (true, "Message sent") + } + + // MARK: - Send Group Message + + private func sendGroupMessage(convId: String, text: String, members: [ConversationMember], replyTo: String? = nil) async -> (success: Bool, message: String) { + guard let identityPrivate = identityPrivate, let userId = userId, let deviceId = deviceId else { + return (false, "Not properly logged in") + } + + // Get or create sender key for this group + var senderKeyState = senderKeyStates[convId] + if senderKeyState == nil { + senderKeyState = KeyStorage.loadSenderKeyState(email: email, convId: convId, localKey: localKey) + } + + var needDistribute = false + if senderKeyState == nil { + senderKeyState = SenderKeyState() + needDistribute = true + } + + senderKeyStates[convId] = senderKeyState + + // Distribute sender key if new + if needDistribute { + await distributeSenderKey(convId: convId, members: members) + } + + // Encrypt with sender key + let plaintext = Data(text.utf8) + do { + let encrypted = try senderKeyState!.encrypt(plaintext) + try? KeyStorage.saveSenderKeyState(email: email, convId: convId, state: senderKeyState!, localKey: localKey) + + // Build recipients (same ciphertext for all) + var recipients: [[String: Any]] = [] + for member in members where member.userId != userId { + recipients.append([ + "user_id": member.userId, + "device_id": Constants.selfDeviceId, // group messages use sentinel + "ciphertext": ProtocolHandler.encodeBinary(encrypted.ciphertext), + "nonce": ProtocolHandler.encodeBinary(encrypted.nonce), + ]) + } + + // Self copy + let selfKey = CryptoUtils.deriveSelfEncryptionKey(identityPrivateRaw: identityPrivate.rawData) + if let (_, nonce, ct, tag) = try? CryptoUtils.aesEncrypt(plaintext, key: selfKey) { + recipients.append([ + "user_id": userId, + "device_id": Constants.selfDeviceId, + "ciphertext": ProtocolHandler.encodeBinary(ct + tag), + "nonce": ProtocolHandler.encodeBinary(nonce), + ]) + } + + let dummyHeader: [String: Any] = [ + "dh_pub": String(repeating: "00", count: 32), + "n": 0, + "pn": 0, + ] + + var params: [String: Any] = [ + "conversation_id": convId, + "ratchet_header": dummyHeader, + "recipients": recipients, + "sender_chain_id": ProtocolHandler.encodeBinary(encrypted.ciphertext.prefix(0)), // placeholder + ] + + // Include sender key metadata for group routing + params["sender_chain_id"] = encrypted.chainIdHex + params["sender_chain_n"] = encrypted.n + + if let replyTo = replyTo { + params["reply_to"] = replyTo + } + + let resp = await sendAndReceive(type: "send_message", params: params) + guard resp.string(for: "status") == "ok" else { + let msg = resp.dict(for: "data")?.string(for: "message") ?? "Send failed" + return (false, msg) + } + + return (true, "Message sent") + } catch { + return (false, "Encryption failed: \(error.localizedDescription)") + } + } + + // MARK: - Distribute Sender Key + + private func distributeSenderKey(convId: String, members: [ConversationMember]) async { + guard let senderKeyState = senderKeyStates[convId], + let userId = userId, + let deviceId = deviceId else { return } + + let exportedKey = senderKeyState.exportKey() + + for member in members where member.userId != userId { + do { + let bundles = try await getDeviceBundles(userId: member.userId) + for bundle in bundles { + let ratchet = try await getOrCreateSession( + peerUserId: member.userId, + peerDeviceId: bundle.deviceId, + bundle: bundle + ) + + let x3dhHeader = ratchet.x3dhHeader + ratchet.x3dhHeader = nil + + // Payload includes sender key + metadata + let controlPayload: [String: Any] = [ + "_sender_key": [ + "conv_id": convId, + "key": ProtocolHandler.encodeBinary(exportedKey), + "sender_device_id": deviceId, + ] + ] + let controlData = try JSONSerialization.data(withJSONObject: controlPayload) + let encrypted = try ratchet.encrypt(controlData) + try? KeyStorage.saveSession(email: email, peerUserId: member.userId, ratchet: ratchet, localKey: localKey, peerDeviceId: bundle.deviceId) + + var recipientEntry: [String: Any] = [ + "user_id": member.userId, + "device_id": bundle.deviceId, + "ciphertext": ProtocolHandler.encodeBinary(encrypted.ciphertext), + "nonce": ProtocolHandler.encodeBinary(encrypted.nonce), + "ratchet_header": encrypted.header, + ] + if let x3dh = x3dhHeader { + recipientEntry["x3dh_header"] = x3dh + } + + let dummyHeader: [String: Any] = [ + "dh_pub": String(repeating: "00", count: 32), + "n": 0, + "pn": 0, + ] + + _ = await sendAndReceive(type: "send_message", params: [ + "conversation_id": convId, + "ratchet_header": dummyHeader, + "recipients": [recipientEntry], + ]) + } + } catch { + print("Failed to distribute sender key to \(member.userId): \(error)") + } + } + } + + // MARK: - Decrypt + + func decryptDMRecipientData( + senderData: [String: Any], + senderId: String, + senderDeviceId: String + ) -> Data? { + guard let ctB64 = senderData["ciphertext"] as? String, + let nonceB64 = senderData["nonce"] as? String, + let ct = try? ProtocolHandler.decodeBinary(ctB64), + let nonce = try? ProtocolHandler.decodeBinary(nonceB64) else { + return nil + } + + // Self-encrypted copy + if senderDeviceId == Constants.selfDeviceId || senderId == userId { + if let cacheKey = cacheKey { + // ct = ciphertext + tag(16) + guard ct.count >= 16 else { return nil } + let ciphertext = ct.prefix(ct.count - 16) + let tag = ct.suffix(16) + return try? CryptoUtils.aesDecrypt(key: cacheKey, nonce: nonce, ciphertext: Data(ciphertext), tag: Data(tag)) + } + return nil + } + + // Regular DM decryption + let headerDict = senderData["ratchet_header"] as? [String: Any] + let x3dhHeader = senderData["x3dh_header"] as? [String: Any] + + let sessionKey = "\(senderId):\(senderDeviceId)" + var ratchet = sessions[sessionKey] + ?? KeyStorage.loadSession(email: email, peerUserId: senderId, localKey: localKey, peerDeviceId: senderDeviceId) + + // Handle X3DH header (new session) + if let x3dh = x3dhHeader { + do { + ratchet = try processX3DHHeader( + senderId: senderId, + x3dhHeader: x3dh, + senderDeviceId: senderDeviceId + ) + } catch { + // Try with previous SPK (grace period) + if let prevSpk = prevSpkPrivate { + ratchet = try? processX3DHHeader( + senderId: senderId, + x3dhHeader: x3dh, + senderDeviceId: senderDeviceId, + spkOverride: prevSpk + ) + } + if ratchet == nil { return nil } + } + } + + guard let ratchet = ratchet, let header = headerDict else { return nil } + + do { + let plaintext = try ratchet.decrypt(headerDict: header, ciphertext: ct, nonce: nonce) + sessions[sessionKey] = ratchet + try? KeyStorage.saveSession(email: email, peerUserId: senderId, ratchet: ratchet, localKey: localKey, peerDeviceId: senderDeviceId) + + // Check for sender key distribution (control message) + if let jsonObj = try? JSONSerialization.jsonObject(with: plaintext) as? [String: Any], + let senderKeyInfo = jsonObj["_sender_key"] as? [String: Any] { + handleSenderKeyDistribution(senderKeyInfo, senderId: senderId) + return nil // Control message + } + + return plaintext + } catch { + return nil + } + } + + private func handleSenderKeyDistribution(_ info: [String: Any], senderId: String) { + guard let convId = info["conv_id"] as? String, + let keyB64 = info["key"] as? String, + let keyData = try? ProtocolHandler.decodeBinary(keyB64) else { return } + + let senderDeviceId = info["sender_device_id"] as? String ?? Constants.selfDeviceId + + do { + let senderKey = try SenderKeyState.fromKey(keyData) + let stateKey = "\(convId):\(senderId):\(senderDeviceId)" + recvSenderKeys[stateKey] = senderKey + try? KeyStorage.saveRecvSenderKey( + email: email, + convId: convId, + senderId: senderId, + senderDeviceId: senderDeviceId, + state: senderKey, + localKey: localKey + ) + } catch { + print("Failed to import sender key: \(error)") + } + } + + func decryptNotification(_ data: [String: Any]) -> Message? { + guard let senderId = data.string(for: "sender_id"), + let conversationId = data.string(for: "conversation_id"), + let messageId = data.string(for: "message_id") else { + return nil + } + + let senderDeviceId = data.string(for: "sender_device_id") ?? Constants.selfDeviceId + + // Find our device's entry + var recipientData: [String: Any]? + if let deviceEntries = data["device_entries"] as? [[String: Any]] { + recipientData = deviceEntries.first(where: { + ($0["device_id"] as? String) == deviceId || ($0["device_id"] as? String) == Constants.selfDeviceId + }) + } + // Fallback: use data directly if it has ciphertext + if recipientData == nil, data["ciphertext"] != nil { + recipientData = data + } + + guard let recipientData = recipientData else { return nil } + + // Try DM decryption + if let plaintext = decryptDMRecipientData( + senderData: recipientData, + senderId: senderId, + senderDeviceId: senderDeviceId + ) { + let text = String(data: plaintext, encoding: .utf8) + + // Parse JSON payload + var messageText = text + var replyTo: String? + var file: FileInfo? + if let jsonObj = try? JSONSerialization.jsonObject(with: plaintext) as? [String: Any] { + messageText = jsonObj["text"] as? String + replyTo = jsonObj["reply_to"] as? String + if let fileDict = jsonObj["file"] as? [String: Any] { + file = FileInfo( + fileId: fileDict["file_id"] as? String ?? "", + aesKey: fileDict["aes_key"] as? String ?? "", + iv: fileDict["iv"] as? String ?? "", + filename: fileDict["filename"] as? String ?? "", + size: fileDict["size"] as? Int ?? 0, + mimeType: fileDict["mime_type"] as? String ?? "" + ) + } + } + + let createdAt = data.string(for: "created_at").flatMap { ISO8601DateFormatter().date(from: $0) } ?? Date() + let senderUsername = data.string(for: "sender_username") ?? userCache[senderId]?.username ?? "Unknown" + + return Message( + id: messageId, + conversationId: conversationId, + senderId: senderId, + senderUsername: senderUsername, + createdAt: createdAt, + text: messageText, + replyTo: replyTo, + imageFileId: data.string(for: "image_file_id"), + file: file, + isDeleted: false, + readBy: [] + ) + } + + return nil + } + + // MARK: - Conversations + + func listConversations() async -> [Conversation] { + let resp = await sendAndReceive(type: "list_conversations") + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data"), + let convList = data["conversations"] as? [[String: Any]] else { + return [] + } + + return convList.compactMap { dict -> Conversation? in + guard let id = dict.string(for: "id") else { return nil } + + let membersRaw = dict["members"] as? [[String: Any]] ?? [] + let members = membersRaw.compactMap { m -> ConversationMember? in + guard let uid = m.string(for: "user_id"), + let uname = m.string(for: "username"), + let uemail = m.string(for: "email") else { return nil } + return ConversationMember(userId: uid, username: uname, email: uemail) + } + + let unreadCount = dict.int(for: "unread_count") ?? 0 + + return Conversation( + id: id, + name: dict.string(for: "name"), + members: members, + createdBy: dict.string(for: "created_by"), + avatarFile: dict.string(for: "avatar_file"), + unreadCount: unreadCount, + isFavorite: false, + lastMessageTime: nil + ) + } + } + + func createConversation(emails: [String], name: String? = nil) async -> (convId: String?, message: String) { + var params: [String: Any] = ["emails": emails] + if let name = name { + params["name"] = name + } + + let resp = await sendAndReceive(type: "create_conversation", params: params) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data"), + let convId = data.string(for: "conversation_id") else { + let msg = resp.dict(for: "data")?.string(for: "message") ?? "Failed to create conversation" + return (nil, msg) + } + + return (convId, "Conversation created") + } + + func findConversation(email: String) async -> String? { + let resp = await sendAndReceive(type: "find_conversation", params: ["email": email]) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data") else { return nil } + return data.string(for: "conversation_id") + } + + // MARK: - Messages + + func getMessages(convId: String, limit: Int = 50, offset: Int = 0) async -> [Message] { + let resp = await sendAndReceive(type: "get_messages", params: [ + "conversation_id": convId, + "limit": limit, + "offset": offset, + ]) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data"), + let messagesRaw = data["messages"] as? [[String: Any]] else { + return [] + } + + var messages: [Message] = [] + for msgDict in messagesRaw { + guard let msgId = msgDict.string(for: "id"), + let senderId = msgDict.string(for: "sender_id") else { continue } + + let senderDeviceId = msgDict.string(for: "sender_device_id") ?? Constants.selfDeviceId + let isDeleted = msgDict["deleted_at"] != nil && !(msgDict["deleted_at"] is NSNull) + + if isDeleted { + let createdAt = msgDict.string(for: "created_at").flatMap { ISO8601DateFormatter().date(from: $0) } ?? Date() + messages.append(Message( + id: msgId, conversationId: convId, senderId: senderId, + senderUsername: msgDict.string(for: "sender_username") ?? "", + createdAt: createdAt, text: nil, isDeleted: true, readBy: [] + )) + continue + } + + // Try to decrypt + if let plaintext = decryptDMRecipientData( + senderData: msgDict, + senderId: senderId, + senderDeviceId: senderDeviceId + ) { + let text = String(data: plaintext, encoding: .utf8) + var messageText = text + var replyTo: String? + var file: FileInfo? + + if let jsonObj = try? JSONSerialization.jsonObject(with: plaintext) as? [String: Any] { + messageText = jsonObj["text"] as? String + replyTo = jsonObj["reply_to"] as? String + if let fileDict = jsonObj["file"] as? [String: Any] { + file = FileInfo( + fileId: fileDict["file_id"] as? String ?? "", + aesKey: fileDict["aes_key"] as? String ?? "", + iv: fileDict["iv"] as? String ?? "", + filename: fileDict["filename"] as? String ?? "", + size: fileDict["size"] as? Int ?? 0, + mimeType: fileDict["mime_type"] as? String ?? "" + ) + } + } + + if messageText == nil && file == nil { continue } // Control message + + let createdAt = msgDict.string(for: "created_at").flatMap { ISO8601DateFormatter().date(from: $0) } ?? Date() + messages.append(Message( + id: msgId, conversationId: convId, senderId: senderId, + senderUsername: msgDict.string(for: "sender_username") ?? "", + createdAt: createdAt, text: messageText, replyTo: replyTo, + imageFileId: msgDict.string(for: "image_file_id"), file: file, + isDeleted: false, readBy: [] + )) + } + } + + return messages + } + + func markRead(convId: String, messageIds: [String]) async { + _ = await sendAndReceive(type: "mark_read", params: [ + "conversation_id": convId, + "message_ids": messageIds, + ]) + } + + func deleteMessage(messageId: String, convId: String) async -> Bool { + let resp = await sendAndReceive(type: "delete_message", params: [ + "message_id": messageId, + "conversation_id": convId, + ]) + return resp.string(for: "status") == "ok" + } + + // MARK: - Group Operations + + func addMember(convId: String, email: String) async -> (success: Bool, message: String) { + let resp = await sendAndReceive(type: "add_member", params: [ + "conversation_id": convId, + "email": email, + ]) + let msg = resp.dict(for: "data")?.string(for: "message") ?? "" + return (resp.string(for: "status") == "ok", msg) + } + + func removeMember(convId: String, userId: String) async -> (success: Bool, message: String) { + let resp = await sendAndReceive(type: "remove_member", params: [ + "conversation_id": convId, + "user_id": userId, + ]) + let msg = resp.dict(for: "data")?.string(for: "message") ?? "" + return (resp.string(for: "status") == "ok", msg) + } + + func leaveGroup(convId: String) async -> (success: Bool, message: String) { + let resp = await sendAndReceive(type: "leave_group", params: [ + "conversation_id": convId, + ]) + if resp.string(for: "status") == "ok" { + // Clean up local sender keys + senderKeyStates.removeValue(forKey: convId) + KeyStorage.deleteSenderKeyState(email: email, convId: convId) + KeyStorage.deleteRecvSenderKeys(email: email, convId: convId) + return (true, "Left group") + } + let msg = resp.dict(for: "data")?.string(for: "message") ?? "Failed" + return (false, msg) + } + + func renameConversation(convId: String, name: String) async -> (success: Bool, message: String) { + let resp = await sendAndReceive(type: "rename_conversation", params: [ + "conversation_id": convId, + "name": name, + ]) + let msg = resp.dict(for: "data")?.string(for: "message") ?? "" + return (resp.string(for: "status") == "ok", msg) + } + + func deleteConversation(convId: String) async -> (success: Bool, message: String) { + let resp = await sendAndReceive(type: "delete_conversation", params: [ + "conversation_id": convId, + ]) + if resp.string(for: "status") == "ok" { + senderKeyStates.removeValue(forKey: convId) + KeyStorage.deleteSenderKeyState(email: email, convId: convId) + KeyStorage.deleteRecvSenderKeys(email: email, convId: convId) + return (true, "Deleted") + } + let msg = resp.dict(for: "data")?.string(for: "message") ?? "Failed" + return (false, msg) + } + + // MARK: - Invitations + + func acceptInvitation(convId: String) async -> (success: Bool, message: String) { + let resp = await sendAndReceive(type: "accept_invitation", params: [ + "conversation_id": convId, + ]) + let msg = resp.dict(for: "data")?.string(for: "message") ?? "" + return (resp.string(for: "status") == "ok", msg) + } + + func declineInvitation(convId: String) async -> (success: Bool, message: String) { + let resp = await sendAndReceive(type: "decline_invitation", params: [ + "conversation_id": convId, + ]) + let msg = resp.dict(for: "data")?.string(for: "message") ?? "" + return (resp.string(for: "status") == "ok", msg) + } + + func listInvitations() async -> [Invitation] { + let resp = await sendAndReceive(type: "list_invitations") + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data"), + let invList = data["invitations"] as? [[String: Any]] else { + return [] + } + + return invList.compactMap { dict -> Invitation? in + guard let convId = dict.string(for: "conversation_id") else { return nil } + return Invitation( + id: dict.string(for: "id") ?? convId, + conversationId: convId, + conversationName: dict.string(for: "conversation_name") ?? "Group", + invitedBy: dict.string(for: "invited_by") ?? "", + invitedByUsername: dict.string(for: "invited_by_username") ?? "" + ) + } + } + + // MARK: - Profile + + func getProfile(userId: String? = nil) async -> UserProfile? { + var params: [String: Any] = [:] + if let userId = userId { + params["user_id"] = userId + } + let resp = await sendAndReceive(type: "get_profile", params: params) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data") else { return nil } + + return UserProfile( + userId: data.string(for: "user_id") ?? userId ?? self.userId ?? "", + username: data.string(for: "username"), + email: data.string(for: "email"), + phone: data.string(for: "phone"), + phoneVisible: data.bool(for: "phone_visible") ?? false, + location: data.string(for: "location"), + locationVisible: data.bool(for: "location_visible") ?? false, + avatarFile: data.string(for: "avatar_file") + ) + } + + func updateProfile(phone: String? = nil, phoneVisible: Bool? = nil, + location: String? = nil, locationVisible: Bool? = nil) async -> Bool { + var params: [String: Any] = [:] + if let phone = phone { params["phone"] = phone } + if let phoneVisible = phoneVisible { params["phone_visible"] = phoneVisible } + if let location = location { params["location"] = location } + if let locationVisible = locationVisible { params["location_visible"] = locationVisible } + + let resp = await sendAndReceive(type: "update_profile", params: params) + return resp.string(for: "status") == "ok" + } + + func updateAvatar(imageData: Data) async -> Bool { + let resp = await sendAndReceive(type: "update_avatar", params: [ + "avatar_data": ProtocolHandler.encodeBinary(imageData), + ]) + return resp.string(for: "status") == "ok" + } + + func getAvatar(userId: String) async -> Data? { + let resp = await sendAndReceive(type: "get_avatar", params: ["user_id": userId]) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data"), + let avatarB64 = data.string(for: "avatar_data"), + let avatarData = try? ProtocolHandler.decodeBinary(avatarB64) else { + return nil + } + return avatarData + } + + // MARK: - Group Avatar + + func updateGroupAvatar(convId: String, imageData: Data) async -> Bool { + let resp = await sendAndReceive(type: "update_group_avatar", params: [ + "conversation_id": convId, + "avatar_data": ProtocolHandler.encodeBinary(imageData), + ]) + return resp.string(for: "status") == "ok" + } + + func getGroupAvatar(convId: String) async -> Data? { + let resp = await sendAndReceive(type: "get_group_avatar", params: ["conversation_id": convId]) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data"), + let avatarB64 = data.string(for: "avatar_data"), + let avatarData = try? ProtocolHandler.decodeBinary(avatarB64) else { + return nil + } + return avatarData + } + + // MARK: - File Sharing + + func sendFile(convId: String, fileData: Data, filename: String, mimeType: String, + members: [ConversationMember], replyTo: String? = nil) async -> (success: Bool, message: String) { + // Encrypt file with AES-GCM + guard let (aesKey, nonce, ct, tag) = try? CryptoUtils.aesEncrypt(fileData) else { + return (false, "File encryption failed") + } + + let encryptedData = ct + tag + let fileType = mimeType.hasPrefix("image/") ? "image" : "file" + + // Start upload + let startResp = await sendAndReceive(type: "upload_image_start", params: [ + "conversation_id": convId, + "file_size": encryptedData.count, + "file_type": fileType, + ]) + guard startResp.string(for: "status") == "ok", + let startData = startResp.dict(for: "data"), + let fileId = startData.string(for: "file_id") else { + let msg = startResp.dict(for: "data")?.string(for: "message") ?? "Upload start failed" + return (false, msg) + } + + // Upload chunks + var offset = 0 + while offset < encryptedData.count { + let end = min(offset + Constants.imageChunkSize, encryptedData.count) + let chunk = encryptedData[offset.. Data? { + var allData = Data() + var offset = 0 + + while true { + let resp = await sendAndReceive(type: "download_image", params: [ + "file_id": fileId, + "offset": offset, + ]) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data"), + let chunkB64 = data.string(for: "chunk_data"), + let chunk = try? ProtocolHandler.decodeBinary(chunkB64) else { + break + } + + if chunk.isEmpty { break } + allData.append(chunk) + offset += chunk.count + + if data.bool(for: "complete") == true { break } + } + + guard !allData.isEmpty else { return nil } + + // Decrypt: allData = ciphertext + tag(16) + guard allData.count >= 16 else { return nil } + let ct = allData.prefix(allData.count - 16) + let tag = allData.suffix(16) + return try? CryptoUtils.aesDecrypt(key: aesKey, nonce: iv, ciphertext: Data(ct), tag: Data(tag)) + } + + // MARK: - Devices + + func listDevices() async -> [[String: Any]] { + let resp = await sendAndReceive(type: "list_devices") + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data") else { return [] } + return data["devices"] as? [[String: Any]] ?? [] + } + + func removeDevice(deviceIdToRemove: String) async -> Bool { + let resp = await sendAndReceive(type: "remove_device", params: [ + "device_id": deviceIdToRemove, + ]) + return resp.string(for: "status") == "ok" + } + + // MARK: - Session Reset + + func resetSession(peerUserId: String, peerDeviceId: String? = nil) async { + if let peerDeviceId = peerDeviceId { + let sessionKey = "\(peerUserId):\(peerDeviceId)" + sessions.removeValue(forKey: sessionKey) + KeyStorage.deleteSession(email: email, peerUserId: peerUserId, peerDeviceId: peerDeviceId) + } else { + // Delete all sessions for this user + for key in sessions.keys where key.hasPrefix(peerUserId) { + sessions.removeValue(forKey: key) + } + KeyStorage.deleteSession(email: email, peerUserId: peerUserId) + } + + _ = await sendAndReceive(type: "session_reset", params: [ + "peer_user_id": peerUserId, + "peer_device_id": peerDeviceId ?? "", + ]) + } + + func handleSessionResetNotification(fromUserId: String, fromDeviceId: String?) { + if let deviceId = fromDeviceId { + let sessionKey = "\(fromUserId):\(deviceId)" + sessions.removeValue(forKey: sessionKey) + KeyStorage.deleteSession(email: email, peerUserId: fromUserId, peerDeviceId: deviceId) + } else { + for key in sessions.keys where key.hasPrefix(fromUserId) { + sessions.removeValue(forKey: key) + } + KeyStorage.deleteSession(email: email, peerUserId: fromUserId) + } + } + + // MARK: - Search + + func searchMessages(convId: String, query: String) -> [[String: Any]] { + MessageCache.search(email: email, convId: convId, query: query, cacheKey: cacheKey) + } +} diff --git a/ios_client/EncryptedChat/Core/KeyStorage.swift b/ios_client/EncryptedChat/Core/KeyStorage.swift new file mode 100644 index 0000000..e310090 --- /dev/null +++ b/ios_client/EncryptedChat/Core/KeyStorage.swift @@ -0,0 +1,397 @@ +import Foundation +import CryptoKit + +/// Local file storage for keys, sessions, and sender keys. +/// Matches Python: chat_core.py key storage functions. +/// +/// Base directory: Application Support / EncryptedChat / {email} +/// Same file names as Python client for cross-platform compatibility. +enum KeyStorage { + + // MARK: - Base Directory + + /// Get or create the key storage directory for a user + static func getKeyDir(email: String) throws -> URL { + let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! + let dir = appSupport.appendingPathComponent("EncryptedChat").appendingPathComponent(email) + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + // iOS file protection + try (dir as NSURL).setResourceValue(URLFileProtection.complete, forKey: .fileProtectionKey) + return dir + } + + // MARK: - RSA Keys + + /// Save RSA keypair + static func saveRSAKeys(email: String, privateKey: SecKey, publicKey: SecKey, password: Data? = nil) throws { + let dir = try getKeyDir(email: email) + let privData = try RSACrypto.serializePrivateKey(privateKey, password: password) + let pubData = try RSACrypto.serializePublicKey(publicKey) + try writeProtected(privData, to: dir.appendingPathComponent("private.pem")) + try pubData.write(to: dir.appendingPathComponent("public.pem")) + } + + /// Load RSA keypair. Returns (private, public, error). + static func loadRSAKeys(email: String, password: Data? = nil) -> (SecKey?, SecKey?, String?) { + guard let dir = try? getKeyDir(email: email) else { + return (nil, nil, "Cannot access key directory") + } + let privPath = dir.appendingPathComponent("private.pem") + let pubPath = dir.appendingPathComponent("public.pem") + + guard FileManager.default.fileExists(atPath: privPath.path) else { + return (nil, nil, "No local keys found.") + } + + guard let privData = try? Data(contentsOf: privPath), + let pubData = try? Data(contentsOf: pubPath) else { + return (nil, nil, "Cannot read key files.") + } + + do { + let privateKey = try RSACrypto.loadPrivateKey(privData, password: password) + let publicKey = try RSACrypto.loadPublicKey(pubData) + return (privateKey, publicKey, nil) + } catch { + // Try without password (unencrypted) + do { + let privateKey = try RSACrypto.loadPrivateKey(privData, password: nil) + let publicKey = try RSACrypto.loadPublicKey(pubData) + // Re-save with password if provided + if let password = password { + try? saveRSAKeys(email: email, privateKey: privateKey, publicKey: publicKey, password: password) + } + return (privateKey, publicKey, nil) + } catch { + return (nil, nil, "Invalid or missing password.") + } + } + } + + // MARK: - Identity Keys (Ed25519) + + static func saveIdentityKeys( + email: String, + privateKey: Curve25519.Signing.PrivateKey, + publicKey: Curve25519.Signing.PublicKey, + password: Data? = nil + ) throws { + let dir = try getKeyDir(email: email) + let privData = try Ed25519Crypto.serializePrivate(privateKey, password: password) + let pubData = Ed25519Crypto.serializePublic(publicKey) + try writeProtected(privData, to: dir.appendingPathComponent("identity_private.bin")) + try pubData.write(to: dir.appendingPathComponent("identity_public.bin")) + } + + static func loadIdentityKeys( + email: String, + password: Data? = nil + ) -> (Curve25519.Signing.PrivateKey?, Curve25519.Signing.PublicKey?) { + guard let dir = try? getKeyDir(email: email) else { return (nil, nil) } + let privPath = dir.appendingPathComponent("identity_private.bin") + let pubPath = dir.appendingPathComponent("identity_public.bin") + + guard FileManager.default.fileExists(atPath: privPath.path), + let privData = try? Data(contentsOf: privPath), + let pubData = try? Data(contentsOf: pubPath) else { + return (nil, nil) + } + + do { + let priv = try Ed25519Crypto.loadPrivate(privData, password: password) + let pub = try Ed25519Crypto.loadPublic(pubData) + return (priv, pub) + } catch { + return (nil, nil) + } + } + + // MARK: - Signed Pre-Key + + static func saveSPK(email: String, privateKey: Curve25519.KeyAgreement.PrivateKey, spkId: String) throws { + let dir = try getKeyDir(email: email) + try writeProtected(X25519Crypto.serializePrivate(privateKey), to: dir.appendingPathComponent("spk_private.bin")) + try spkId.write(to: dir.appendingPathComponent("spk_id.txt"), atomically: true, encoding: .utf8) + } + + static func loadSPK(email: String) -> (Curve25519.KeyAgreement.PrivateKey?, String?) { + guard let dir = try? getKeyDir(email: email) else { return (nil, nil) } + let privPath = dir.appendingPathComponent("spk_private.bin") + let idPath = dir.appendingPathComponent("spk_id.txt") + + guard FileManager.default.fileExists(atPath: privPath.path), + let privData = try? Data(contentsOf: privPath), + let priv = try? X25519Crypto.loadPrivate(privData) else { + return (nil, nil) + } + let spkId = (try? String(contentsOf: idPath, encoding: .utf8))?.trimmed ?? "" + return (priv, spkId) + } + + // MARK: - Previous SPK (Grace Period) + + static func savePrevSPK(email: String, privateKey: Curve25519.KeyAgreement.PrivateKey, spkId: String) throws { + let dir = try getKeyDir(email: email) + try writeProtected(X25519Crypto.serializePrivate(privateKey), to: dir.appendingPathComponent("prev_spk_private.bin")) + try spkId.write(to: dir.appendingPathComponent("prev_spk_id.txt"), atomically: true, encoding: .utf8) + } + + static func loadPrevSPK(email: String) -> (Curve25519.KeyAgreement.PrivateKey?, String?) { + guard let dir = try? getKeyDir(email: email) else { return (nil, nil) } + let privPath = dir.appendingPathComponent("prev_spk_private.bin") + let idPath = dir.appendingPathComponent("prev_spk_id.txt") + + guard FileManager.default.fileExists(atPath: privPath.path), + let privData = try? Data(contentsOf: privPath), + let priv = try? X25519Crypto.loadPrivate(privData) else { + return (nil, nil) + } + let spkId = (try? String(contentsOf: idPath, encoding: .utf8))?.trimmed ?? "" + return (priv, spkId) + } + + // MARK: - One-Time Pre-Keys + + static func saveOPKPrivate(email: String, opkId: String, privateKey: Curve25519.KeyAgreement.PrivateKey) throws { + let dir = try getKeyDir(email: email).appendingPathComponent("opk_private") + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + try writeProtected(X25519Crypto.serializePrivate(privateKey), to: dir.appendingPathComponent("\(opkId).bin")) + } + + static func loadOPKPrivate(email: String, opkId: String) -> Curve25519.KeyAgreement.PrivateKey? { + guard let dir = try? getKeyDir(email: email) else { return nil } + let path = dir.appendingPathComponent("opk_private").appendingPathComponent("\(opkId).bin") + guard let data = try? Data(contentsOf: path) else { return nil } + return try? X25519Crypto.loadPrivate(data) + } + + static func deleteOPKPrivate(email: String, opkId: String) { + guard let dir = try? getKeyDir(email: email) else { return } + let path = dir.appendingPathComponent("opk_private").appendingPathComponent("\(opkId).bin") + try? FileManager.default.removeItem(at: path) + } + + // MARK: - Device ID + + static func saveDeviceId(email: String, deviceId: String) throws { + let dir = try getKeyDir(email: email) + try writeProtected(Data(deviceId.utf8), to: dir.appendingPathComponent("device_id.txt")) + } + + static func loadDeviceId(email: String) -> String? { + guard let dir = try? getKeyDir(email: email) else { return nil } + let path = dir.appendingPathComponent("device_id.txt") + guard let data = try? Data(contentsOf: path) else { return nil } + let str = String(data: data, encoding: .utf8)?.trimmed + return (str?.isEmpty ?? true) ? nil : str + } + + // MARK: - Sessions (Double Ratchet) + + static func saveSession( + email: String, + peerUserId: String, + ratchet: DoubleRatchet, + localKey: Data? = nil, + peerDeviceId: String? = nil + ) throws { + let dir = try getKeyDir(email: email).appendingPathComponent("sessions") + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + + let filename: String + if let deviceId = peerDeviceId { + filename = "\(peerUserId)_\(deviceId).bin" + } else { + filename = "\(peerUserId).bin" + } + + var data = try ratchet.exportState() + if let localKey = localKey { + data = try CryptoUtils.encryptLocal(data, key: localKey) + } + try writeProtected(data, to: dir.appendingPathComponent(filename)) + } + + static func loadSession( + email: String, + peerUserId: String, + localKey: Data? = nil, + peerDeviceId: String? = nil + ) -> DoubleRatchet? { + guard let dir = try? getKeyDir(email: email) else { return nil } + let sessionsDir = dir.appendingPathComponent("sessions") + + let filename: String + if let deviceId = peerDeviceId { + filename = "\(peerUserId)_\(deviceId).bin" + } else { + filename = "\(peerUserId).bin" + } + + let path = sessionsDir.appendingPathComponent(filename) + return loadSessionFile(path, localKey: localKey) + } + + static func deleteSession(email: String, peerUserId: String, peerDeviceId: String? = nil) { + guard let dir = try? getKeyDir(email: email) else { return } + let sessionsDir = dir.appendingPathComponent("sessions") + + if let deviceId = peerDeviceId { + let path = sessionsDir.appendingPathComponent("\(peerUserId)_\(deviceId).bin") + try? FileManager.default.removeItem(at: path) + } else { + // Delete all sessions for this user + if let files = try? FileManager.default.contentsOfDirectory(atPath: sessionsDir.path) { + for file in files where file.hasPrefix(peerUserId) { + try? FileManager.default.removeItem(at: sessionsDir.appendingPathComponent(file)) + } + } + } + } + + private static func loadSessionFile(_ path: URL, localKey: Data?) -> DoubleRatchet? { + guard let raw = try? Data(contentsOf: path) else { return nil } + + if let localKey = localKey { + // Try encrypted first + if let decrypted = try? CryptoUtils.decryptLocal(raw, key: localKey) { + return try? DoubleRatchet.importState(decrypted) + } + // Fallback: plaintext (transparent migration) + if let ratchet = try? DoubleRatchet.importState(raw) { + // Re-save encrypted + try? writeProtected(CryptoUtils.encryptLocal(try ratchet.exportState(), key: localKey), to: path) + return ratchet + } + return nil + } + + return try? DoubleRatchet.importState(raw) + } + + // MARK: - Sender Keys + + static func saveSenderKeyState( + email: String, + convId: String, + state: SenderKeyState, + localKey: Data? = nil + ) throws { + let dir = try getKeyDir(email: email).appendingPathComponent("sender_keys") + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + + var data = state.exportState() + if let localKey = localKey { + data = try CryptoUtils.encryptLocal(data, key: localKey) + } + try writeProtected(data, to: dir.appendingPathComponent("\(convId).bin")) + } + + static func loadSenderKeyState( + email: String, + convId: String, + localKey: Data? = nil + ) -> SenderKeyState? { + guard let dir = try? getKeyDir(email: email) else { return nil } + let path = dir.appendingPathComponent("sender_keys").appendingPathComponent("\(convId).bin") + guard let raw = try? Data(contentsOf: path) else { return nil } + + if let localKey = localKey { + if let decrypted = try? CryptoUtils.decryptLocal(raw, key: localKey) { + return try? SenderKeyState.importState(decrypted) + } + // Plaintext fallback + if let state = try? SenderKeyState.importState(raw) { + try? writeProtected(CryptoUtils.encryptLocal(state.exportState(), key: localKey), to: path) + return state + } + return nil + } + + return try? SenderKeyState.importState(raw) + } + + static func deleteSenderKeyState(email: String, convId: String) { + guard let dir = try? getKeyDir(email: email) else { return } + let path = dir.appendingPathComponent("sender_keys").appendingPathComponent("\(convId).bin") + try? FileManager.default.removeItem(at: path) + } + + // MARK: - Received Sender Keys + + static func saveRecvSenderKey( + email: String, + convId: String, + senderId: String, + senderDeviceId: String, + state: SenderKeyState, + localKey: Data? = nil + ) throws { + let dir = try getKeyDir(email: email).appendingPathComponent("sender_keys_recv") + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + + var data = state.exportState() + if let localKey = localKey { + data = try CryptoUtils.encryptLocal(data, key: localKey) + } + try writeProtected(data, to: dir.appendingPathComponent("\(convId)_\(senderId)_\(senderDeviceId).bin")) + } + + static func loadRecvSenderKey( + email: String, + convId: String, + senderId: String, + senderDeviceId: String, + localKey: Data? = nil + ) -> SenderKeyState? { + guard let dir = try? getKeyDir(email: email) else { return nil } + let path = dir.appendingPathComponent("sender_keys_recv").appendingPathComponent("\(convId)_\(senderId)_\(senderDeviceId).bin") + guard let raw = try? Data(contentsOf: path) else { return nil } + + if let localKey = localKey { + if let decrypted = try? CryptoUtils.decryptLocal(raw, key: localKey) { + return try? SenderKeyState.importState(decrypted) + } + if let state = try? SenderKeyState.importState(raw) { + try? writeProtected(CryptoUtils.encryptLocal(state.exportState(), key: localKey), to: path) + return state + } + return nil + } + + return try? SenderKeyState.importState(raw) + } + + static func deleteRecvSenderKeys(email: String, convId: String) { + guard let dir = try? getKeyDir(email: email) else { return } + let recvDir = dir.appendingPathComponent("sender_keys_recv") + guard let files = try? FileManager.default.contentsOfDirectory(atPath: recvDir.path) else { return } + for file in files where file.hasPrefix(convId) { + try? FileManager.default.removeItem(at: recvDir.appendingPathComponent(file)) + } + } + + // MARK: - Favorites + + static func saveFavorites(email: String, favorites: Set) throws { + let dir = try getKeyDir(email: email) + let data = try JSONSerialization.data(withJSONObject: Array(favorites)) + try data.write(to: dir.appendingPathComponent("favorites.json")) + } + + static func loadFavorites(email: String) -> Set { + guard let dir = try? getKeyDir(email: email) else { return [] } + let path = dir.appendingPathComponent("favorites.json") + guard let data = try? Data(contentsOf: path), + let array = try? JSONSerialization.jsonObject(with: data) as? [String] else { + return [] + } + return Set(array) + } + + // MARK: - Helpers + + private static func writeProtected(_ data: Data, to url: URL) throws { + try data.write(to: url, options: .completeFileProtection) + } +} diff --git a/ios_client/EncryptedChat/Core/MessageCache.swift b/ios_client/EncryptedChat/Core/MessageCache.swift new file mode 100644 index 0000000..f88d9df --- /dev/null +++ b/ios_client/EncryptedChat/Core/MessageCache.swift @@ -0,0 +1,65 @@ +import Foundation + +/// Encrypted local message cache. +/// Matches Python: chat_core.py message cache (message_cache/{conv_id}.json) +enum MessageCache { + + /// Save messages for a conversation (encrypted with local storage key) + static func save(email: String, convId: String, messages: [[String: Any]], cacheKey: Data?) throws { + let dir = try KeyStorage.getKeyDir(email: email).appendingPathComponent("message_cache") + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + + let jsonData = try JSONSerialization.data(withJSONObject: messages) + + let dataToWrite: Data + if let cacheKey = cacheKey { + dataToWrite = try CryptoUtils.encryptLocal(jsonData, key: cacheKey) + } else { + dataToWrite = jsonData + } + + try dataToWrite.write(to: dir.appendingPathComponent("\(convId).json"), options: .completeFileProtection) + } + + /// Load messages for a conversation + static func load(email: String, convId: String, cacheKey: Data?) -> [[String: Any]]? { + guard let dir = try? KeyStorage.getKeyDir(email: email) else { return nil } + let path = dir.appendingPathComponent("message_cache").appendingPathComponent("\(convId).json") + guard let raw = try? Data(contentsOf: path) else { return nil } + + let jsonData: Data + if let cacheKey = cacheKey { + if let decrypted = try? CryptoUtils.decryptLocal(raw, key: cacheKey) { + jsonData = decrypted + } else { + // Plaintext fallback (migration) + jsonData = raw + } + } else { + jsonData = raw + } + + return try? JSONSerialization.jsonObject(with: jsonData) as? [[String: Any]] + } + + /// Search messages in a conversation + static func search(email: String, convId: String, query: String, cacheKey: Data?) -> [[String: Any]] { + guard let messages = load(email: email, convId: convId, cacheKey: cacheKey) else { + return [] + } + let lowerQuery = query.lowercased() + return messages.filter { msg in + if let text = msg["text"] as? String, text.lowercased().contains(lowerQuery) { + return true + } + return false + } + } + + /// Delete cache for a conversation + static func delete(email: String, convId: String) { + guard let dir = try? KeyStorage.getKeyDir(email: email) else { return } + let path = dir.appendingPathComponent("message_cache").appendingPathComponent("\(convId).json") + try? FileManager.default.removeItem(at: path) + } +} diff --git a/ios_client/EncryptedChat/Crypto/CryptoErrors.swift b/ios_client/EncryptedChat/Crypto/CryptoErrors.swift new file mode 100644 index 0000000..bf45320 --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/CryptoErrors.swift @@ -0,0 +1,95 @@ +import Foundation + +enum CryptoError: Error, LocalizedError { + case invalidBase64 + case invalidHex + case invalidKeyData(String) + case invalidSignature + case signatureVerificationFailed + case encryptionFailed(String) + case decryptionFailed(String) + case invalidECP1Format + case pbkdf2Failed + case rsaKeyGenerationFailed + case rsaOperationFailed(String) + case x3dhFailed(String) + case ratchetError(String) + case senderKeyError(String) + case maxSkipExceeded + case duplicateMessage + case invalidHeader(String) + case stateImportFailed(String) + case keyConversionFailed(String) + + var errorDescription: String? { + switch self { + case .invalidBase64: return "Invalid base64 encoding" + case .invalidHex: return "Invalid hex encoding" + case .invalidKeyData(let msg): return "Invalid key data: \(msg)" + case .invalidSignature: return "Invalid signature format" + case .signatureVerificationFailed: return "Signature verification failed" + case .encryptionFailed(let msg): return "Encryption failed: \(msg)" + case .decryptionFailed(let msg): return "Decryption failed: \(msg)" + case .invalidECP1Format: return "Invalid ECP1 key format" + case .pbkdf2Failed: return "PBKDF2 key derivation failed" + case .rsaKeyGenerationFailed: return "RSA key generation failed" + case .rsaOperationFailed(let msg): return "RSA operation failed: \(msg)" + case .x3dhFailed(let msg): return "X3DH failed: \(msg)" + case .ratchetError(let msg): return "Ratchet error: \(msg)" + case .senderKeyError(let msg): return "Sender key error: \(msg)" + case .maxSkipExceeded: return "Maximum message skip exceeded" + case .duplicateMessage: return "Duplicate message detected" + case .invalidHeader(let msg): return "Invalid header: \(msg)" + case .stateImportFailed(let msg): return "State import failed: \(msg)" + case .keyConversionFailed(let msg): return "Key conversion failed: \(msg)" + } + } +} + +enum NetworkError: Error, LocalizedError { + case notConnected + case connectionFailed(String) + case timeout + case serverError(String) + case protocolError(String) + case messageTooLarge + case invalidResponse(String) + case authenticationFailed(String) + case alreadyConnected + + var errorDescription: String? { + switch self { + case .notConnected: return "Not connected to server" + case .connectionFailed(let msg): return "Connection failed: \(msg)" + case .timeout: return "Request timed out" + case .serverError(let msg): return "Server error: \(msg)" + case .protocolError(let msg): return "Protocol error: \(msg)" + case .messageTooLarge: return "Message exceeds maximum size" + case .invalidResponse(let msg): return "Invalid response: \(msg)" + case .authenticationFailed(let msg): return "Authentication failed: \(msg)" + case .alreadyConnected: return "Already connected" + } + } +} + +enum ChatError: Error, LocalizedError { + case notLoggedIn + case conversationNotFound + case membershipRequired + case permissionDenied(String) + case operationFailed(String) + case fileError(String) + case invalidData(String) + + var errorDescription: String? { + switch self { + case .notLoggedIn: return "Not logged in" + case .conversationNotFound: return "Conversation not found" + case .membershipRequired: return "Must be a member of this conversation" + case .permissionDenied(let msg): return "Permission denied: \(msg)" + case .operationFailed(let msg): return "Operation failed: \(msg)" + case .fileError(let msg): return "File error: \(msg)" + case .invalidData(let msg): return "Invalid data: \(msg)" + } + } +} diff --git a/ios_client/EncryptedChat/Crypto/CryptoUtils.swift b/ios_client/EncryptedChat/Crypto/CryptoUtils.swift new file mode 100644 index 0000000..60e5abb --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/CryptoUtils.swift @@ -0,0 +1,196 @@ +import Foundation +import CryptoKit + +/// Core cryptographic utilities: AES-GCM, HKDF, KDF helpers +enum CryptoUtils { + + // MARK: - AES-256-GCM + + /// Encrypt with AES-256-GCM. Returns (key, nonce, ciphertext, tag) — all as Data. + /// If key is nil, generates a random 256-bit key. + /// Matches Python: aes_encrypt(plaintext, key=None) + static func aesEncrypt(_ plaintext: Data, key: Data? = nil) throws -> (key: Data, nonce: Data, ciphertext: Data, tag: Data) { + let keyData = key ?? Data.randomBytes(32) + let symmetricKey = SymmetricKey(data: keyData) + let nonceData = Data.randomBytes(12) + let gcmNonce = try AES.GCM.Nonce(data: nonceData) + + let sealedBox = try AES.GCM.seal(plaintext, using: symmetricKey, nonce: gcmNonce) + + return ( + key: keyData, + nonce: nonceData, + ciphertext: Data(sealedBox.ciphertext), + tag: Data(sealedBox.tag) + ) + } + + /// Decrypt with AES-256-GCM. + /// Matches Python: aes_decrypt(key, nonce, ciphertext, tag) + static func aesDecrypt(key: Data, nonce: Data, ciphertext: Data, tag: Data) throws -> Data { + let symmetricKey = SymmetricKey(data: key) + let gcmNonce = try AES.GCM.Nonce(data: nonce) + + let sealedBox = try AES.GCM.SealedBox( + nonce: gcmNonce, + ciphertext: ciphertext, + tag: tag + ) + + do { + return try AES.GCM.open(sealedBox, using: symmetricKey) + } catch { + throw CryptoError.decryptionFailed("AES-GCM decryption failed") + } + } + + /// Encrypt with AES-256-GCM using AAD. Returns ciphertext with tag appended. + /// Used by Double Ratchet and Sender Keys. + static func aesGcmEncrypt(_ plaintext: Data, key: Data, nonce: Data, aad: Data) throws -> Data { + let symmetricKey = SymmetricKey(data: key) + let gcmNonce = try AES.GCM.Nonce(data: nonce) + + let sealedBox = try AES.GCM.seal( + plaintext, + using: symmetricKey, + nonce: gcmNonce, + authenticating: aad + ) + + // Return ciphertext + tag concatenated (matches Python AESGCM.encrypt) + return Data(sealedBox.ciphertext) + Data(sealedBox.tag) + } + + /// Decrypt AES-256-GCM with AAD. Input ciphertext has tag appended (last 16 bytes). + static func aesGcmDecrypt(_ ctWithTag: Data, key: Data, nonce: Data, aad: Data) throws -> Data { + guard ctWithTag.count >= 16 else { + throw CryptoError.decryptionFailed("Ciphertext too short") + } + + let ct = ctWithTag.prefix(ctWithTag.count - 16) + let tag = ctWithTag.suffix(16) + + let symmetricKey = SymmetricKey(data: key) + let gcmNonce = try AES.GCM.Nonce(data: nonce) + + let sealedBox = try AES.GCM.SealedBox( + nonce: gcmNonce, + ciphertext: ct, + tag: tag + ) + + do { + return try AES.GCM.open(sealedBox, using: symmetricKey, authenticating: aad) + } catch { + throw CryptoError.decryptionFailed("AES-GCM decryption with AAD failed") + } + } + + // MARK: - HKDF + + /// HKDF-SHA256 key derivation. + /// Matches Python: hkdf_derive(input_key, salt, info, length=32) + static func hkdfDerive(inputKey: Data, salt: Data, info: Data, length: Int = 32) -> Data { + let symmetricKey = SymmetricKey(data: inputKey) + let derived = HKDF.deriveKey( + inputKeyMaterial: symmetricKey, + salt: salt, + info: info, + outputByteCount: length + ) + return derived.withUnsafeBytes { Data($0) } + } + + // MARK: - KDF for Double Ratchet + + /// Root key KDF. Returns (newRootKey, chainKey). + /// HKDF with rootKey as salt and DH output as input. Derives 64 bytes, split in half. + /// Matches Python: kdf_rk(root_key, dh_output) + static func kdfRK(rootKey: Data, dhOutput: Data) -> (newRootKey: Data, chainKey: Data) { + let derived = hkdfDerive( + inputKey: dhOutput, + salt: rootKey, + info: Data(Constants.rootKeyInfo.utf8), + length: 64 + ) + return (derived.prefix(32), Data(derived.suffix(32))) + } + + /// Chain key KDF. Returns (newChainKey, messageKey). + /// HMAC-SHA256: messageKey = HMAC(chainKey, 0x01), newChainKey = HMAC(chainKey, 0x02) + /// Matches Python: kdf_ck(chain_key) + static func kdfCK(chainKey: Data) -> (newChainKey: Data, messageKey: Data) { + let symmetricKey = SymmetricKey(data: chainKey) + let messageKey = Data(HMAC.authenticationCode(for: Data([0x01]), using: symmetricKey)) + let newChainKey = Data(HMAC.authenticationCode(for: Data([0x02]), using: symmetricKey)) + return (newChainKey, messageKey) + } + + // MARK: - Self-Encryption Key + + /// Derive static AES-256 key from identity key for self-encrypted message copies. + /// Matches Python: derive_self_encryption_key(identity_private) + static func deriveSelfEncryptionKey(identityPrivateRaw: Data) -> Data { + hkdfDerive( + inputKey: identityPrivateRaw, + salt: Data(Constants.selfEncryptionSalt.utf8), + info: Data(Constants.selfEncryptionInfo.utf8), + length: 32 + ) + } + + // MARK: - Local Storage Key + + /// Derive AES-256 key for encrypting local session/sender key files. + /// Matches Python: derive_local_storage_key(identity_private) + static func deriveLocalStorageKey(identityPrivateRaw: Data) -> Data { + hkdfDerive( + inputKey: identityPrivateRaw, + salt: Data(Constants.localStorageSalt.utf8), + info: Data(Constants.localStorageInfo.utf8), + length: 32 + ) + } + + // MARK: - Local File Encryption + + /// Encrypt data for local storage. Format: nonce(12) + tag(16) + ciphertext + /// Matches Python: _encrypt_local(data, key) + static func encryptLocal(_ data: Data, key: Data) throws -> Data { + let symmetricKey = SymmetricKey(data: key) + let sealedBox = try AES.GCM.seal(data, using: symmetricKey) + + var result = Data() + result.append(Data(sealedBox.nonce)) // 12 bytes + result.append(Data(sealedBox.tag)) // 16 bytes + result.append(Data(sealedBox.ciphertext)) // N bytes + return result + } + + /// Decrypt locally stored data. Format: nonce(12) + tag(16) + ciphertext + /// Matches Python: _decrypt_local(raw, key) + static func decryptLocal(_ raw: Data, key: Data) throws -> Data { + guard raw.count >= 28 else { // 12 + 16 minimum + throw CryptoError.decryptionFailed("Local encrypted data too short") + } + + let nonce = raw[0..<12] + let tag = raw[12..<28] + let ct = raw[28...] + + let symmetricKey = SymmetricKey(data: key) + let gcmNonce = try AES.GCM.Nonce(data: nonce) + + let sealedBox = try AES.GCM.SealedBox( + nonce: gcmNonce, + ciphertext: ct, + tag: tag + ) + + do { + return try AES.GCM.open(sealedBox, using: symmetricKey) + } catch { + throw CryptoError.decryptionFailed("Local storage decryption failed") + } + } +} diff --git a/ios_client/EncryptedChat/Crypto/DoubleRatchet.swift b/ios_client/EncryptedChat/Crypto/DoubleRatchet.swift new file mode 100644 index 0000000..29c8add --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/DoubleRatchet.swift @@ -0,0 +1,371 @@ +import Foundation +import CryptoKit + +/// Ratchet header sent with each message +struct RatchetHeader { + let dhPub: Data // sender's current ratchet public key (32 bytes) + let n: Int // message number in current sending chain + let pn: Int // number of messages in previous sending chain + + /// Serialize header to JSON bytes for use as AAD. + /// Matches Python: RatchetHeader.serialize() + func serialize() -> Data { + let dict: [String: Any] = [ + "dh_pub": dhPub.hexString, + "n": n, + "pn": pn, + ] + // Must produce consistent JSON — sorted keys for determinism + return try! JSONSerialization.data(withJSONObject: dict, options: .sortedKeys) + } + + /// Convert to dictionary for protocol. + /// Matches Python: RatchetHeader.to_dict() + func toDict() -> [String: Any] { + [ + "dh_pub": dhPub.hexString, + "n": n, + "pn": pn, + ] + } + + /// Parse from dictionary. + /// Matches Python: RatchetHeader.from_dict(d) + static func fromDict(_ d: [String: Any]) throws -> RatchetHeader { + guard let dhPubHex = d["dh_pub"] as? String, + let dhPub = Data(hexString: dhPubHex), + let n = d["n"] as? Int, + let pn = d["pn"] as? Int else { + throw CryptoError.invalidHeader("Missing or invalid header fields") + } + return RatchetHeader(dhPub: dhPub, n: n, pn: pn) + } +} + +/// Signal Double Ratchet implementation. +/// Matches Python: DoubleRatchet class in crypto_utils.py +class DoubleRatchet { + + private(set) var dhPair: (privateKey: Curve25519.KeyAgreement.PrivateKey, + publicKey: Curve25519.KeyAgreement.PublicKey)? + private(set) var dhRemote: Curve25519.KeyAgreement.PublicKey? + private(set) var rootKey: Data = Data() + private(set) var sendChainKey: Data? + private(set) var recvChainKey: Data? + private(set) var sendN: Int = 0 + private(set) var recvN: Int = 0 + private(set) var prevSendN: Int = 0 + // Skipped message keys: "dh_pub_hex:n" → message_key + private(set) var skipped: [String: Data] = [:] + + /// Attached X3DH header — set when creating a new session, consumed on first send. + /// Matches Python: ratchet._x3dh_header + var x3dhHeader: [String: Any]? + + init() {} + + // MARK: - Initialization + + /// Initialize as initiator (Alice) after X3DH. + /// Matches Python: DoubleRatchet.init_alice(shared_secret, bob_spk_pub) + static func initAlice(sharedSecret: Data, bobSpkPub: Curve25519.KeyAgreement.PublicKey) throws -> DoubleRatchet { + let ratchet = DoubleRatchet() + let (priv, pub) = X25519Crypto.generateKeypair() + ratchet.dhPair = (priv, pub) + ratchet.dhRemote = bobSpkPub + + // Perform DH ratchet to derive send chain + let dhOutput = try X25519Crypto.dh(priv, bobSpkPub) + let (newRK, sendCK) = CryptoUtils.kdfRK(rootKey: sharedSecret, dhOutput: dhOutput) + ratchet.rootKey = newRK + ratchet.sendChainKey = sendCK + ratchet.recvChainKey = nil + ratchet.sendN = 0 + ratchet.recvN = 0 + ratchet.prevSendN = 0 + return ratchet + } + + /// Initialize as responder (Bob) after X3DH. + /// Matches Python: DoubleRatchet.init_bob(shared_secret, spk_pair) + static func initBob( + sharedSecret: Data, + spkPair: (privateKey: Curve25519.KeyAgreement.PrivateKey, publicKey: Curve25519.KeyAgreement.PublicKey) + ) -> DoubleRatchet { + let ratchet = DoubleRatchet() + ratchet.dhPair = spkPair + ratchet.rootKey = sharedSecret + ratchet.sendChainKey = nil + ratchet.recvChainKey = nil + ratchet.sendN = 0 + ratchet.recvN = 0 + ratchet.prevSendN = 0 + return ratchet + } + + // MARK: - Encrypt + + /// Encrypt a message. + /// Returns (header dict, ciphertext with tag, nonce). + /// Matches Python: DoubleRatchet.encrypt(plaintext) + func encrypt(_ plaintext: Data) throws -> (header: [String: Any], ciphertext: Data, nonce: Data) { + guard sendChainKey != nil else { + throw CryptoError.ratchetError("Send chain not initialized") + } + guard let dhPair = dhPair else { + throw CryptoError.ratchetError("DH pair not set") + } + + let (newCK, messageKey) = CryptoUtils.kdfCK(chainKey: sendChainKey!) + sendChainKey = newCK + + let header = RatchetHeader( + dhPub: X25519Crypto.serializePublic(dhPair.publicKey), + n: sendN, + pn: prevSendN + ) + + let nonce = Data.randomBytes(12) + let aad = header.serialize() + let ctWithTag = try CryptoUtils.aesGcmEncrypt(plaintext, key: messageKey, nonce: nonce, aad: aad) + + sendN += 1 + + return (header.toDict(), ctWithTag, nonce) + } + + // MARK: - Decrypt + + /// Decrypt a message. Handles DH ratchet step if new dh_pub. + /// State is snapshotted before modification and restored on failure (M9 fix). + /// Matches Python: DoubleRatchet.decrypt(header_dict, ciphertext, nonce) + func decrypt(headerDict: [String: Any], ciphertext: Data, nonce: Data) throws -> Data { + let header = try RatchetHeader.fromDict(headerDict) + let remoteDhPubBytes = header.dhPub + + // Check if this is from a skipped message + let skipKey = "\(remoteDhPubBytes.hexString):\(header.n)" + if let mk = skipped[skipKey] { + skipped.removeValue(forKey: skipKey) + let aad = header.serialize() + do { + return try CryptoUtils.aesGcmDecrypt(ciphertext, key: mk, nonce: nonce, aad: aad) + } catch { + // Restore skipped key on failure + skipped[skipKey] = mk + throw error + } + } + + // Snapshot state before modifications + let snap = snapshot() + + do { + let remoteDhPub = try X25519Crypto.loadPublic(remoteDhPubBytes) + let currentRemoteBytes: Data? = dhRemote.map { X25519Crypto.serializePublic($0) } + + if currentRemoteBytes == nil || remoteDhPubBytes != currentRemoteBytes { + // New DH ratchet step + try skipMessages(until: header.pn) + try dhRatchet(remoteDhPub: remoteDhPub) + } + + try skipMessages(until: header.n) + + // Derive message key from receive chain + guard recvChainKey != nil else { + throw CryptoError.ratchetError("Receive chain key is nil") + } + let (newCK, mk) = CryptoUtils.kdfCK(chainKey: recvChainKey!) + recvChainKey = newCK + recvN += 1 + + let aad = header.serialize() + return try CryptoUtils.aesGcmDecrypt(ciphertext, key: mk, nonce: nonce, aad: aad) + } catch { + restore(snap) + throw error + } + } + + // MARK: - State Snapshot/Restore (M9) + + private struct Snapshot { + let dhPairPriv: Data? + let dhPairPub: Data? + let dhRemote: Data? + let rootKey: Data + let sendChainKey: Data? + let recvChainKey: Data? + let sendN: Int + let recvN: Int + let prevSendN: Int + let skipped: [String: Data] + } + + private func snapshot() -> Snapshot { + Snapshot( + dhPairPriv: dhPair.map { X25519Crypto.serializePrivate($0.privateKey) }, + dhPairPub: dhPair.map { X25519Crypto.serializePublic($0.publicKey) }, + dhRemote: dhRemote.map { X25519Crypto.serializePublic($0) }, + rootKey: rootKey, + sendChainKey: sendChainKey, + recvChainKey: recvChainKey, + sendN: sendN, + recvN: recvN, + prevSendN: prevSendN, + skipped: skipped + ) + } + + private func restore(_ snap: Snapshot) { + if let privData = snap.dhPairPriv, let pubData = snap.dhPairPub, + let priv = try? X25519Crypto.loadPrivate(privData), + let pub = try? X25519Crypto.loadPublic(pubData) { + dhPair = (priv, pub) + } else { + dhPair = nil + } + if let remoteData = snap.dhRemote, let remote = try? X25519Crypto.loadPublic(remoteData) { + dhRemote = remote + } else { + dhRemote = nil + } + rootKey = snap.rootKey + sendChainKey = snap.sendChainKey + recvChainKey = snap.recvChainKey + sendN = snap.sendN + recvN = snap.recvN + prevSendN = snap.prevSendN + skipped = snap.skipped + } + + // MARK: - Internal Ratchet Operations + + private func skipMessages(until: Int) throws { + guard recvChainKey != nil else { return } + if until - recvN > Constants.maxSkip { + throw CryptoError.maxSkipExceeded + } + while recvN < until { + let (newCK, mk) = CryptoUtils.kdfCK(chainKey: recvChainKey!) + recvChainKey = newCK + let remoteHex = dhRemote.map { X25519Crypto.serializePublic($0).hexString } ?? "" + skipped["\(remoteHex):\(recvN)"] = mk + recvN += 1 + } + } + + private func dhRatchet(remoteDhPub: Curve25519.KeyAgreement.PublicKey) throws { + prevSendN = sendN + sendN = 0 + recvN = 0 + dhRemote = remoteDhPub + + // Derive new receive chain key + guard let dhPair = dhPair else { + throw CryptoError.ratchetError("DH pair not set") + } + let dhOutput1 = try X25519Crypto.dh(dhPair.privateKey, remoteDhPub) + let (newRK1, recvCK) = CryptoUtils.kdfRK(rootKey: rootKey, dhOutput: dhOutput1) + rootKey = newRK1 + recvChainKey = recvCK + + // Generate new DH pair and derive new send chain key + let (newPriv, newPub) = X25519Crypto.generateKeypair() + self.dhPair = (newPriv, newPub) + let dhOutput2 = try X25519Crypto.dh(newPriv, remoteDhPub) + let (newRK2, sendCK) = CryptoUtils.kdfRK(rootKey: rootKey, dhOutput: dhOutput2) + rootKey = newRK2 + sendChainKey = sendCK + } + + // MARK: - State Export/Import + + /// Serialize full ratchet state for persistent storage. + /// Produces JSON matching Python's DoubleRatchet.export_state() exactly. + func exportState() throws -> Data { + var state: [String: Any] = [:] + + if let pair = dhPair { + state["dh_priv"] = X25519Crypto.serializePrivate(pair.privateKey).hexString + state["dh_pub"] = X25519Crypto.serializePublic(pair.publicKey).hexString + } else { + state["dh_priv"] = NSNull() + state["dh_pub"] = NSNull() + } + + if let remote = dhRemote { + state["dh_remote"] = X25519Crypto.serializePublic(remote).hexString + } else { + state["dh_remote"] = NSNull() + } + + state["root_key"] = rootKey.hexString + state["send_ck"] = sendChainKey?.hexString ?? NSNull() + state["recv_ck"] = recvChainKey?.hexString ?? NSNull() + state["send_n"] = sendN + state["recv_n"] = recvN + state["prev_send_n"] = prevSendN + + // Skipped keys: Python format is "dh_pub_hex:n" -> message_key_hex + var skippedDict: [String: String] = [:] + for (key, value) in skipped { + skippedDict[key] = value.hexString + } + state["skipped"] = skippedDict + + return try JSONSerialization.data(withJSONObject: state) + } + + /// Deserialize ratchet state. + /// Matches Python: DoubleRatchet.import_state(data) + static func importState(_ data: Data) throws -> DoubleRatchet { + guard let state = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { + throw CryptoError.stateImportFailed("Invalid JSON") + } + + let r = DoubleRatchet() + + if let dhPrivHex = state["dh_priv"] as? String, + let dhPubHex = state["dh_pub"] as? String, + let privData = Data(hexString: dhPrivHex), + let pubData = Data(hexString: dhPubHex) { + let priv = try X25519Crypto.loadPrivate(privData) + let pub = try X25519Crypto.loadPublic(pubData) + r.dhPair = (priv, pub) + } + + if let dhRemoteHex = state["dh_remote"] as? String, + let remoteData = Data(hexString: dhRemoteHex) { + r.dhRemote = try X25519Crypto.loadPublic(remoteData) + } + + guard let rootKeyHex = state["root_key"] as? String, + let rootKey = Data(hexString: rootKeyHex) else { + throw CryptoError.stateImportFailed("Missing root_key") + } + r.rootKey = rootKey + + if let sendCKHex = state["send_ck"] as? String, let ck = Data(hexString: sendCKHex) { + r.sendChainKey = ck + } + if let recvCKHex = state["recv_ck"] as? String, let ck = Data(hexString: recvCKHex) { + r.recvChainKey = ck + } + + r.sendN = state["send_n"] as? Int ?? 0 + r.recvN = state["recv_n"] as? Int ?? 0 + r.prevSendN = state["prev_send_n"] as? Int ?? 0 + + if let skippedDict = state["skipped"] as? [String: String] { + for (key, valueHex) in skippedDict { + if let value = Data(hexString: valueHex) { + r.skipped[key] = value + } + } + } + + return r + } +} diff --git a/ios_client/EncryptedChat/Crypto/Ed25519Crypto.swift b/ios_client/EncryptedChat/Crypto/Ed25519Crypto.swift new file mode 100644 index 0000000..a71ad96 --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/Ed25519Crypto.swift @@ -0,0 +1,73 @@ +import Foundation +import CryptoKit + +/// Ed25519 signing operations — Identity Key management +enum Ed25519Crypto { + + // MARK: - Key Generation + + /// Generate Ed25519 keypair + static func generateKeypair() -> (privateKey: Curve25519.Signing.PrivateKey, publicKey: Curve25519.Signing.PublicKey) { + let privateKey = Curve25519.Signing.PrivateKey() + return (privateKey, privateKey.publicKey) + } + + // MARK: - Serialization + + /// Serialize Ed25519 private key. With password: raw 32B → ECP1. Without: raw 32B. + /// Matches Python: serialize_ed25519_private(key, password=None) + static func serializePrivate(_ key: Curve25519.Signing.PrivateKey, password: Data? = nil) throws -> Data { + let raw = key.rawData // 32 bytes + if let password = password { + return try KeyEncryption.encrypt(raw, password: password) + } + return raw + } + + /// Serialize Ed25519 public key to 32 raw bytes. + /// Matches Python: serialize_ed25519_public(key) + static func serializePublic(_ key: Curve25519.Signing.PublicKey) -> Data { + key.rawData // 32 bytes + } + + // MARK: - Loading + + /// Load Ed25519 private key. Auto-detects ECP1 / raw 32B. + /// Matches Python: load_ed25519_private(data, password=None) + static func loadPrivate(_ data: Data, password: Data? = nil) throws -> Curve25519.Signing.PrivateKey { + if KeyEncryption.isECP1Format(data) { + guard let pwd = password else { + throw CryptoError.invalidKeyData("ECP1 key requires password") + } + let raw = try KeyEncryption.decrypt(data, password: pwd) + return try Curve25519.Signing.PrivateKey(rawRepresentation: raw) + } + if data.count == 32 { + return try Curve25519.Signing.PrivateKey(rawRepresentation: data) + } + throw CryptoError.invalidKeyData("Cannot parse Ed25519 private key (\(data.count) bytes)") + } + + /// Load Ed25519 public key from 32 raw bytes. + /// Matches Python: load_ed25519_public(data) + static func loadPublic(_ data: Data) throws -> Curve25519.Signing.PublicKey { + guard data.count == 32 else { + throw CryptoError.invalidKeyData("Ed25519 public key must be 32 bytes, got \(data.count)") + } + return try Curve25519.Signing.PublicKey(rawRepresentation: data) + } + + // MARK: - Sign / Verify + + /// Sign data with Ed25519. Returns 64-byte signature. + /// Matches Python: ed25519_sign(private_key, data) + static func sign(_ privateKey: Curve25519.Signing.PrivateKey, data: Data) throws -> Data { + Data(try privateKey.signature(for: data)) + } + + /// Verify Ed25519 signature. + /// Matches Python: ed25519_verify(public_key, signature, data) + static func verify(_ publicKey: Curve25519.Signing.PublicKey, signature: Data, data: Data) -> Bool { + publicKey.isValidSignature(signature, for: data) + } +} diff --git a/ios_client/EncryptedChat/Crypto/FieldArithmetic.swift b/ios_client/EncryptedChat/Crypto/FieldArithmetic.swift new file mode 100644 index 0000000..bf844ff --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/FieldArithmetic.swift @@ -0,0 +1,231 @@ +import Foundation + +/// Pure Swift GF(2^255-19) arithmetic for Ed25519 → X25519 public key conversion. +/// +/// The conversion formula is: u = (1 + y) / (1 - y) mod p +/// where p = 2^255 - 19, and y is the Ed25519 public key's y-coordinate. +/// +/// Uses 4-limb UInt64 representation (little-endian). +enum FieldArithmetic { + + // p = 2^255 - 19 + static let p: [UInt64] = [ + 0xFFFF_FFFF_FFFF_FFED, // limb 0 (least significant) + 0xFFFF_FFFF_FFFF_FFFF, // limb 1 + 0xFFFF_FFFF_FFFF_FFFF, // limb 2 + 0x7FFF_FFFF_FFFF_FFFF, // limb 3 (most significant, 2^63 - 1 accounting for -19) + ] + + /// Load a 256-bit little-endian byte array into 4 UInt64 limbs + static func load(_ bytes: Data) -> [UInt64] { + precondition(bytes.count == 32) + var limbs = [UInt64](repeating: 0, count: 4) + for i in 0..<4 { + var val: UInt64 = 0 + for j in 0..<8 { + val |= UInt64(bytes[i * 8 + j]) << (j * 8) + } + limbs[i] = val + } + return limbs + } + + /// Store 4 UInt64 limbs as 32 little-endian bytes + static func store(_ limbs: [UInt64]) -> Data { + var bytes = Data(count: 32) + for i in 0..<4 { + for j in 0..<8 { + bytes[i * 8 + j] = UInt8((limbs[i] >> (j * 8)) & 0xFF) + } + } + return bytes + } + + /// a + b mod p + static func add(_ a: [UInt64], _ b: [UInt64]) -> [UInt64] { + var result = [UInt64](repeating: 0, count: 4) + var carry: UInt64 = 0 + for i in 0..<4 { + let (sum1, c1) = a[i].addingReportingOverflow(b[i]) + let (sum2, c2) = sum1.addingReportingOverflow(carry) + result[i] = sum2 + carry = (c1 ? 1 : 0) + (c2 ? 1 : 0) + } + // Reduce mod p + return reduceOnce(result, carry: carry) + } + + /// a - b mod p + static func sub(_ a: [UInt64], _ b: [UInt64]) -> [UInt64] { + var result = [UInt64](repeating: 0, count: 4) + var borrow: UInt64 = 0 + for i in 0..<4 { + let (diff1, b1) = a[i].subtractingReportingOverflow(b[i]) + let (diff2, b2) = diff1.subtractingReportingOverflow(borrow) + result[i] = diff2 + borrow = (b1 ? 1 : 0) + (b2 ? 1 : 0) + } + if borrow > 0 { + // Add p back + var c: UInt64 = 0 + for i in 0..<4 { + let (s1, c1) = result[i].addingReportingOverflow(p[i]) + let (s2, c2) = s1.addingReportingOverflow(c) + result[i] = s2 + c = (c1 ? 1 : 0) + (c2 ? 1 : 0) + } + } + return result + } + + /// Multiply two 256-bit numbers mod p using schoolbook multiplication + static func mul(_ a: [UInt64], _ b: [UInt64]) -> [UInt64] { + // Full 512-bit product in 8 limbs + var product = [UInt64](repeating: 0, count: 8) + + for i in 0..<4 { + var carry: UInt64 = 0 + for j in 0..<4 { + let (hi, lo) = a[i].multipliedFullWidth(by: b[j]) + let (sum1, c1) = product[i + j].addingReportingOverflow(lo) + let (sum2, c2) = sum1.addingReportingOverflow(carry) + product[i + j] = sum2 + carry = hi + (c1 ? 1 : 0) + (c2 ? 1 : 0) + } + product[i + 4] = carry + } + + // Reduce mod p using Barrett-like reduction + // Since p = 2^255 - 19, for a 512-bit number we can use: + // x mod p = (x_low + x_high * 2^256) mod p + // Since 2^255 ≡ 19 (mod p), 2^256 ≡ 38 (mod p) + return reduceFull(product) + } + + /// Reduce 512-bit product mod p using 2^256 ≡ 38 (mod p) + private static func reduceFull(_ product: [UInt64]) -> [UInt64] { + // Split: low = product[0..3], high = product[4..7] + // result = low + high * 38 + var result = [UInt64](repeating: 0, count: 5) + + // Start with low part + for i in 0..<4 { + result[i] = product[i] + } + + // Add high * 38 + var carry: UInt64 = 0 + for i in 0..<4 { + let (hi, lo) = product[i + 4].multipliedFullWidth(by: 38) + let (sum1, c1) = result[i].addingReportingOverflow(lo) + let (sum2, c2) = sum1.addingReportingOverflow(carry) + result[i] = sum2 + carry = hi + (c1 ? 1 : 0) + (c2 ? 1 : 0) + } + result[4] = carry + + // The result might still be >= p, so reduce once more + // result[4] * 2^256 ≡ result[4] * 38 (mod p) + var extra: UInt64 = result[4] + result[4] = 0 + if extra > 0 { + let (hi, lo) = extra.multipliedFullWidth(by: 38) + let (sum1, c1) = result[0].addingReportingOverflow(lo) + result[0] = sum1 + var c = hi + (c1 ? 1 : 0) + for i in 1..<4 { + let (s, cf) = result[i].addingReportingOverflow(c) + result[i] = s + c = cf ? 1 : 0 + } + // One more round if carry + if c > 0 { + let (s, _) = result[0].addingReportingOverflow(c * 38) + result[0] = s + } + } + + var out = Array(result[0..<4]) + // Final reduction: if >= p, subtract p + out = reduceOnce(out, carry: 0) + return out + } + + /// If the number >= p, subtract p + private static func reduceOnce(_ val: [UInt64], carry: UInt64) -> [UInt64] { + if carry > 0 || isGreaterOrEqual(val, p) { + var result = [UInt64](repeating: 0, count: 4) + var borrow: UInt64 = 0 + for i in 0..<4 { + let (diff1, b1) = val[i].subtractingReportingOverflow(p[i]) + let (diff2, b2) = diff1.subtractingReportingOverflow(borrow) + result[i] = diff2 + borrow = (b1 ? 1 : 0) + (b2 ? 1 : 0) + } + // If borrow after subtracting p, the original was fine (shouldn't happen with carry) + if borrow > 0 && carry == 0 { + return val + } + return result + } + return val + } + + /// Compare a >= b + private static func isGreaterOrEqual(_ a: [UInt64], _ b: [UInt64]) -> Bool { + for i in stride(from: 3, through: 0, by: -1) { + if a[i] > b[i] { return true } + if a[i] < b[i] { return false } + } + return true // equal + } + + /// Modular inverse using Fermat's little theorem: a^(-1) = a^(p-2) mod p + static func inverse(_ a: [UInt64]) -> [UInt64] { + // p - 2 = 2^255 - 21 + let pMinus2 = sub(p, [2, 0, 0, 0]) + return power(a, pMinus2) + } + + /// Modular exponentiation using square-and-multiply + static func power(_ base: [UInt64], _ exp: [UInt64]) -> [UInt64] { + var result: [UInt64] = [1, 0, 0, 0] // 1 + var b = base + + for i in 0..<4 { + var limb = exp[i] + let bits = (i == 3) ? 63 : 64 // top limb has 63 bits for p-2 + for _ in 0..>= 1 + } + } + return result + } + + // MARK: - Ed25519 → X25519 Public Key Conversion + + /// Convert Ed25519 public key (32 bytes) to X25519 public key (32 bytes). + /// Formula: u = (1 + y) * inverse(1 - y) mod p + static func ed25519PublicToX25519(_ ed25519Pub: Data) -> Data { + precondition(ed25519Pub.count == 32) + + // Ed25519 public key is the y-coordinate with sign bit in the top bit of byte 31 + var keyBytes = ed25519Pub + // Clear the sign bit + keyBytes[31] &= 0x7F + + let y = load(keyBytes) + let one: [UInt64] = [1, 0, 0, 0] + + let onePlusY = add(one, y) + let oneMinusY = sub(one, y) + let inv = inverse(oneMinusY) + let u = mul(onePlusY, inv) + + return store(u) + } +} diff --git a/ios_client/EncryptedChat/Crypto/KeyEncryption.swift b/ios_client/EncryptedChat/Crypto/KeyEncryption.swift new file mode 100644 index 0000000..16e8dd2 --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/KeyEncryption.swift @@ -0,0 +1,106 @@ +import Foundation +import CryptoKit +import CommonCrypto + +/// ECP1 key encryption format: PBKDF2-HMAC-SHA256 (600k iterations) + AES-256-GCM +/// Wire format: magic(4) + salt(16) + nonce(12) + ciphertext_with_tag(N+16) +enum KeyEncryption { + + /// Encrypt raw key bytes with password using ECP1 format + static func encrypt(_ rawBytes: Data, password: Data) throws -> Data { + let salt = Data.randomBytes(16) + let derivedKey = try pbkdf2(password: password, salt: salt) + + let nonce = Data.randomBytes(12) + let symmetricKey = SymmetricKey(data: derivedKey) + let gcmNonce = try AES.GCM.Nonce(data: nonce) + + // AAD = ECP1 magic bytes (matching Python) + let sealedBox = try AES.GCM.seal( + rawBytes, + using: symmetricKey, + nonce: gcmNonce, + authenticating: Constants.ecp1Magic + ) + + // ciphertext + tag concatenated (matches Python's AESGCM.encrypt output) + var result = Data() + result.append(Constants.ecp1Magic) // 4 bytes + result.append(salt) // 16 bytes + result.append(nonce) // 12 bytes + result.append(sealedBox.ciphertext) // N bytes + result.append(sealedBox.tag) // 16 bytes + return result + } + + /// Decrypt ECP1-encrypted key bytes with password + static func decrypt(_ data: Data, password: Data) throws -> Data { + guard data.count >= 48 else { // 4 + 16 + 12 + 16 minimum + throw CryptoError.invalidECP1Format + } + guard data.prefix(4) == Constants.ecp1Magic else { + throw CryptoError.invalidECP1Format + } + + let salt = data[4..<20] + let nonce = data[20..<32] + let ctWithTag = data[32...] + + guard ctWithTag.count >= 16 else { + throw CryptoError.invalidECP1Format + } + + let derivedKey = try pbkdf2(password: password, salt: Data(salt)) + let symmetricKey = SymmetricKey(data: derivedKey) + let gcmNonce = try AES.GCM.Nonce(data: nonce) + + // Split ciphertext and tag + let ct = ctWithTag.prefix(ctWithTag.count - 16) + let tag = ctWithTag.suffix(16) + + let sealedBox = try AES.GCM.SealedBox( + nonce: gcmNonce, + ciphertext: ct, + tag: tag + ) + + do { + return try AES.GCM.open(sealedBox, using: symmetricKey, authenticating: Constants.ecp1Magic) + } catch { + throw CryptoError.decryptionFailed("ECP1 decryption failed - wrong password?") + } + } + + /// Check if data starts with ECP1 magic + static func isECP1Format(_ data: Data) -> Bool { + data.count >= 4 && data.prefix(4) == Constants.ecp1Magic + } + + // MARK: - PBKDF2 + + /// Derive 32-byte key using PBKDF2-HMAC-SHA256 with 600k iterations + static func pbkdf2(password: Data, salt: Data) throws -> Data { + var derivedKey = Data(count: 32) + let status = derivedKey.withUnsafeMutableBytes { derivedKeyPtr in + password.withUnsafeBytes { passwordPtr in + salt.withUnsafeBytes { saltPtr in + CCKeyDerivationPBKDF( + CCPBKDFAlgorithm(kCCPBKDF2), + passwordPtr.baseAddress?.assumingMemoryBound(to: Int8.self), + password.count, + saltPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), + salt.count, + CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), + Constants.pbkdf2Iterations, + derivedKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), + 32 + ) + } + } + } + guard status == kCCSuccess else { + throw CryptoError.pbkdf2Failed + } + return derivedKey + } +} diff --git a/ios_client/EncryptedChat/Crypto/RSACrypto.swift b/ios_client/EncryptedChat/Crypto/RSACrypto.swift new file mode 100644 index 0000000..6e027a3 --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/RSACrypto.swift @@ -0,0 +1,309 @@ +import Foundation +import Security + +/// RSA-4096 operations — used for login challenge-response ONLY +enum RSACrypto { + + // MARK: - Key Generation + + /// Generate RSA-4096 keypair + static func generateKeypair() throws -> (privateKey: SecKey, publicKey: SecKey) { + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeySizeInBits as String: 4096, + ] + + var error: Unmanaged? + guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + throw CryptoError.rsaKeyGenerationFailed + } + guard let publicKey = SecKeyCopyPublicKey(privateKey) else { + throw CryptoError.rsaKeyGenerationFailed + } + return (privateKey, publicKey) + } + + // MARK: - Serialization + + /// Serialize RSA private key. With password: DER → ECP1. Without: PEM PKCS#8. + static func serializePrivateKey(_ key: SecKey, password: Data? = nil) throws -> Data { + var error: Unmanaged? + guard let derData = SecKeyCopyExternalRepresentation(key, &error) as Data? else { + throw CryptoError.rsaOperationFailed("Failed to export private key") + } + + // SecKey exports in PKCS#1 format on iOS — wrap in PKCS#8 for Python compat + let pkcs8 = wrapRSAPrivateKeyPKCS8(derData) + + if let password = password { + return try KeyEncryption.encrypt(pkcs8, password: password) + } + + // PEM encode for Python compatibility + return pemEncode(pkcs8, label: "PRIVATE KEY") + } + + /// Serialize RSA public key as PEM SubjectPublicKeyInfo (Python-compatible) + static func serializePublicKey(_ key: SecKey) throws -> Data { + var error: Unmanaged? + guard let derData = SecKeyCopyExternalRepresentation(key, &error) as Data? else { + throw CryptoError.rsaOperationFailed("Failed to export public key") + } + + // SecKey exports PKCS#1 on iOS — wrap in SubjectPublicKeyInfo + let spki = wrapRSAPublicKeySPKI(derData) + return pemEncode(spki, label: "PUBLIC KEY") + } + + /// Load RSA private key. Auto-detects ECP1 vs PEM format. + static func loadPrivateKey(_ data: Data, password: Data? = nil) throws -> SecKey { + let derData: Data + + if KeyEncryption.isECP1Format(data) { + guard let pwd = password else { + throw CryptoError.invalidKeyData("ECP1 key requires password") + } + let raw = try KeyEncryption.decrypt(data, password: pwd) + derData = unwrapPKCS8ToRSAPrivateKey(raw) + } else { + // PEM format + let pem = String(data: data, encoding: .utf8) ?? "" + derData = try pemDecode(pem, label: "PRIVATE KEY") + .flatMap { unwrapPKCS8ToRSAPrivateKey($0) } + ?? pemDecode(pem, label: "RSA PRIVATE KEY") + ?? { throw CryptoError.invalidKeyData("Cannot parse RSA private key PEM") }() + } + + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, + ] + + var error: Unmanaged? + guard let key = SecKeyCreateWithData(derData as CFData, attributes as CFDictionary, &error) else { + throw CryptoError.invalidKeyData("Failed to create RSA private key from DER") + } + return key + } + + /// Load RSA public key from PEM + static func loadPublicKey(_ pemData: Data) throws -> SecKey { + let pem = String(data: pemData, encoding: .utf8) ?? "" + + // Try SubjectPublicKeyInfo (PUBLIC KEY), unwrap to PKCS#1 + let derData: Data + if let spki = pemDecode(pem, label: "PUBLIC KEY") { + derData = unwrapSPKIToRSAPublicKey(spki) + } else if let pkcs1 = pemDecode(pem, label: "RSA PUBLIC KEY") { + derData = pkcs1 + } else { + throw CryptoError.invalidKeyData("Cannot parse RSA public key PEM") + } + + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPublic, + ] + + var error: Unmanaged? + guard let key = SecKeyCreateWithData(derData as CFData, attributes as CFDictionary, &error) else { + throw CryptoError.invalidKeyData("Failed to create RSA public key from DER") + } + return key + } + + // MARK: - Sign / Verify + + /// Sign data with RSA-PSS SHA-256. + /// Note: iOS uses salt_length = hash_length (32). Server must use PSS.AUTO to verify. + static func sign(_ privateKey: SecKey, data: Data) throws -> Data { + var error: Unmanaged? + guard let signature = SecKeyCreateSignature( + privateKey, + .rsaSignatureMessagePSSSHA256, + data as CFData, + &error + ) as Data? else { + throw CryptoError.rsaOperationFailed("RSA signing failed") + } + return signature + } + + /// Verify RSA-PSS SHA-256 signature + static func verify(_ publicKey: SecKey, signature: Data, data: Data) -> Bool { + SecKeyVerifySignature( + publicKey, + .rsaSignatureMessagePSSSHA256, + data as CFData, + signature as CFData, + nil + ) + } + + // MARK: - PEM Helpers + + private static func pemEncode(_ der: Data, label: String) -> Data { + let base64 = der.base64EncodedString(options: .lineLength64Characters) + let pem = "-----BEGIN \(label)-----\n\(base64)\n-----END \(label)-----\n" + return Data(pem.utf8) + } + + private static func pemDecode(_ pem: String, label: String) -> Data? { + let beginMarker = "-----BEGIN \(label)-----" + let endMarker = "-----END \(label)-----" + + guard let beginRange = pem.range(of: beginMarker), + let endRange = pem.range(of: endMarker) else { + return nil + } + + let base64String = pem[beginRange.upperBound.. Data { + // PrivateKeyInfo ::= SEQUENCE { + // version INTEGER (0), + // algorithm AlgorithmIdentifier, + // privateKey OCTET STRING (containing PKCS#1 key) + // } + let version = Data([0x02, 0x01, 0x00]) // INTEGER 0 + let algorithmSeq = asn1Sequence(Data(rsaOID) + Data(nullParam)) + let privateKeyOctet = asn1OctetString(pkcs1) + return asn1Sequence(version + algorithmSeq + privateKeyOctet) + } + + /// Unwrap PKCS#8 to get PKCS#1 RSA private key + private static func unwrapPKCS8ToRSAPrivateKey(_ pkcs8: Data) -> Data { + // Parse SEQUENCE, skip version + algorithm, extract OCTET STRING + guard pkcs8.count > 2 else { return pkcs8 } + + var offset = 0 + // Outer SEQUENCE + guard pkcs8[offset] == 0x30 else { return pkcs8 } + offset += 1 + offset = skipASN1Length(pkcs8, offset: offset) + + // Version INTEGER + guard offset < pkcs8.count, pkcs8[offset] == 0x02 else { return pkcs8 } + offset += 1 + let versionLen = readASN1Length(pkcs8, offset: &offset) + offset += versionLen + + // Algorithm SEQUENCE + guard offset < pkcs8.count, pkcs8[offset] == 0x30 else { return pkcs8 } + offset += 1 + let algoLen = readASN1Length(pkcs8, offset: &offset) + offset += algoLen + + // Private key OCTET STRING + guard offset < pkcs8.count, pkcs8[offset] == 0x04 else { return pkcs8 } + offset += 1 + let keyLen = readASN1Length(pkcs8, offset: &offset) + guard offset + keyLen <= pkcs8.count else { return pkcs8 } + return Data(pkcs8[offset..<(offset + keyLen)]) + } + + /// Wrap PKCS#1 RSA public key in SubjectPublicKeyInfo + private static func wrapRSAPublicKeySPKI(_ pkcs1: Data) -> Data { + // SubjectPublicKeyInfo ::= SEQUENCE { + // algorithm AlgorithmIdentifier, + // subjectPublicKey BIT STRING (containing PKCS#1 key) + // } + let algorithmSeq = asn1Sequence(Data(rsaOID) + Data(nullParam)) + let bitString = asn1BitString(pkcs1) + return asn1Sequence(algorithmSeq + bitString) + } + + /// Unwrap SubjectPublicKeyInfo to get PKCS#1 RSA public key + private static func unwrapSPKIToRSAPublicKey(_ spki: Data) -> Data { + guard spki.count > 2 else { return spki } + + var offset = 0 + // Outer SEQUENCE + guard spki[offset] == 0x30 else { return spki } + offset += 1 + offset = skipASN1Length(spki, offset: offset) + + // Algorithm SEQUENCE + guard offset < spki.count, spki[offset] == 0x30 else { return spki } + offset += 1 + let algoLen = readASN1Length(spki, offset: &offset) + offset += algoLen + + // BIT STRING + guard offset < spki.count, spki[offset] == 0x03 else { return spki } + offset += 1 + let bitLen = readASN1Length(spki, offset: &offset) + // Skip the unused bits byte + guard offset < spki.count, spki[offset] == 0x00 else { return spki } + offset += 1 + let keyLen = bitLen - 1 + guard offset + keyLen <= spki.count else { return spki } + return Data(spki[offset..<(offset + keyLen)]) + } + + // MARK: - ASN.1 Primitives + + private static func asn1Length(_ length: Int) -> Data { + if length < 0x80 { + return Data([UInt8(length)]) + } else if length <= 0xFF { + return Data([0x81, UInt8(length)]) + } else if length <= 0xFFFF { + return Data([0x82, UInt8(length >> 8), UInt8(length & 0xFF)]) + } else { + return Data([0x83, UInt8(length >> 16), UInt8((length >> 8) & 0xFF), UInt8(length & 0xFF)]) + } + } + + private static func asn1Sequence(_ content: Data) -> Data { + Data([0x30]) + asn1Length(content.count) + content + } + + private static func asn1OctetString(_ content: Data) -> Data { + Data([0x04]) + asn1Length(content.count) + content + } + + private static func asn1BitString(_ content: Data) -> Data { + // BIT STRING: tag + length + unused_bits(0) + content + Data([0x03]) + asn1Length(content.count + 1) + Data([0x00]) + content + } + + private static func readASN1Length(_ data: Data, offset: inout Int) -> Int { + guard offset < data.count else { return 0 } + let first = data[offset] + offset += 1 + if first < 0x80 { + return Int(first) + } + let numBytes = Int(first & 0x7F) + var length = 0 + for _ in 0.. Int { + var off = offset + _ = readASN1Length(data, offset: &off) + return off + } +} diff --git a/ios_client/EncryptedChat/Crypto/SenderKeyState.swift b/ios_client/EncryptedChat/Crypto/SenderKeyState.swift new file mode 100644 index 0000000..63cc53d --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/SenderKeyState.swift @@ -0,0 +1,175 @@ +import Foundation +import CryptoKit + +/// Sender key chain for group messaging. +/// Each sender in a group has their own chain. Others receive the initial key via pairwise ratchet. +/// Matches Python: SenderKeyState class in crypto_utils.py +class SenderKeyState { + + let senderKey: Data + let chainId: Data + private(set) var chainKey: Data + private(set) var n: Int + private var knownKeys: [Int: Data] + + /// Initialize with optional sender key (generates random 32B if nil). + /// Matches Python: SenderKeyState.__init__(sender_key=None) + init(senderKey: Data? = nil) { + let key = senderKey ?? Data.randomBytes(32) + self.senderKey = key + self.chainId = Data(SHA256.hash(data: key)) + self.chainKey = CryptoUtils.hkdfDerive( + inputKey: key, + salt: Data(repeating: 0x00, count: 32), + info: Data(Constants.senderKeyChainInfo.utf8), + length: 32 + ) + self.n = 0 + self.knownKeys = [:] + } + + /// Private init for import + private init(senderKey: Data, chainId: Data, chainKey: Data, n: Int, knownKeys: [Int: Data]) { + self.senderKey = senderKey + self.chainId = chainId + self.chainKey = chainKey + self.n = n + self.knownKeys = knownKeys + } + + // MARK: - Encrypt + + /// Encrypt with current chain key. + /// Returns (chainId hex, n, ciphertext with tag, nonce). + /// Matches Python: SenderKeyState.encrypt(plaintext) + func encrypt(_ plaintext: Data) throws -> (chainIdHex: String, n: Int, ciphertext: Data, nonce: Data) { + let (newCK, messageKey) = CryptoUtils.kdfCK(chainKey: chainKey) + chainKey = newCK + + let nonce = Data.randomBytes(12) + // AAD = chainId + bigEndian(UInt32(n)) + let aad = chainId + UInt32(n).bigEndianData + let ctWithTag = try CryptoUtils.aesGcmEncrypt(plaintext, key: messageKey, nonce: nonce, aad: aad) + + let result = (chainIdHex: chainId.hexString, n: n, ciphertext: ctWithTag, nonce: nonce) + n += 1 + return result + } + + // MARK: - Decrypt + + /// Decrypt a group message. Fast-forwards the chain if needed. + /// State is snapshotted before modification and restored on failure. + /// Matches Python: SenderKeyState.decrypt(chain_id_hex, n, ciphertext, nonce) + func decrypt(chainIdHex: String, n: Int, ciphertext: Data, nonce: Data) throws -> Data { + guard let expectedChainId = Data(hexString: chainIdHex) else { + throw CryptoError.senderKeyError("Invalid chain ID hex") + } + guard expectedChainId == chainId else { + throw CryptoError.senderKeyError("Chain ID mismatch") + } + + if n - self.n > Constants.maxSenderKeySkip { + throw CryptoError.senderKeyError("Sender key skip too large (\(n - self.n) > \(Constants.maxSenderKeySkip))") + } + + // Snapshot before fast-forward + let snapChainKey = chainKey + let snapN = self.n + let snapKnown = knownKeys + + do { + // Fast-forward the chain to reach message n + while self.n <= n { + let (newCK, mk) = CryptoUtils.kdfCK(chainKey: chainKey) + chainKey = newCK + knownKeys[self.n] = mk + self.n += 1 + } + + guard let mk = knownKeys.removeValue(forKey: n) else { + throw CryptoError.senderKeyError("Message key for n=\(n) not available") + } + + let aad = chainId + UInt32(n).bigEndianData + return try CryptoUtils.aesGcmDecrypt(ciphertext, key: mk, nonce: nonce, aad: aad) + } catch { + // Restore state on failure + chainKey = snapChainKey + self.n = snapN + knownKeys = snapKnown + throw error + } + } + + // MARK: - Key Export/Import + + /// Export sender key for distribution to group members. + /// Matches Python: SenderKeyState.export_key() + func exportKey() -> Data { + let dict: [String: Any] = ["sender_key": senderKey.hexString] + return try! JSONSerialization.data(withJSONObject: dict) + } + + /// Initialize a receiving SenderKeyState from an exported key. + /// Matches Python: SenderKeyState.from_key(exported_key) + static func fromKey(_ exportedKey: Data) throws -> SenderKeyState { + guard let dict = try JSONSerialization.jsonObject(with: exportedKey) as? [String: Any], + let senderKeyHex = dict["sender_key"] as? String, + let senderKey = Data(hexString: senderKeyHex) else { + throw CryptoError.stateImportFailed("Invalid sender key export") + } + return SenderKeyState(senderKey: senderKey) + } + + // MARK: - Full State Export/Import + + /// Serialize full state for persistent storage. + /// Matches Python: SenderKeyState.export_state() + func exportState() -> Data { + var knownKeysDict: [String: String] = [:] + for (k, v) in knownKeys { + knownKeysDict[String(k)] = v.hexString + } + let state: [String: Any] = [ + "sender_key": senderKey.hexString, + "chain_id": chainId.hexString, + "chain_key": chainKey.hexString, + "n": n, + "known_keys": knownKeysDict, + ] + return try! JSONSerialization.data(withJSONObject: state) + } + + /// Deserialize full state. + /// Matches Python: SenderKeyState.import_state(data) + static func importState(_ data: Data) throws -> SenderKeyState { + guard let state = try JSONSerialization.jsonObject(with: data) as? [String: Any], + let senderKeyHex = state["sender_key"] as? String, + let senderKey = Data(hexString: senderKeyHex), + let chainIdHex = state["chain_id"] as? String, + let chainId = Data(hexString: chainIdHex), + let chainKeyHex = state["chain_key"] as? String, + let chainKey = Data(hexString: chainKeyHex), + let n = state["n"] as? Int else { + throw CryptoError.stateImportFailed("Invalid sender key state") + } + + var knownKeys: [Int: Data] = [:] + if let knownKeysDict = state["known_keys"] as? [String: String] { + for (k, v) in knownKeysDict { + if let idx = Int(k), let data = Data(hexString: v) { + knownKeys[idx] = data + } + } + } + + return SenderKeyState( + senderKey: senderKey, + chainId: chainId, + chainKey: chainKey, + n: n, + knownKeys: knownKeys + ) + } +} diff --git a/ios_client/EncryptedChat/Crypto/X25519Crypto.swift b/ios_client/EncryptedChat/Crypto/X25519Crypto.swift new file mode 100644 index 0000000..5431489 --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/X25519Crypto.swift @@ -0,0 +1,77 @@ +import Foundation +import CryptoKit + +/// X25519 Diffie-Hellman key agreement +enum X25519Crypto { + + // MARK: - Key Generation + + /// Generate X25519 keypair + static func generateKeypair() -> (privateKey: Curve25519.KeyAgreement.PrivateKey, publicKey: Curve25519.KeyAgreement.PublicKey) { + let privateKey = Curve25519.KeyAgreement.PrivateKey() + return (privateKey, privateKey.publicKey) + } + + // MARK: - Serialization + + /// Serialize X25519 private key to 32 raw bytes + static func serializePrivate(_ key: Curve25519.KeyAgreement.PrivateKey) -> Data { + key.rawData // 32 bytes + } + + /// Serialize X25519 public key to 32 raw bytes + static func serializePublic(_ key: Curve25519.KeyAgreement.PublicKey) -> Data { + key.rawData // 32 bytes + } + + /// Load X25519 private key from 32 raw bytes + static func loadPrivate(_ data: Data) throws -> Curve25519.KeyAgreement.PrivateKey { + guard data.count == 32 else { + throw CryptoError.invalidKeyData("X25519 private key must be 32 bytes") + } + return try Curve25519.KeyAgreement.PrivateKey(rawRepresentation: data) + } + + /// Load X25519 public key from 32 raw bytes + static func loadPublic(_ data: Data) throws -> Curve25519.KeyAgreement.PublicKey { + guard data.count == 32 else { + throw CryptoError.invalidKeyData("X25519 public key must be 32 bytes") + } + return try Curve25519.KeyAgreement.PublicKey(rawRepresentation: data) + } + + // MARK: - Diffie-Hellman + + /// Perform X25519 DH key agreement. Returns 32-byte shared secret. + /// Matches Python: x25519_dh(private_key, public_key) + static func dh(_ privateKey: Curve25519.KeyAgreement.PrivateKey, _ publicKey: Curve25519.KeyAgreement.PublicKey) throws -> Data { + let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(with: publicKey) + // Extract raw bytes from SharedSecret + return sharedSecret.withUnsafeBytes { Data($0) } + } + + // MARK: - Ed25519 → X25519 Key Conversion + + /// Convert Ed25519 private key to X25519 private key. + /// SHA-512(seed) → take first 32 bytes → clamp per RFC 7748 + /// Matches Python: ed25519_private_to_x25519(ed_private) + static func fromEd25519Private(_ edPrivate: Curve25519.Signing.PrivateKey) throws -> Curve25519.KeyAgreement.PrivateKey { + let raw = edPrivate.rawData // 32 bytes seed + // SHA-512 of the seed + let hash = SHA512.hash(data: raw) + var clamped = Data(hash.prefix(32)) + // Clamp per RFC 7748 + clamped[0] &= 248 + clamped[31] &= 127 + clamped[31] |= 64 + return try Curve25519.KeyAgreement.PrivateKey(rawRepresentation: clamped) + } + + /// Convert Ed25519 public key to X25519 public key. + /// Uses Montgomery birational map: u = (1+y)/(1-y) mod p + /// Matches Python: ed25519_public_to_x25519(ed_public) + static func fromEd25519Public(_ edPublic: Curve25519.Signing.PublicKey) throws -> Curve25519.KeyAgreement.PublicKey { + let x25519Bytes = FieldArithmetic.ed25519PublicToX25519(edPublic.rawData) + return try Curve25519.KeyAgreement.PublicKey(rawRepresentation: x25519Bytes) + } +} diff --git a/ios_client/EncryptedChat/Crypto/X3DH.swift b/ios_client/EncryptedChat/Crypto/X3DH.swift new file mode 100644 index 0000000..f99b602 --- /dev/null +++ b/ios_client/EncryptedChat/Crypto/X3DH.swift @@ -0,0 +1,118 @@ +import Foundation +import CryptoKit + +/// X3DH key agreement protocol (Signal Protocol) +enum X3DH { + + // MARK: - Pre-Key Generation + + /// Generate a signed pre-key (SPK). + /// Returns (private, public, signature, id). + /// Matches Python: generate_signed_prekey(identity_private) + static func generateSignedPrekey( + identityPrivate: Curve25519.Signing.PrivateKey + ) throws -> (privateKey: Curve25519.KeyAgreement.PrivateKey, + publicKey: Curve25519.KeyAgreement.PublicKey, + signature: Data, + id: String) { + let (spkPriv, spkPub) = X25519Crypto.generateKeypair() + let spkPubBytes = X25519Crypto.serializePublic(spkPub) + let signature = try Ed25519Crypto.sign(identityPrivate, data: spkPubBytes) + return (spkPriv, spkPub, signature, UUID().uuidString) + } + + /// Generate a batch of one-time pre-keys. + /// Matches Python: generate_one_time_prekeys(count=50) + static func generateOneTimePrekeys(count: Int = 50) -> [(privateKey: Curve25519.KeyAgreement.PrivateKey, + publicKey: Curve25519.KeyAgreement.PublicKey, + id: String)] { + (0.. (sharedSecret: Data, + ephemeralPrivate: Curve25519.KeyAgreement.PrivateKey, + ephemeralPublic: Curve25519.KeyAgreement.PublicKey) { + // Verify SPK signature + let spkRemoteBytes = X25519Crypto.serializePublic(spkRemote) + guard Ed25519Crypto.verify(ikPublicRemoteEd, signature: spkSignature, data: spkRemoteBytes) else { + throw CryptoError.x3dhFailed("Invalid SPK signature") + } + + // Convert identity keys to X25519 + let ikX25519Private = try X25519Crypto.fromEd25519Private(ikPrivateEd) + let ikX25519Remote = try X25519Crypto.fromEd25519Public(ikPublicRemoteEd) + + // Generate ephemeral keypair + let (ekPriv, ekPub) = X25519Crypto.generateKeypair() + + // DH computations + let dh1 = try X25519Crypto.dh(ikX25519Private, spkRemote) // IK_A, SPK_B + let dh2 = try X25519Crypto.dh(ekPriv, ikX25519Remote) // EK_A, IK_B + let dh3 = try X25519Crypto.dh(ekPriv, spkRemote) // EK_A, SPK_B + + var dhConcat = dh1 + dh2 + dh3 + if let opk = opkRemote { + let dh4 = try X25519Crypto.dh(ekPriv, opk) // EK_A, OPK_B + dhConcat += dh4 + } + + // Derive shared secret + let sharedSecret = CryptoUtils.hkdfDerive( + inputKey: dhConcat, + salt: Data(repeating: 0x00, count: 32), + info: Data(Constants.x3dhInfo.utf8), + length: 32 + ) + + return (sharedSecret, ekPriv, ekPub) + } + + // MARK: - X3DH Respond (Bob) + + /// Responder side of X3DH. + /// Returns sharedSecret. + /// Matches Python: x3dh_respond(ik_private_ed, spk_private, ik_remote_ed, ek_remote, opk_private?) + static func respond( + ikPrivateEd: Curve25519.Signing.PrivateKey, + spkPrivate: Curve25519.KeyAgreement.PrivateKey, + ikRemoteEd: Curve25519.Signing.PublicKey, + ekRemote: Curve25519.KeyAgreement.PublicKey, + opkPrivate: Curve25519.KeyAgreement.PrivateKey? = nil + ) throws -> Data { + let ikX25519Private = try X25519Crypto.fromEd25519Private(ikPrivateEd) + let ikX25519Remote = try X25519Crypto.fromEd25519Public(ikRemoteEd) + + let dh1 = try X25519Crypto.dh(spkPrivate, ikX25519Remote) // SPK_B, IK_A + let dh2 = try X25519Crypto.dh(ikX25519Private, ekRemote) // IK_B, EK_A + let dh3 = try X25519Crypto.dh(spkPrivate, ekRemote) // SPK_B, EK_A + + var dhConcat = dh1 + dh2 + dh3 + if let opk = opkPrivate { + let dh4 = try X25519Crypto.dh(opk, ekRemote) // OPK_B, EK_A + dhConcat += dh4 + } + + let sharedSecret = CryptoUtils.hkdfDerive( + inputKey: dhConcat, + salt: Data(repeating: 0x00, count: 32), + info: Data(Constants.x3dhInfo.utf8), + length: 32 + ) + + return sharedSecret + } +} diff --git a/ios_client/EncryptedChat/Models/Conversation.swift b/ios_client/EncryptedChat/Models/Conversation.swift new file mode 100644 index 0000000..c005883 --- /dev/null +++ b/ios_client/EncryptedChat/Models/Conversation.swift @@ -0,0 +1,46 @@ +import Foundation + +struct Conversation: Identifiable, Equatable { + let id: String + var name: String? + var members: [ConversationMember] + var createdBy: String? + var avatarFile: String? + var unreadCount: Int + var isFavorite: Bool + var lastMessageTime: Date? + + var isGroup: Bool { + name != nil || members.count > 2 + } + + /// Display name: group name, or DM partner username + func displayName(currentUserId: String) -> String { + if let name = name, !name.isEmpty { + return name + } + // DM: show the other person's name + if let other = members.first(where: { $0.userId != currentUserId }) { + return other.username + } + return "Unknown" + } + + /// DM partner user ID (nil for groups) + func dmPartnerId(currentUserId: String) -> String? { + guard !isGroup else { return nil } + return members.first(where: { $0.userId != currentUserId })?.userId + } + + static func == (lhs: Conversation, rhs: Conversation) -> Bool { + lhs.id == rhs.id + } +} + +struct ConversationMember: Identifiable, Equatable, Codable { + let userId: String + var username: String + var email: String + + var id: String { userId } +} diff --git a/ios_client/EncryptedChat/Models/DeviceBundle.swift b/ios_client/EncryptedChat/Models/DeviceBundle.swift new file mode 100644 index 0000000..1192c46 --- /dev/null +++ b/ios_client/EncryptedChat/Models/DeviceBundle.swift @@ -0,0 +1,43 @@ +import Foundation + +/// Key bundle for one device, used in X3DH +struct DeviceBundle { + let deviceId: String + let identityKey: Data // Ed25519 public key (32 bytes) + let spk: Data // X25519 public key (32 bytes) + let spkSignature: Data // Ed25519 signature (64 bytes) + let spkId: String + let opk: Data? // X25519 public key (32 bytes), optional + let opkId: String? + + /// Parse from server response dictionary + static func fromDict(_ dict: [String: Any]) throws -> DeviceBundle { + guard let deviceId = dict["device_id"] as? String, + let ikHex = dict["identity_key"] as? String, + let ik = Data(hexString: ikHex), + let spkHex = dict["spk"] as? String, + let spk = Data(hexString: spkHex), + let spkSigHex = dict["spk_signature"] as? String, + let spkSig = Data(hexString: spkSigHex), + let spkId = dict["spk_id"] as? String else { + throw ChatError.invalidData("Invalid device bundle") + } + + var opk: Data? + var opkId: String? + if let opkHex = dict["opk"] as? String, let opkData = Data(hexString: opkHex) { + opk = opkData + opkId = dict["opk_id"] as? String + } + + return DeviceBundle( + deviceId: deviceId, + identityKey: ik, + spk: spk, + spkSignature: spkSig, + spkId: spkId, + opk: opk, + opkId: opkId + ) + } +} diff --git a/ios_client/EncryptedChat/Models/Invitation.swift b/ios_client/EncryptedChat/Models/Invitation.swift new file mode 100644 index 0000000..6b0631c --- /dev/null +++ b/ios_client/EncryptedChat/Models/Invitation.swift @@ -0,0 +1,9 @@ +import Foundation + +struct Invitation: Identifiable { + let id: String // invitation id (from server) or conversationId + let conversationId: String + let conversationName: String + let invitedBy: String + let invitedByUsername: String +} diff --git a/ios_client/EncryptedChat/Models/Message.swift b/ios_client/EncryptedChat/Models/Message.swift new file mode 100644 index 0000000..ae95bd5 --- /dev/null +++ b/ios_client/EncryptedChat/Models/Message.swift @@ -0,0 +1,33 @@ +import Foundation + +struct Message: Identifiable, Equatable { + let id: String + let conversationId: String + let senderId: String + var senderUsername: String + let createdAt: Date + var text: String? + var replyTo: String? + var imageFileId: String? + var file: FileInfo? + var isDeleted: Bool + var readBy: Set + + /// Whether this is a self-sent message + func isMine(currentUserId: String) -> Bool { + senderId == currentUserId + } + + static func == (lhs: Message, rhs: Message) -> Bool { + lhs.id == rhs.id + } +} + +struct FileInfo: Equatable, Codable { + let fileId: String + let aesKey: String // hex + let iv: String // hex + let filename: String + let size: Int + let mimeType: String +} diff --git a/ios_client/EncryptedChat/Models/User.swift b/ios_client/EncryptedChat/Models/User.swift new file mode 100644 index 0000000..0e429b3 --- /dev/null +++ b/ios_client/EncryptedChat/Models/User.swift @@ -0,0 +1,19 @@ +import Foundation + +struct User: Identifiable, Equatable { + let id: String + var username: String + var email: String + var identityKey: Data? // Ed25519 public key (32 bytes) +} + +struct UserProfile: Equatable { + var userId: String + var username: String? + var email: String? + var phone: String? + var phoneVisible: Bool + var location: String? + var locationVisible: Bool + var avatarFile: String? +} diff --git a/ios_client/EncryptedChat/Network/ConnectionManager.swift b/ios_client/EncryptedChat/Network/ConnectionManager.swift new file mode 100644 index 0000000..e184729 --- /dev/null +++ b/ios_client/EncryptedChat/Network/ConnectionManager.swift @@ -0,0 +1,188 @@ +import Foundation +import Network + +/// TCP connection manager using Network.framework. +/// Handles connection lifecycle, TLS, buffered reading (newline-delimited), and writing. +actor ConnectionManager { + + enum ConnectionState: Equatable { + case disconnected + case connecting + case connected + case failed(String) + } + + private var connection: NWConnection? + private var receiveBuffer = Data() + private(set) var state: ConnectionState = .disconnected + private var stateCallback: ((ConnectionState) -> Void)? + private var messageStream: AsyncStream<[String: Any]>.Continuation? + + /// Set a callback for connection state changes + func onStateChange(_ callback: @escaping (ConnectionState) -> Void) { + stateCallback = callback + } + + // MARK: - Connect / Disconnect + + /// Connect to server + func connect(host: String, port: UInt16, useTLS: Bool = false, tlsInsecure: Bool = false) async throws { + guard state == .disconnected || state != .connected else { + throw NetworkError.alreadyConnected + } + + updateState(.connecting) + + let nwHost = NWEndpoint.Host(host) + let nwPort = NWEndpoint.Port(rawValue: port)! + + let params: NWParameters + if useTLS { + let tlsOptions = NWProtocolTLS.Options() + if tlsInsecure { + // Skip certificate verification (dev only) + sec_protocol_options_set_verify_block( + tlsOptions.securityProtocolOptions, + { _, _, completionHandler in completionHandler(true) }, + .main + ) + } + params = NWParameters(tls: tlsOptions, tcp: .init()) + } else { + params = .tcp + } + + let conn = NWConnection(host: nwHost, port: nwPort, using: params) + self.connection = conn + self.receiveBuffer = Data() + + return try await withCheckedThrowingContinuation { continuation in + conn.stateUpdateHandler = { [weak self] newState in + Task { [weak self] in + guard let self = self else { return } + switch newState { + case .ready: + await self.updateState(.connected) + continuation.resume() + case .failed(let error): + await self.updateState(.failed(error.localizedDescription)) + continuation.resume(throwing: NetworkError.connectionFailed(error.localizedDescription)) + case .cancelled: + await self.updateState(.disconnected) + case .waiting(let error): + await self.updateState(.failed(error.localizedDescription)) + continuation.resume(throwing: NetworkError.connectionFailed("Waiting: \(error.localizedDescription)")) + default: + break + } + } + } + conn.start(queue: .global(qos: .userInitiated)) + } + } + + /// Disconnect from server + func disconnect() { + connection?.cancel() + connection = nil + receiveBuffer = Data() + updateState(.disconnected) + messageStream?.finish() + messageStream = nil + } + + // MARK: - Send + + /// Send raw data over the connection + func send(_ data: Data) async throws { + guard let connection = connection, state == .connected else { + throw NetworkError.notConnected + } + + return try await withCheckedThrowingContinuation { continuation in + connection.send(content: data, completion: .contentProcessed { error in + if let error = error { + continuation.resume(throwing: NetworkError.connectionFailed(error.localizedDescription)) + } else { + continuation.resume() + } + }) + } + } + + /// Send a protocol message (builds JSON + newline, sends) + func sendMessage(type: String, requestId: String? = nil, params: [String: Any] = [:]) async throws { + let data = try ProtocolHandler.buildRequest(type: type, requestId: requestId, params: params) + try await send(data) + } + + // MARK: - Receive + + /// Read one newline-delimited JSON message. + /// Returns nil on EOF / connection close. + func readMessage() async throws -> [String: Any]? { + while true { + // Check buffer for a complete line + if let newlineIndex = receiveBuffer.firstIndex(of: 0x0A) { + let lineData = receiveBuffer.prefix(through: newlineIndex) + receiveBuffer.removeSubrange(...newlineIndex) + + // Check size + if lineData.count > Constants.maxMessageBytes { + throw NetworkError.messageTooLarge + } + + return try ProtocolHandler.parseMessage(Data(lineData)) + } + + // Buffer doesn't have a complete line — read more from the connection + guard let connection = connection else { + return nil + } + + let chunk = try await receiveChunk(connection: connection) + guard let chunk = chunk else { + return nil // EOF + } + + receiveBuffer.append(chunk) + + // Safety: if buffer exceeds max without a newline, drop it + if receiveBuffer.count > Constants.maxMessageBytes * 2 { + receiveBuffer = Data() + throw NetworkError.messageTooLarge + } + } + } + + /// Read a chunk of data from the connection + private func receiveChunk(connection: NWConnection) async throws -> Data? { + return try await withCheckedThrowingContinuation { continuation in + connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { content, _, isComplete, error in + if let error = error { + continuation.resume(throwing: NetworkError.connectionFailed(error.localizedDescription)) + return + } + if let content = content, !content.isEmpty { + continuation.resume(returning: content) + } else if isComplete { + continuation.resume(returning: nil) + } else { + // No data and not complete — shouldn't happen but return nil + continuation.resume(returning: nil) + } + } + } + } + + // MARK: - State + + var isConnected: Bool { + state == .connected + } + + private func updateState(_ newState: ConnectionState) { + state = newState + stateCallback?(newState) + } +} diff --git a/ios_client/EncryptedChat/Network/ProtocolHandler.swift b/ios_client/EncryptedChat/Network/ProtocolHandler.swift new file mode 100644 index 0000000..2ed65c9 --- /dev/null +++ b/ios_client/EncryptedChat/Network/ProtocolHandler.swift @@ -0,0 +1,90 @@ +import Foundation + +/// Newline-delimited JSON protocol handler. +/// Matches Python: protocol.py build_request, build_response, parse_message, encode_binary, decode_binary +enum ProtocolHandler { + + /// Build a request message (newline-terminated JSON). + /// Matches Python: build_request(msg_type, request_id=None, **kwargs) + static func buildRequest(type: String, requestId: String? = nil, params: [String: Any] = [:]) throws -> Data { + var msg: [String: Any] = ["type": type] + if let requestId = requestId { + msg["request_id"] = requestId + } + // Merge params into msg + for (key, value) in params { + msg[key] = value + } + + let jsonData = try JSONSerialization.data(withJSONObject: msg) + guard jsonData.count < Constants.maxMessageBytes else { + throw NetworkError.messageTooLarge + } + return jsonData + Data([0x0A]) // newline + } + + /// Build a response message (newline-terminated JSON). + static func buildResponse(type: String, status: String, data: [String: Any]? = nil, requestId: String? = nil) throws -> Data { + var msg: [String: Any] = ["type": type, "status": status] + if let data = data { + msg["data"] = data + } + if let requestId = requestId { + msg["request_id"] = requestId + } + + let jsonData = try JSONSerialization.data(withJSONObject: msg) + guard jsonData.count < Constants.maxMessageBytes else { + throw NetworkError.messageTooLarge + } + return jsonData + Data([0x0A]) + } + + /// Parse a single protocol message from bytes. + /// Matches Python: parse_message(line) + static func parseMessage(_ data: Data) throws -> [String: Any] { + let trimmed = data.trimmingNewlines() + guard !trimmed.isEmpty else { + throw NetworkError.protocolError("Empty message") + } + guard let obj = try JSONSerialization.jsonObject(with: trimmed) as? [String: Any] else { + throw NetworkError.protocolError("Message is not a JSON object") + } + return obj + } + + /// Encode bytes to base64 string. + /// Matches Python: encode_binary(data) + static func encodeBinary(_ data: Data) -> String { + data.base64EncodedString(options: []) + } + + /// Decode base64 string to bytes. + /// Matches Python: decode_binary(data) + static func decodeBinary(_ string: String) throws -> Data { + guard let data = Data(base64Encoded: string, options: .ignoreUnknownCharacters) else { + throw CryptoError.invalidBase64 + } + return data + } + + /// Generate a new request ID (UUID string). + static func newRequestId() -> String { + UUID().uuidString + } +} + +// MARK: - Data Helpers + +private extension Data { + func trimmingNewlines() -> Data { + var data = self + while let last = data.last, last == 0x0A || last == 0x0D { + data.removeLast() + } + while let first = data.first, first == 0x0A || first == 0x0D { + data.removeFirst() + } + return data + } +} diff --git a/ios_client/EncryptedChat/Utilities/Constants.swift b/ios_client/EncryptedChat/Utilities/Constants.swift new file mode 100644 index 0000000..b4dfc3c --- /dev/null +++ b/ios_client/EncryptedChat/Utilities/Constants.swift @@ -0,0 +1,38 @@ +import Foundation + +enum Constants { + static let version = "0.8.2" + static let maxMessageBytes = 65536 + static let maxImageBytes = 5 * 1024 * 1024 // 5 MB + static let maxFileBytes = 50 * 1024 * 1024 // 50 MB + static let imageChunkSize = 32768 // 32 KB + static let selfDeviceId = "00000000-0000-0000-0000-000000000000" + + static let opkReplenishThreshold = 20 + static let opkBatchSize = 50 + static let spkRotationDays = 7 + + static let maxSkip = 256 + static let maxSenderKeySkip = 256 + + static let deviceBundleCacheTTL: TimeInterval = 300 // 5 minutes + static let sendReceiveTimeout: TimeInterval = 30 + static let reconnectBaseDelay: TimeInterval = 1 + static let reconnectMaxDelay: TimeInterval = 30 + + static let pbkdf2Iterations: UInt32 = 600_000 + static let ecp1Magic = Data([0x45, 0x43, 0x50, 0x31]) // "ECP1" + + // HKDF info/salt strings matching Python + static let x3dhInfo = "EncryptedChat_X3DH" + static let rootKeyInfo = "EncryptedChat_RootKey" + static let selfEncryptionSalt = "self_encryption" + static let selfEncryptionInfo = "EncryptedChat_SelfKey" + static let localStorageSalt = "local_storage" + static let localStorageInfo = "EncryptedChat_LocalStorage" + static let senderKeyChainInfo = "SenderKeyChain" + + // Server connection defaults + static let defaultHost = "127.0.0.1" + static let defaultPort: UInt16 = 9999 +} diff --git a/ios_client/EncryptedChat/Utilities/Extensions.swift b/ios_client/EncryptedChat/Utilities/Extensions.swift new file mode 100644 index 0000000..84ca32e --- /dev/null +++ b/ios_client/EncryptedChat/Utilities/Extensions.swift @@ -0,0 +1,132 @@ +import Foundation +import CryptoKit + +// MARK: - Data ↔ Hex + +extension Data { + /// Convert data to lowercase hex string + var hexString: String { + map { String(format: "%02x", $0) }.joined() + } + + /// Initialize Data from a hex string + init?(hexString: String) { + let hex = hexString.lowercased() + guard hex.count % 2 == 0 else { return nil } + var data = Data(capacity: hex.count / 2) + var index = hex.startIndex + while index < hex.endIndex { + let nextIndex = hex.index(index, offsetBy: 2) + guard let byte = UInt8(hex[index.. Data { + var data = Data(count: count) + data.withUnsafeMutableBytes { ptr in + _ = SecRandomCopyBytes(kSecRandomDefault, count, ptr.baseAddress!) + } + return data + } +} + +// MARK: - Data ↔ Base64 (Protocol Wire Format) + +extension Data { + /// Encode to standard base64 string (matching Python's base64.b64encode) + func base64EncodedString() -> String { + self.base64EncodedString(options: []) + } + + /// Decode from base64 string + static func fromBase64(_ string: String) throws -> Data { + // Try standard base64 first, then URL-safe + if let data = Data(base64Encoded: string, options: .ignoreUnknownCharacters) { + return data + } + throw CryptoError.invalidBase64 + } +} + +// MARK: - UInt32 Big-Endian + +extension UInt32 { + var bigEndianData: Data { + var value = self.bigEndian + return Data(bytes: &value, count: 4) + } +} + +// MARK: - CryptoKit Key → Data + +extension Curve25519.KeyAgreement.PublicKey { + var rawData: Data { + Data(rawRepresentation) + } +} + +extension Curve25519.KeyAgreement.PrivateKey { + var rawData: Data { + Data(rawRepresentation) + } +} + +extension Curve25519.Signing.PublicKey { + var rawData: Data { + Data(rawRepresentation) + } +} + +extension Curve25519.Signing.PrivateKey { + var rawData: Data { + Data(rawRepresentation) + } +} + +// MARK: - String helpers + +extension String { + /// Trim whitespace and newlines + var trimmed: String { + trimmingCharacters(in: .whitespacesAndNewlines) + } +} + +// MARK: - Dictionary merge helper + +extension Dictionary where Key == String, Value == Any { + func string(for key: String) -> String? { + self[key] as? String + } + + func int(for key: String) -> Int? { + if let i = self[key] as? Int { return i } + if let s = self[key] as? String, let i = Int(s) { return i } + return nil + } + + func dict(for key: String) -> [String: Any]? { + self[key] as? [String: Any] + } + + func array(for key: String) -> [[String: Any]]? { + self[key] as? [[String: Any]] + } + + func data(for key: String) -> Data? { + if let hex = self[key] as? String { + return Data(hexString: hex) + } + return nil + } + + func bool(for key: String) -> Bool? { + if let b = self[key] as? Bool { return b } + if let i = self[key] as? Int { return i != 0 } + return nil + } +} diff --git a/ios_client/EncryptedChat/ViewModels/AuthViewModel.swift b/ios_client/EncryptedChat/ViewModels/AuthViewModel.swift new file mode 100644 index 0000000..33b98ac --- /dev/null +++ b/ios_client/EncryptedChat/ViewModels/AuthViewModel.swift @@ -0,0 +1,114 @@ +import Foundation +import SwiftUI + +@Observable +final class AuthViewModel { + var email = "" + var password = "" + var confirmPassword = "" + var username = "" + var confirmationCode = "" + + var isLoading = false + var errorMessage: String? + var showConfirmation = false + var registrationMessage: String? + + var serverHost = Constants.defaultHost + var serverPort = String(Constants.defaultPort) + var useTLS = false + + enum AuthMode { + case login, register, pairing + } + var mode: AuthMode = .login + + func login(appState: AppState) async { + guard !email.isEmpty, !password.isEmpty else { + errorMessage = "Email and password are required" + return + } + + isLoading = true + errorMessage = nil + + do { + let port = UInt16(serverPort) ?? Constants.defaultPort + try await appState.chatClient.connect(host: serverHost, port: port, useTLS: useTLS) + } catch { + isLoading = false + errorMessage = "Connection failed: \(error.localizedDescription)" + return + } + + let (success, message) = await appState.chatClient.login(email: email, password: password) + isLoading = false + + if success { + appState.email = email + appState.isLoggedIn = true + appState.connectionStatus = .connected + if let userId = await appState.chatClient.userId { + appState.currentUser = User(id: userId, username: await appState.chatClient.username, email: email) + } + } else { + errorMessage = message + } + } + + func register(appState: AppState) async { + guard !email.isEmpty, !password.isEmpty, !username.isEmpty else { + errorMessage = "All fields are required" + return + } + guard password == confirmPassword else { + errorMessage = "Passwords don't match" + return + } + + isLoading = true + errorMessage = nil + + do { + let port = UInt16(serverPort) ?? Constants.defaultPort + try await appState.chatClient.connect(host: serverHost, port: port, useTLS: useTLS) + } catch { + isLoading = false + errorMessage = "Connection failed: \(error.localizedDescription)" + return + } + + let (success, message) = await appState.chatClient.register(username: username, password: password, email: email) + isLoading = false + + if success { + registrationMessage = message + showConfirmation = true + } else { + errorMessage = message + } + } + + func confirmRegistration(appState: AppState) async { + guard !confirmationCode.isEmpty else { + errorMessage = "Enter the confirmation code" + return + } + + isLoading = true + errorMessage = nil + + let (success, message) = await appState.chatClient.confirmRegistration( + email: email, username: username, code: confirmationCode + ) + isLoading = false + + if success { + registrationMessage = message + // Auto-login after registration + await login(appState: appState) + } else { + errorMessage = message + } + } +} diff --git a/ios_client/EncryptedChat/ViewModels/ChatViewModel.swift b/ios_client/EncryptedChat/ViewModels/ChatViewModel.swift new file mode 100644 index 0000000..e47dfef --- /dev/null +++ b/ios_client/EncryptedChat/ViewModels/ChatViewModel.swift @@ -0,0 +1,131 @@ +import Foundation +import SwiftUI + +@Observable +final class ChatViewModel { + var messages: [Message] = [] + var isLoading = false + var isSending = false + var errorMessage: String? + var searchQuery = "" + var searchResults: [String] = [] // message IDs matching search + var currentSearchIndex = 0 + + private var notificationTask: Task? + + func loadMessages(convId: String, chatClient: ChatClient) async { + isLoading = true + messages = await chatClient.getMessages(convId: convId, limit: 50) + isLoading = false + + // Mark as read + let unreadIds = messages.filter { !$0.isMine(currentUserId: await chatClient.userId ?? "") }.map(\.id) + if !unreadIds.isEmpty { + await chatClient.markRead(convId: convId, messageIds: unreadIds) + } + } + + func loadOlderMessages(convId: String, chatClient: ChatClient) async { + let older = await chatClient.getMessages(convId: convId, limit: 50, offset: messages.count) + messages.insert(contentsOf: older, at: 0) + } + + func sendMessage(convId: String, text: String, members: [ConversationMember], + chatClient: ChatClient, replyTo: String? = nil) async { + guard !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return } + + isSending = true + errorMessage = nil + + let (success, msg) = await chatClient.sendMessage( + convId: convId, text: text, members: members, replyTo: replyTo + ) + + isSending = false + + if !success { + errorMessage = msg + } else { + // Reload messages to get the sent message + await loadMessages(convId: convId, chatClient: chatClient) + } + } + + func deleteMessage(messageId: String, convId: String, chatClient: ChatClient) async { + let success = await chatClient.deleteMessage(messageId: messageId, convId: convId) + if success { + messages.removeAll { $0.id == messageId } + } + } + + func search(query: String) { + searchQuery = query + if query.isEmpty { + searchResults = [] + currentSearchIndex = 0 + return + } + let lower = query.lowercased() + searchResults = messages.filter { $0.text?.lowercased().contains(lower) == true }.map(\.id) + currentSearchIndex = searchResults.isEmpty ? 0 : searchResults.count - 1 + } + + func nextSearchResult() { + guard !searchResults.isEmpty else { return } + currentSearchIndex = (currentSearchIndex + 1) % searchResults.count + } + + func prevSearchResult() { + guard !searchResults.isEmpty else { return } + currentSearchIndex = (currentSearchIndex - 1 + searchResults.count) % searchResults.count + } + + func startNotificationListener(convId: String, chatClient: ChatClient) { + notificationTask?.cancel() + notificationTask = Task { + for await notification in await chatClient.notifications { + await handleNotification(notification, convId: convId, chatClient: chatClient) + } + } + } + + @MainActor + private func handleNotification(_ notification: ChatNotification, convId: String, chatClient: ChatClient) { + switch notification { + case .newMessage(let data): + if data["conversation_id"] as? String == convId { + if let msg = Task.detached(priority: .userInitiated, operation: { + await chatClient.decryptNotification(data) + }) as? Task { + Task { + if let message = await msg.value { + messages.append(message) + // Mark as read immediately since we're viewing this conv + await chatClient.markRead(convId: convId, messageIds: [message.id]) + } + } + } + } + case .messageDeleted(let data): + if let msgId = data["message_id"] as? String { + messages.removeAll { $0.id == msgId } + } + case .messagesRead(let data): + if let readUserId = data["user_id"] as? String, + let msgIds = data["message_ids"] as? [String] { + for i in messages.indices { + if msgIds.contains(messages[i].id) { + messages[i].readBy.insert(readUserId) + } + } + } + default: + break + } + } + + func stop() { + notificationTask?.cancel() + notificationTask = nil + } +} diff --git a/ios_client/EncryptedChat/ViewModels/ConversationListVM.swift b/ios_client/EncryptedChat/ViewModels/ConversationListVM.swift new file mode 100644 index 0000000..82e93ed --- /dev/null +++ b/ios_client/EncryptedChat/ViewModels/ConversationListVM.swift @@ -0,0 +1,127 @@ +import Foundation +import SwiftUI + +@Observable +final class ConversationListVM { + var conversations: [Conversation] = [] + var invitations: [Invitation] = [] + var onlineUsers: Set = [] + var unreadCounts: [String: Int] = [:] + var favorites: Set = [] + var isLoading = false + + private var notificationTask: Task? + + func load(chatClient: ChatClient, email: String) async { + isLoading = true + + // Load favorites from disk + favorites = KeyStorage.loadFavorites(email: email) + + // Fetch conversations + let convs = await chatClient.listConversations() + conversations = sortConversations(convs, currentUserId: await chatClient.userId ?? "") + + // Populate unread counts from server + for conv in conversations where conv.unreadCount > 0 { + unreadCounts[conv.id] = conv.unreadCount + } + + // Fetch invitations + invitations = await chatClient.listInvitations() + + isLoading = false + + // Start notification listener + startNotificationListener(chatClient: chatClient, email: email) + } + + func refresh(chatClient: ChatClient) async { + let convs = await chatClient.listConversations() + conversations = sortConversations(convs, currentUserId: await chatClient.userId ?? "") + invitations = await chatClient.listInvitations() + } + + func toggleFavorite(convId: String, email: String) { + if favorites.contains(convId) { + favorites.remove(convId) + } else { + favorites.insert(convId) + } + try? KeyStorage.saveFavorites(email: email, favorites: favorites) + + // Re-sort + let userId = conversations.first?.createdBy ?? "" + conversations = sortConversations(conversations, currentUserId: userId) + } + + func markConversationRead(convId: String) { + unreadCounts[convId] = 0 + } + + func incrementUnread(convId: String) { + unreadCounts[convId, default: 0] += 1 + } + + private func sortConversations(_ convs: [Conversation], currentUserId: String) -> [Conversation] { + var result = convs.map { conv -> Conversation in + var c = conv + c.isFavorite = favorites.contains(conv.id) + c.unreadCount = unreadCounts[conv.id] ?? conv.unreadCount + return c + } + + result.sort { a, b in + // Favorites first + if a.isFavorite != b.isFavorite { return a.isFavorite } + // Online DMs next + let aOnline = a.dmPartnerId(currentUserId: currentUserId).map { onlineUsers.contains($0) } ?? false + let bOnline = b.dmPartnerId(currentUserId: currentUserId).map { onlineUsers.contains($0) } ?? false + if aOnline != bOnline { return aOnline } + // Alphabetical + return a.displayName(currentUserId: currentUserId).lowercased() < b.displayName(currentUserId: currentUserId).lowercased() + } + + return result + } + + private func startNotificationListener(chatClient: ChatClient, email: String) { + notificationTask?.cancel() + notificationTask = Task { + for await notification in await chatClient.notifications { + await handleNotification(notification, chatClient: chatClient, email: email) + } + } + } + + @MainActor + private func handleNotification(_ notification: ChatNotification, chatClient: ChatClient, email: String) { + switch notification { + case .newMessage(let data): + if let convId = data["conversation_id"] as? String { + incrementUnread(convId: convId) + } + case .onlineUsers(let userIds): + onlineUsers = Set(userIds) + case .userOnline(let userId): + onlineUsers.insert(userId) + case .userOffline(let userId): + onlineUsers.remove(userId) + case .conversationCreated, .memberAdded, .memberRemoved, .conversationRenamed: + Task { await refresh(chatClient: chatClient) } + case .groupInvitation: + Task { invitations = await chatClient.listInvitations() } + case .connectionStateChanged(let connected): + if !connected { + // Could trigger auto-reconnect here + } + default: + break + } + } + + func stop() { + notificationTask?.cancel() + notificationTask = nil + } +} diff --git a/ios_client/EncryptedChat/ViewModels/ProfileViewModel.swift b/ios_client/EncryptedChat/ViewModels/ProfileViewModel.swift new file mode 100644 index 0000000..2408919 --- /dev/null +++ b/ios_client/EncryptedChat/ViewModels/ProfileViewModel.swift @@ -0,0 +1,66 @@ +import Foundation +import SwiftUI + +@Observable +final class ProfileViewModel { + var profile: UserProfile? + var avatarData: Data? + var isLoading = false + var isSaving = false + var errorMessage: String? + + // Editable fields + var phone = "" + var phoneVisible = false + var location = "" + var locationVisible = false + + func loadProfile(userId: String? = nil, chatClient: ChatClient) async { + isLoading = true + profile = await chatClient.getProfile(userId: userId) + isLoading = false + + if let p = profile { + phone = p.phone ?? "" + phoneVisible = p.phoneVisible + location = p.location ?? "" + locationVisible = p.locationVisible + } + + // Load avatar + let uid = userId ?? await chatClient.userId ?? "" + if !uid.isEmpty { + avatarData = await chatClient.getAvatar(userId: uid) + } + } + + func saveProfile(chatClient: ChatClient) async { + isSaving = true + errorMessage = nil + + let success = await chatClient.updateProfile( + phone: phone.isEmpty ? nil : phone, + phoneVisible: phoneVisible, + location: location.isEmpty ? nil : location, + locationVisible: locationVisible + ) + + isSaving = false + + if !success { + errorMessage = "Failed to update profile" + } + } + + func uploadAvatar(imageData: Data, chatClient: ChatClient) async { + isSaving = true + let success = await chatClient.updateAvatar(imageData: imageData) + isSaving = false + + if success { + avatarData = imageData + } else { + errorMessage = "Failed to upload avatar" + } + } +} diff --git a/ios_client/EncryptedChat/Views/Auth/LoginView.swift b/ios_client/EncryptedChat/Views/Auth/LoginView.swift new file mode 100644 index 0000000..4b8b839 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Auth/LoginView.swift @@ -0,0 +1,134 @@ +import SwiftUI + +struct LoginView: View { + @Bindable var viewModel: AuthViewModel + var appState: AppState + + var body: some View { + NavigationStack { + ScrollView { + VStack(spacing: 20) { + Image(systemName: "lock.shield.fill") + .font(.system(size: 60)) + .foregroundStyle(.blue) + .padding(.top, 40) + + Text("Encrypted Chat") + .font(.largeTitle.bold()) + + Text("End-to-end encrypted messaging") + .font(.subheadline) + .foregroundStyle(.secondary) + + VStack(spacing: 16) { + // Server config + DisclosureGroup("Server") { + TextField("Host", text: $viewModel.serverHost) + .textContentType(.URL) + .autocapitalization(.none) + TextField("Port", text: $viewModel.serverPort) + .keyboardType(.numberPad) + Toggle("Use TLS", isOn: $viewModel.useTLS) + } + .padding(.horizontal) + + TextField("Email", text: $viewModel.email) + .textContentType(.emailAddress) + .keyboardType(.emailAddress) + .autocapitalization(.none) + .textFieldStyle(.roundedBorder) + + SecureField("Password", text: $viewModel.password) + .textContentType(.password) + .textFieldStyle(.roundedBorder) + + if viewModel.mode == .register { + TextField("Username", text: $viewModel.username) + .textContentType(.username) + .autocapitalization(.none) + .textFieldStyle(.roundedBorder) + + SecureField("Confirm Password", text: $viewModel.confirmPassword) + .textFieldStyle(.roundedBorder) + } + + if let error = viewModel.errorMessage { + Text(error) + .foregroundStyle(.red) + .font(.caption) + .multilineTextAlignment(.center) + } + + Button(action: { + Task { + if viewModel.mode == .login { + await viewModel.login(appState: appState) + } else { + await viewModel.register(appState: appState) + } + } + }) { + if viewModel.isLoading { + ProgressView() + .frame(maxWidth: .infinity) + } else { + Text(viewModel.mode == .login ? "Login" : "Register") + .frame(maxWidth: .infinity) + } + } + .buttonStyle(.borderedProminent) + .disabled(viewModel.isLoading) + + Button(viewModel.mode == .login ? "Don't have an account? Register" : "Already have an account? Login") { + viewModel.mode = viewModel.mode == .login ? .register : .login + viewModel.errorMessage = nil + } + .font(.caption) + } + .padding(.horizontal, 32) + } + } + .sheet(isPresented: $viewModel.showConfirmation) { + ConfirmationSheet(viewModel: viewModel, appState: appState) + } + } + } +} + +struct ConfirmationSheet: View { + @Bindable var viewModel: AuthViewModel + var appState: AppState + + var body: some View { + VStack(spacing: 20) { + Text("Confirm Registration") + .font(.title2.bold()) + + if let msg = viewModel.registrationMessage { + Text(msg) + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + } + + TextField("Confirmation Code", text: $viewModel.confirmationCode) + .textFieldStyle(.roundedBorder) + .keyboardType(.numberPad) + + if let error = viewModel.errorMessage { + Text(error) + .foregroundStyle(.red) + .font(.caption) + } + + Button("Confirm") { + Task { + await viewModel.confirmRegistration(appState: appState) + } + } + .buttonStyle(.borderedProminent) + .disabled(viewModel.isLoading) + } + .padding(32) + } +} diff --git a/ios_client/EncryptedChat/Views/Auth/PairingView.swift b/ios_client/EncryptedChat/Views/Auth/PairingView.swift new file mode 100644 index 0000000..043fb5c --- /dev/null +++ b/ios_client/EncryptedChat/Views/Auth/PairingView.swift @@ -0,0 +1,49 @@ +import SwiftUI + +struct PairingView: View { + var appState: AppState + @State private var pairingCode = "" + @State private var isWaiting = false + @State private var statusMessage: String? + + var body: some View { + VStack(spacing: 24) { + Image(systemName: "iphone.and.arrow.forward") + .font(.system(size: 48)) + .foregroundStyle(.blue) + + Text("Device Pairing") + .font(.title2.bold()) + + Text("Enter the 8-digit pairing code shown on your other device.") + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + + TextField("Pairing Code", text: $pairingCode) + .textFieldStyle(.roundedBorder) + .keyboardType(.numberPad) + .frame(maxWidth: 200) + + if let status = statusMessage { + Text(status) + .font(.caption) + .foregroundStyle(status.contains("Error") ? .red : .secondary) + } + + if isWaiting { + ProgressView("Waiting for authorization...") + } + + Button("Start Pairing") { + Task { + // Pairing implementation would go here + statusMessage = "Pairing not yet implemented" + } + } + .buttonStyle(.borderedProminent) + .disabled(pairingCode.count != 8 || isWaiting) + } + .padding(32) + } +} diff --git a/ios_client/EncryptedChat/Views/Auth/RegisterView.swift b/ios_client/EncryptedChat/Views/Auth/RegisterView.swift new file mode 100644 index 0000000..04c0b57 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Auth/RegisterView.swift @@ -0,0 +1,4 @@ +import SwiftUI + +// Registration is handled within LoginView via mode toggle. +// This file exists for potential future separation. diff --git a/ios_client/EncryptedChat/Views/Chat/ChatView.swift b/ios_client/EncryptedChat/Views/Chat/ChatView.swift new file mode 100644 index 0000000..f1671e2 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Chat/ChatView.swift @@ -0,0 +1,164 @@ +import SwiftUI + +struct ChatView: View { + let conversation: Conversation + var appState: AppState + @State private var viewModel = ChatViewModel() + @State private var inputText = "" + @State private var replyTo: Message? + @State private var showGroupInfo = false + @State private var showSearch = false + @State private var showDeleteConfirm = false + + var body: some View { + VStack(spacing: 0) { + // Search bar + if showSearch { + SearchOverlayView( + query: $viewModel.searchQuery, + matchCount: viewModel.searchResults.count, + currentIndex: viewModel.currentSearchIndex, + onSearch: { viewModel.search(query: $0) }, + onNext: { viewModel.nextSearchResult() }, + onPrev: { viewModel.prevSearchResult() }, + onClose: { showSearch = false; viewModel.search(query: "") } + ) + } + + // Messages + ScrollViewReader { proxy in + ScrollView { + LazyVStack(spacing: 8) { + if viewModel.messages.count >= 50 { + Button("Load older messages") { + Task { + await viewModel.loadOlderMessages(convId: conversation.id, chatClient: appState.chatClient) + } + } + .font(.caption) + .padding() + } + + ForEach(viewModel.messages) { message in + MessageBubbleView( + message: message, + isMine: message.isMine(currentUserId: appState.currentUser?.id ?? ""), + isHighlighted: viewModel.searchResults.contains(message.id), + isCurrentSearchResult: viewModel.searchResults.indices.contains(viewModel.currentSearchIndex) && + viewModel.searchResults[viewModel.currentSearchIndex] == message.id, + onReply: { replyTo = message }, + onDelete: { + Task { + await viewModel.deleteMessage(messageId: message.id, convId: conversation.id, chatClient: appState.chatClient) + } + } + ) + .id(message.id) + } + } + .padding(.horizontal) + .padding(.vertical, 8) + } + .onChange(of: viewModel.messages.count) { + if let lastId = viewModel.messages.last?.id { + withAnimation { + proxy.scrollTo(lastId, anchor: .bottom) + } + } + } + } + + // Reply preview + if let reply = replyTo { + HStack { + Rectangle() + .fill(.blue) + .frame(width: 3) + VStack(alignment: .leading) { + Text(reply.senderUsername) + .font(.caption.bold()) + Text(reply.text ?? "") + .font(.caption) + .lineLimit(1) + } + Spacer() + Button(action: { replyTo = nil }) { + Image(systemName: "xmark.circle.fill") + .foregroundStyle(.secondary) + } + } + .padding(.horizontal) + .padding(.vertical, 6) + .background(.ultraThinMaterial) + } + + // Input + MessageInputView( + text: $inputText, + isSending: viewModel.isSending, + onSend: { + Task { + let text = inputText + inputText = "" + let reply = replyTo?.id + replyTo = nil + await viewModel.sendMessage( + convId: conversation.id, + text: text, + members: conversation.members, + chatClient: appState.chatClient, + replyTo: reply + ) + } + } + ) + } + .navigationTitle(conversation.displayName(currentUserId: appState.currentUser?.id ?? "")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + HStack(spacing: 16) { + Button(action: { showSearch.toggle() }) { + Image(systemName: "magnifyingglass") + } + + if conversation.isGroup { + Button(action: { showGroupInfo = true }) { + Image(systemName: "info.circle") + } + } + + // Delete button + if !conversation.isGroup || conversation.createdBy == appState.currentUser?.id { + Button(action: { showDeleteConfirm = true }) { + Image(systemName: "trash") + .foregroundStyle(.red) + } + } + } + } + } + .alert("Delete Conversation?", isPresented: $showDeleteConfirm) { + Button("Cancel", role: .cancel) {} + Button("Delete", role: .destructive) { + Task { + await appState.chatClient.deleteConversation(convId: conversation.id) + } + } + } message: { + Text(conversation.isGroup + ? "This will remove all members and delete the conversation." + : "This will remove you from the conversation.") + } + .sheet(isPresented: $showGroupInfo) { + GroupInfoView(conversation: conversation, appState: appState) + } + .task { + await viewModel.loadMessages(convId: conversation.id, chatClient: appState.chatClient) + viewModel.startNotificationListener(convId: conversation.id, chatClient: appState.chatClient) + } + .onDisappear { + viewModel.stop() + } + } +} diff --git a/ios_client/EncryptedChat/Views/Chat/ImageViewerView.swift b/ios_client/EncryptedChat/Views/Chat/ImageViewerView.swift new file mode 100644 index 0000000..0e35cfd --- /dev/null +++ b/ios_client/EncryptedChat/Views/Chat/ImageViewerView.swift @@ -0,0 +1,43 @@ +import SwiftUI + +struct ImageViewerView: View { + let imageData: Data + @State private var scale: CGFloat = 1.0 + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + GeometryReader { geo in + if let uiImage = UIImage(data: imageData) { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fit) + .scaleEffect(scale) + .gesture( + MagnifyGesture() + .onChanged { value in + scale = value.magnification + } + .onEnded { _ in + withAnimation { + scale = max(1.0, min(scale, 5.0)) + } + } + ) + .onTapGesture(count: 2) { + withAnimation { + scale = scale > 1 ? 1 : 2 + } + } + .frame(width: geo.size.width, height: geo.size.height) + } + } + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Done") { dismiss() } + } + } + .background(.black) + } + } +} diff --git a/ios_client/EncryptedChat/Views/Chat/MessageBubbleView.swift b/ios_client/EncryptedChat/Views/Chat/MessageBubbleView.swift new file mode 100644 index 0000000..e883b56 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Chat/MessageBubbleView.swift @@ -0,0 +1,123 @@ +import SwiftUI + +struct MessageBubbleView: View { + let message: Message + let isMine: Bool + var isHighlighted: Bool = false + var isCurrentSearchResult: Bool = false + var onReply: (() -> Void)? + var onDelete: (() -> Void)? + + var body: some View { + HStack { + if isMine { Spacer(minLength: 60) } + + VStack(alignment: isMine ? .trailing : .leading, spacing: 4) { + if !isMine { + Text(message.senderUsername) + .font(.caption.bold()) + .foregroundStyle(.secondary) + } + + if message.isDeleted { + Text("Message deleted") + .font(.body.italic()) + .foregroundStyle(.secondary) + .padding(12) + .background(Color(.systemGray6)) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } else { + // Reply reference + if let replyTo = message.replyTo { + HStack(spacing: 4) { + Rectangle() + .fill(.blue.opacity(0.5)) + .frame(width: 2) + Text("Reply to message") + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(.horizontal, 8) + } + + // File card + if let file = message.file { + VStack(alignment: .leading, spacing: 4) { + HStack { + Image(systemName: "paperclip") + Text(file.filename) + .lineLimit(1) + } + .font(.subheadline) + + Text(formatFileSize(file.size)) + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(12) + .background(Color(.systemGray5)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } + + // Text content + if let text = message.text { + Text(text) + .padding(12) + .background( + isMine ? Color.blue : Color(.systemGray5) + ) + .foregroundStyle(isMine ? .white : .primary) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } + + // Timestamp + Text(formatTime(message.createdAt)) + .font(.caption2) + .foregroundStyle(.secondary) + } + } + .background( + isCurrentSearchResult ? Color.orange.opacity(0.3) : + isHighlighted ? Color.yellow.opacity(0.2) : Color.clear + ) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .contextMenu { + if !message.isDeleted { + Button(action: { onReply?() }) { + Label("Reply", systemImage: "arrowshape.turn.up.left") + } + + Button(action: { + UIPasteboard.general.string = message.text ?? "" + }) { + Label("Copy", systemImage: "doc.on.doc") + } + + if isMine { + Button(role: .destructive, action: { onDelete?() }) { + Label("Delete", systemImage: "trash") + } + } + } + } + + if !isMine { Spacer(minLength: 60) } + } + } + + private func formatTime(_ date: Date) -> String { + let formatter = DateFormatter() + if Calendar.current.isDateInToday(date) { + formatter.dateFormat = "HH:mm" + } else { + formatter.dateFormat = "MMM d, HH:mm" + } + return formatter.string(from: date) + } + + private func formatFileSize(_ bytes: Int) -> String { + if bytes < 1024 { return "\(bytes) B" } + if bytes < 1024 * 1024 { return "\(bytes / 1024) KB" } + return String(format: "%.1f MB", Double(bytes) / (1024 * 1024)) + } +} diff --git a/ios_client/EncryptedChat/Views/Chat/MessageInputView.swift b/ios_client/EncryptedChat/Views/Chat/MessageInputView.swift new file mode 100644 index 0000000..de53335 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Chat/MessageInputView.swift @@ -0,0 +1,55 @@ +import SwiftUI +import PhotosUI + +struct MessageInputView: View { + @Binding var text: String + let isSending: Bool + let onSend: () -> Void + + @State private var showAttachMenu = false + @State private var selectedPhoto: PhotosPickerItem? + + var body: some View { + HStack(spacing: 8) { + // Attach button + Menu { + Button(action: {}) { + Label("Photo", systemImage: "photo") + } + Button(action: {}) { + Label("File", systemImage: "doc") + } + } label: { + Image(systemName: "plus.circle.fill") + .font(.title2) + .foregroundStyle(.blue) + } + + // Text field + TextField("Message", text: $text, axis: .vertical) + .textFieldStyle(.roundedBorder) + .lineLimit(1...5) + .onSubmit { + if !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + onSend() + } + } + + // Send button + Button(action: onSend) { + if isSending { + ProgressView() + .scaleEffect(0.8) + } else { + Image(systemName: "arrow.up.circle.fill") + .font(.title2) + .foregroundStyle(.blue) + } + } + .disabled(text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || isSending) + } + .padding(.horizontal) + .padding(.vertical, 8) + .background(.ultraThinMaterial) + } +} diff --git a/ios_client/EncryptedChat/Views/Chat/SearchOverlayView.swift b/ios_client/EncryptedChat/Views/Chat/SearchOverlayView.swift new file mode 100644 index 0000000..98bc3b4 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Chat/SearchOverlayView.swift @@ -0,0 +1,46 @@ +import SwiftUI + +struct SearchOverlayView: View { + @Binding var query: String + let matchCount: Int + let currentIndex: Int + let onSearch: (String) -> Void + let onNext: () -> Void + let onPrev: () -> Void + let onClose: () -> Void + + var body: some View { + HStack(spacing: 8) { + Image(systemName: "magnifyingglass") + .foregroundStyle(.secondary) + + TextField("Search messages", text: $query) + .textFieldStyle(.roundedBorder) + .onChange(of: query) { _, newValue in + onSearch(newValue) + } + + if matchCount > 0 { + Text("\(currentIndex + 1)/\(matchCount)") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize() + + Button(action: onPrev) { + Image(systemName: "chevron.up") + } + Button(action: onNext) { + Image(systemName: "chevron.down") + } + } + + Button(action: onClose) { + Image(systemName: "xmark.circle.fill") + .foregroundStyle(.secondary) + } + } + .padding(.horizontal) + .padding(.vertical, 6) + .background(.ultraThinMaterial) + } +} diff --git a/ios_client/EncryptedChat/Views/Components/CircularAvatarView.swift b/ios_client/EncryptedChat/Views/Components/CircularAvatarView.swift new file mode 100644 index 0000000..787a349 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Components/CircularAvatarView.swift @@ -0,0 +1,46 @@ +import SwiftUI + +struct CircularAvatarView: View { + let name: String + var imageData: Data? + var size: CGFloat = 32 + var isGroup: Bool = false + + var body: some View { + if let imageData = imageData, let uiImage = UIImage(data: imageData) { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: size, height: size) + .clipShape(Circle()) + } else { + // Default: colored circle with initial letter + ZStack { + Circle() + .fill(avatarColor) + .frame(width: size, height: size) + + Text(initial) + .font(.system(size: size * 0.4, weight: .semibold)) + .foregroundStyle(.white) + } + } + } + + private var initial: String { + String(name.prefix(1)).uppercased() + } + + /// Deterministic color from name hash (matching Python gui_client behavior) + private var avatarColor: Color { + let colors: [Color] = [ + .red, .orange, .yellow, .green, .mint, + .teal, .cyan, .blue, .indigo, .purple, .pink + ] + var hash = 0 + for char in name.unicodeScalars { + hash = hash &* 31 &+ Int(char.value) + } + return colors[abs(hash) % colors.count] + } +} diff --git a/ios_client/EncryptedChat/Views/Components/ConnectionIndicator.swift b/ios_client/EncryptedChat/Views/Components/ConnectionIndicator.swift new file mode 100644 index 0000000..4907e63 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Components/ConnectionIndicator.swift @@ -0,0 +1,35 @@ +import SwiftUI + +struct ConnectionIndicator: View { + let status: ConnectionStatus + + var body: some View { + HStack(spacing: 4) { + Circle() + .fill(statusColor) + .frame(width: 8, height: 8) + + if status != .connected { + Text(statusText) + .font(.caption2) + .foregroundStyle(.secondary) + } + } + } + + private var statusColor: Color { + switch status { + case .connected: return .green + case .connecting: return .orange + case .disconnected: return .red + } + } + + private var statusText: String { + switch status { + case .connected: return "" + case .connecting: return "Connecting..." + case .disconnected: return "Disconnected" + } + } +} diff --git a/ios_client/EncryptedChat/Views/Components/OnlineDotOverlay.swift b/ios_client/EncryptedChat/Views/Components/OnlineDotOverlay.swift new file mode 100644 index 0000000..4a2b645 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Components/OnlineDotOverlay.swift @@ -0,0 +1,15 @@ +import SwiftUI + +struct OnlineDotOverlay: View { + var size: CGFloat = 12 + + var body: some View { + Circle() + .fill(.green) + .frame(width: size, height: size) + .overlay( + Circle() + .stroke(.white, lineWidth: 2) + ) + } +} diff --git a/ios_client/EncryptedChat/Views/Conversations/ConversationListView.swift b/ios_client/EncryptedChat/Views/Conversations/ConversationListView.swift new file mode 100644 index 0000000..d387128 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Conversations/ConversationListView.swift @@ -0,0 +1,99 @@ +import SwiftUI + +struct ConversationListView: View { + var appState: AppState + @Bindable var viewModel: ConversationListVM + @State private var showNewConversation = false + @State private var showProfile = false + @State private var selectedConversation: Conversation? + + var body: some View { + NavigationStack { + List { + // Invitations section + if !viewModel.invitations.isEmpty { + Section { + ForEach(viewModel.invitations) { invitation in + InvitationBanner( + invitation: invitation, + onAccept: { + Task { + let (success, _) = await appState.chatClient.acceptInvitation(convId: invitation.conversationId) + if success { + await viewModel.refresh(chatClient: appState.chatClient) + } + } + }, + onDecline: { + Task { + await appState.chatClient.declineInvitation(convId: invitation.conversationId) + await viewModel.refresh(chatClient: appState.chatClient) + } + } + ) + } + } header: { + Text("Invitations") + } + } + + // Conversations section + Section { + ForEach(viewModel.conversations) { conversation in + NavigationLink(value: conversation) { + ConversationRowView( + conversation: conversation, + currentUserId: appState.currentUser?.id ?? "", + isOnline: conversation.dmPartnerId(currentUserId: appState.currentUser?.id ?? "") + .map { viewModel.onlineUsers.contains($0) } ?? false, + unreadCount: viewModel.unreadCounts[conversation.id] ?? 0 + ) + } + .contextMenu { + Button(conversation.isFavorite ? "Remove from Favorites" : "Add to Favorites") { + viewModel.toggleFavorite(convId: conversation.id, email: appState.email) + } + } + } + } + } + .navigationTitle("Chats") + .navigationDestination(for: Conversation.self) { conversation in + ChatView( + conversation: conversation, + appState: appState + ) + } + .toolbar { + ToolbarItem(placement: .topBarLeading) { + ConnectionIndicator(status: appState.connectionStatus) + } + ToolbarItem(placement: .topBarTrailing) { + HStack { + Button(action: { showProfile = true }) { + Image(systemName: "person.circle") + } + Button(action: { showNewConversation = true }) { + Image(systemName: "square.and.pencil") + } + } + } + } + .refreshable { + await viewModel.refresh(chatClient: appState.chatClient) + } + .sheet(isPresented: $showNewConversation) { + NewConversationSheet(appState: appState) { convId in + showNewConversation = false + await viewModel.refresh(chatClient: appState.chatClient) + } + } + .sheet(isPresented: $showProfile) { + ProfileView(appState: appState, isOwnProfile: true) + } + .task { + await viewModel.load(chatClient: appState.chatClient, email: appState.email) + } + } + } +} diff --git a/ios_client/EncryptedChat/Views/Conversations/ConversationRowView.swift b/ios_client/EncryptedChat/Views/Conversations/ConversationRowView.swift new file mode 100644 index 0000000..2708773 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Conversations/ConversationRowView.swift @@ -0,0 +1,58 @@ +import SwiftUI + +struct ConversationRowView: View { + let conversation: Conversation + let currentUserId: String + let isOnline: Bool + let unreadCount: Int + + var body: some View { + HStack(spacing: 12) { + // Avatar + ZStack(alignment: .bottomTrailing) { + CircularAvatarView( + name: conversation.displayName(currentUserId: currentUserId), + size: 44, + isGroup: conversation.isGroup + ) + + if isOnline && !conversation.isGroup { + OnlineDotOverlay(size: 12) + } + } + + VStack(alignment: .leading, spacing: 2) { + HStack { + if conversation.isFavorite { + Image(systemName: "star.fill") + .font(.caption2) + .foregroundStyle(.yellow) + } + + Text(conversation.displayName(currentUserId: currentUserId)) + .font(unreadCount > 0 ? .body.bold() : .body) + .lineLimit(1) + } + + if conversation.isGroup { + Text("\(conversation.members.count) members") + .font(.caption) + .foregroundStyle(.secondary) + } + } + + Spacer() + + if unreadCount > 0 { + Text("\(unreadCount)") + .font(.caption2.bold()) + .foregroundStyle(.white) + .padding(.horizontal, 8) + .padding(.vertical, 2) + .background(Color.blue) + .clipShape(Capsule()) + } + } + .padding(.vertical, 4) + } +} diff --git a/ios_client/EncryptedChat/Views/Conversations/NewConversationSheet.swift b/ios_client/EncryptedChat/Views/Conversations/NewConversationSheet.swift new file mode 100644 index 0000000..c0ffb31 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Conversations/NewConversationSheet.swift @@ -0,0 +1,100 @@ +import SwiftUI + +struct NewConversationSheet: View { + var appState: AppState + var onCreated: (String) async -> Void + + @State private var email = "" + @State private var groupName = "" + @State private var isGroup = false + @State private var memberEmails: [String] = [""] + @State private var isLoading = false + @State private var errorMessage: String? + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + Form { + Section { + Toggle("Create Group", isOn: $isGroup) + + if isGroup { + TextField("Group Name", text: $groupName) + } + } + + Section(isGroup ? "Members" : "Recipient") { + if isGroup { + ForEach(memberEmails.indices, id: \.self) { index in + TextField("Email", text: $memberEmails[index]) + .textContentType(.emailAddress) + .keyboardType(.emailAddress) + .autocapitalization(.none) + } + Button("Add Member") { + memberEmails.append("") + } + } else { + TextField("Email", text: $email) + .textContentType(.emailAddress) + .keyboardType(.emailAddress) + .autocapitalization(.none) + } + } + + if let error = errorMessage { + Section { + Text(error) + .foregroundStyle(.red) + } + } + } + .navigationTitle("New Conversation") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } + } + ToolbarItem(placement: .confirmationAction) { + Button("Create") { + Task { await create() } + } + .disabled(isLoading) + } + } + } + } + + private func create() async { + isLoading = true + errorMessage = nil + + let emails: [String] + if isGroup { + emails = memberEmails.map { $0.trimmed }.filter { !$0.isEmpty } + guard !emails.isEmpty else { + errorMessage = "Add at least one member" + isLoading = false + return + } + } else { + guard !email.trimmed.isEmpty else { + errorMessage = "Enter an email address" + isLoading = false + return + } + emails = [email.trimmed] + } + + let name = isGroup && !groupName.trimmed.isEmpty ? groupName.trimmed : nil + let (convId, message) = await appState.chatClient.createConversation(emails: emails, name: name) + + isLoading = false + + if let convId = convId { + await onCreated(convId) + } else { + errorMessage = message + } + } +} diff --git a/ios_client/EncryptedChat/Views/Groups/CreateGroupSheet.swift b/ios_client/EncryptedChat/Views/Groups/CreateGroupSheet.swift new file mode 100644 index 0000000..cbeaa75 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Groups/CreateGroupSheet.swift @@ -0,0 +1,4 @@ +import SwiftUI + +// Group creation is handled within NewConversationSheet via the isGroup toggle. +// This file exists for potential future separation. diff --git a/ios_client/EncryptedChat/Views/Groups/GroupInfoView.swift b/ios_client/EncryptedChat/Views/Groups/GroupInfoView.swift new file mode 100644 index 0000000..c9eb0c4 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Groups/GroupInfoView.swift @@ -0,0 +1,123 @@ +import SwiftUI + +struct GroupInfoView: View { + let conversation: Conversation + var appState: AppState + @State private var showRenameSheet = false + @State private var showLeaveConfirm = false + @State private var newName = "" + @Environment(\.dismiss) private var dismiss + + private var isCreator: Bool { + conversation.createdBy == appState.currentUser?.id + } + + var body: some View { + NavigationStack { + List { + // Avatar section + Section { + HStack { + Spacer() + VStack(spacing: 8) { + CircularAvatarView( + name: conversation.name ?? "Group", + size: 64, + isGroup: true + ) + + Text(conversation.name ?? "Group") + .font(.title2.bold()) + + Text("\(conversation.members.count) members") + .font(.subheadline) + .foregroundStyle(.secondary) + } + Spacer() + } + .listRowBackground(Color.clear) + } + + // Actions + if isCreator { + Section { + Button("Rename Group") { + newName = conversation.name ?? "" + showRenameSheet = true + } + + Button("Change Avatar") { + // Photo picker would go here + } + } + } + + // Members + Section("Members") { + ForEach(conversation.members) { member in + HStack { + CircularAvatarView(name: member.username, size: 32, isGroup: false) + + VStack(alignment: .leading) { + Text(member.username) + .font(.body) + Text(member.email) + .font(.caption) + .foregroundStyle(.secondary) + } + + Spacer() + + if member.userId == conversation.createdBy { + Text("Admin") + .font(.caption) + .foregroundStyle(.blue) + } + } + } + } + + // Leave / Delete + Section { + Button("Leave Group", role: .destructive) { + showLeaveConfirm = true + } + + if isCreator { + Button("Delete Group", role: .destructive) { + Task { + await appState.chatClient.deleteConversation(convId: conversation.id) + dismiss() + } + } + } + } + } + .navigationTitle("Group Info") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Done") { dismiss() } + } + } + .alert("Leave Group?", isPresented: $showLeaveConfirm) { + Button("Cancel", role: .cancel) {} + Button("Leave", role: .destructive) { + Task { + await appState.chatClient.leaveGroup(convId: conversation.id) + dismiss() + } + } + } + .alert("Rename Group", isPresented: $showRenameSheet) { + TextField("Group Name", text: $newName) + Button("Cancel", role: .cancel) {} + Button("Rename") { + Task { + await appState.chatClient.renameConversation(convId: conversation.id, name: newName) + } + } + } + } + } +} diff --git a/ios_client/EncryptedChat/Views/Groups/InvitationBanner.swift b/ios_client/EncryptedChat/Views/Groups/InvitationBanner.swift new file mode 100644 index 0000000..5f9c877 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Groups/InvitationBanner.swift @@ -0,0 +1,41 @@ +import SwiftUI + +struct InvitationBanner: View { + let invitation: Invitation + let onAccept: () -> Void + let onDecline: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Image(systemName: "envelope.badge") + .foregroundStyle(.orange) + + VStack(alignment: .leading) { + Text(invitation.conversationName) + .font(.body.bold()) + Text("Invited by \(invitation.invitedByUsername)") + .font(.caption) + .foregroundStyle(.secondary) + } + + Spacer() + } + + HStack(spacing: 12) { + Button("Accept") { + onAccept() + } + .buttonStyle(.borderedProminent) + .controlSize(.small) + + Button("Decline") { + onDecline() + } + .buttonStyle(.bordered) + .controlSize(.small) + } + } + .padding(.vertical, 4) + } +} diff --git a/ios_client/EncryptedChat/Views/Profile/EditProfileView.swift b/ios_client/EncryptedChat/Views/Profile/EditProfileView.swift new file mode 100644 index 0000000..b993ca0 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Profile/EditProfileView.swift @@ -0,0 +1,4 @@ +import SwiftUI + +// Profile editing is handled within ProfileView when isOwnProfile = true. +// This file exists for potential future separation. diff --git a/ios_client/EncryptedChat/Views/Profile/ProfileView.swift b/ios_client/EncryptedChat/Views/Profile/ProfileView.swift new file mode 100644 index 0000000..4f10289 --- /dev/null +++ b/ios_client/EncryptedChat/Views/Profile/ProfileView.swift @@ -0,0 +1,111 @@ +import SwiftUI + +struct ProfileView: View { + var appState: AppState + var isOwnProfile: Bool + var userId: String? + @State private var viewModel = ProfileViewModel() + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + Form { + // Avatar + Section { + HStack { + Spacer() + VStack(spacing: 8) { + if let avatarData = viewModel.avatarData, + let uiImage = UIImage(data: avatarData) { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 80, height: 80) + .clipShape(Circle()) + } else { + CircularAvatarView( + name: viewModel.profile?.username ?? "?", + size: 80, + isGroup: false + ) + } + + if isOwnProfile { + Button("Change Photo") { + // Photo picker would go here + } + .font(.caption) + } + } + Spacer() + } + .listRowBackground(Color.clear) + } + + // Info + Section("Info") { + if let username = viewModel.profile?.username { + LabeledContent("Username", value: username) + } + if let email = viewModel.profile?.email { + LabeledContent("Email", value: email) + } + } + + if isOwnProfile { + // Editable fields + Section("Contact") { + TextField("Phone", text: $viewModel.phone) + .keyboardType(.phonePad) + Toggle("Phone visible to contacts", isOn: $viewModel.phoneVisible) + + TextField("Location", text: $viewModel.location) + Toggle("Location visible to contacts", isOn: $viewModel.locationVisible) + } + } else { + // Read-only view + if let phone = viewModel.profile?.phone, viewModel.profile?.phoneVisible == true { + Section("Contact") { + LabeledContent("Phone", value: phone) + } + } + if let location = viewModel.profile?.location, viewModel.profile?.locationVisible == true { + Section("Location") { + LabeledContent("Location", value: location) + } + } + } + + if let error = viewModel.errorMessage { + Section { + Text(error) + .foregroundStyle(.red) + } + } + } + .navigationTitle(isOwnProfile ? "My Profile" : "Profile") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + if isOwnProfile { + Button("Save") { + Task { + await viewModel.saveProfile(chatClient: appState.chatClient) + dismiss() + } + } + .disabled(viewModel.isSaving) + } else { + Button("Done") { dismiss() } + } + } + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } + } + } + .task { + await viewModel.loadProfile(userId: userId, chatClient: appState.chatClient) + } + } + } +} diff --git a/ios_client/incremental_sync_changes.md b/ios_client/incremental_sync_changes.md new file mode 100644 index 0000000..0d4f142 --- /dev/null +++ b/ios_client/incremental_sync_changes.md @@ -0,0 +1,239 @@ +# iOS Client — Inkrementální sync zpráv + +## Problém + +Klient při každém otevření konverzace posílá `get_messages` a server vrací 50 zpráv (šifrované bloby + metadata). I když klient 49 z nich už má. Zbytečný přenos dat a zátěž serveru. + +## Řešení + +Server už podporuje parametr `after_ts` v `get_messages`. Klient si pamatuje timestamp poslední zprávy a posílá jen dotaz na novější. + +--- + +## Protokol — co posílat serveru + +### `get_messages` — nový volitelný parametr `after_ts` + +**Request:** +```json +{ + "type": "get_messages", + "request_id": "uuid", + "conversation_id": "conv-uuid", + "limit": 50, + "offset": 0, + "after_ts": "2026-02-15T22:15:45" +} +``` + +- `after_ts` (string, ISO 8601, volitelný) — server vrátí jen zprávy s `created_at > after_ts` +- Pokud `after_ts` chybí nebo je null, chová se jako dřív (vrátí posledních `limit` zpráv) + +**Response** — beze změny, jen méně zpráv: +```json +{ + "type": "get_messages", + "status": "ok", + "data": { + "messages": [...], + "total_count": 123 + } +} +``` + +### `get_deleted_since` — sync smazaných zpráv + +Po inkrementálním fetchi je nutné zjistit co bylo smazáno od posledního syncu. + +**Request:** +```json +{ + "type": "get_deleted_since", + "request_id": "uuid", + "conversation_id": "conv-uuid", + "since": "2026-02-15T22:15:45" +} +``` + +**Response:** +```json +{ + "type": "get_deleted_since", + "status": "ok", + "data": { + "message_ids": ["msg-uuid-1", "msg-uuid-2"] + } +} +``` + +### `mark_read` — optimalizace + +**Request** — beze změny, jen posílat méně ID: +```json +{ + "type": "mark_read", + "request_id": "uuid", + "conversation_id": "conv-uuid", + "message_ids": ["only-unread-msg-id-1"] +} +``` + +Filtrovat na klientovi: jen zprávy kde `sender_id != myId` **a** `myId` není v `read_by`. + +--- + +## Implementace na iOS klientovi + +### 1. Lokální cache zpráv + +Ukládat dešifrované zprávy na disk per konverzace. Klíč = `message_id`, hodnota = dešifrovaný payload (bez `read_by` — ten se mění). + +```swift +// MessageCache.swift nebo rozšíření ChatClient + +/// Uložit zprávu do lokální cache +func cacheMessage(convId: String, msgId: String, payload: [String: Any]) + +/// Načíst cache pro konverzaci → [msgId: payload] +func loadCache(convId: String) -> [String: [String: Any]] + +/// Smazat zprávu z cache +func removeCachedMessage(convId: String, msgId: String) +``` + +Formát na disku: JSON soubor v app sandbox, šifrovaný identity key (stejně jako Python klient). + +### 2. Logika v `getMessages()` + +``` +1. Načíst lokální cache pro conv_id +2. Pokud cache je neprázdná A offset == 0: + a. Najít nejnovější created_at v cache → after_ts + b. Poslat get_messages s after_ts (server vrátí jen nové) + c. Dešifrovat nové zprávy, přidat do cache + d. Poslat get_deleted_since s after_ts → smazat z cache + e. Sestavit výsledek z cache (seřadit, vzít posledních limit) +3. Pokud cache je prázdná NEBO offset > 0: + a. Plný fetch jako dřív (bez after_ts) + b. Dešifrovat, uložit do cache + c. Vrátit +4. mark_read: filtrovat jen sender_id != myId a myId not in read_by +``` + +### 3. Pseudokód + +```swift +func getMessages(convId: String, limit: Int = 50, offset: Int = 0) async -> [Message] { + var cache = loadCache(convId: convId) + let myId = userId ?? "" + + // Rozhodnout: inkrementální vs plný fetch + var afterTs: String? = nil + if !cache.isEmpty && offset == 0 { + afterTs = cache.values + .compactMap { $0["created_at"] as? String } + .filter { !($0.isEmpty) } + .max() + } + + // Fetch ze serveru + var params: [String: Any] = [ + "conversation_id": convId, + "limit": limit, + "offset": offset, + ] + if let ts = afterTs { + params["after_ts"] = ts + } + let resp = await sendAndReceive(type: "get_messages", params: params) + + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data"), + let rawMessages = data["messages"] as? [[String: Any]] else { + // Offline fallback — vrátit z cache + if !cache.isEmpty && offset == 0 { + return buildFromCache(cache, limit: limit) + } + return [] + } + + // Dešifrovat nové zprávy (existující logika) + let newMessages = decryptRawMessages(rawMessages, cache: &cache, convId: convId) + + // mark_read jen pro nepřečtené + let unreadIds = rawMessages.filter { msg in + let senderId = msg["sender_id"] as? String ?? "" + if senderId == myId { return false } + let readBy = msg["read_by"] as? [[String: Any]] ?? [] + return !readBy.contains { ($0["user_id"] as? String) == myId } + }.compactMap { $0["message_id"] as? String } + + if !unreadIds.isEmpty { + await markRead(convId: convId, messageIds: unreadIds) + } + + if afterTs != nil { + // Inkrementální: sync smazaných + let delResp = await sendAndReceive(type: "get_deleted_since", params: [ + "conversation_id": convId, + "since": afterTs!, + ]) + if let delData = delResp.dict(for: "data"), + let delIds = delData["message_ids"] as? [String] { + for id in delIds { + cache.removeValue(forKey: id) + removeCachedMessage(convId: convId, msgId: id) + } + } + return buildFromCache(cache, limit: limit) + } + + return newMessages +} + +/// Sestavit seřazený seznam z cache +func buildFromCache(_ cache: [String: [String: Any]], limit: Int) -> [Message] { + var messages: [Message] = [] + for (msgId, payload) in cache { + guard payload["_control"] == nil else { continue } + // Vytvořit Message z payload... + messages.append(messageFromPayload(msgId: msgId, payload: payload)) + } + messages.sort { $0.createdAt < $1.createdAt } + if messages.count > limit { + messages = Array(messages.suffix(limit)) + } + return messages +} +``` + +### 4. Co se změní v praxi + +| Situace | Dřív | Teď | +|---------|------|-----| +| Otevření konverzace kde jsem byl před 5 min | Server vrátí 50 zpráv (vše) | Server vrátí 0-2 nové zprávy | +| Otevření konverzace poprvé | Server vrátí 50 zpráv | Stejné (plný fetch) | +| Load older (scroll nahoru) | Server vrátí 50 starších | Stejné (offset > 0, plný fetch) | +| Po reconnectu | Server vrátí 50 zpráv | Server vrátí jen zprávy od odpojení | +| Offline | Nic (chyba) | Zobrazí cache | + +### 5. Metadata (read_by, reactions, pins) + +- **read_by** — neukládá se do cache (mění se často). Přichází v reálném čase přes `messages_read` notifikaci. Po reconnectu může být chvilku stale — přijatelné. +- **reactions** — server je vrací u každé zprávy. V cache se ukládají. Aktualizace přes `message_reacted` notifikaci v reálném čase. +- **pins** — stejně jako reactions. `message_pinned`/`message_unpinned` notifikace. +- Po inkrementálním fetchi jsou metadata aktuální jen pro NOVÉ zprávy. Starší mají stav z cache + real-time notifikací. Při plném fetchi (scroll nahoru / první load) jsou vždy aktuální. + +### 6. `ChatViewModel.loadMessages` — úprava + +```swift +func loadMessages(convId: String, chatClient: ChatClient) async { + isLoading = true + messages = await chatClient.getMessages(convId: convId, limit: 50) + isLoading = false + // mark_read se teď řeší uvnitř getMessages — tady nic + updatePinnedBanner() +} +``` + +`mark_read` volání se přesune z ViewModelu do `getMessages()` v ChatClientu (tam kde má přístup k `read_by` z response). diff --git a/ios_client/project.yml b/ios_client/project.yml new file mode 100644 index 0000000..5c6f78a --- /dev/null +++ b/ios_client/project.yml @@ -0,0 +1,33 @@ +name: EncryptedChat +options: + bundleIdPrefix: com.encryptedchat + deploymentTarget: + iOS: "16.0" + xcodeVersion: "15.0" + generateEmptyDirectories: true + +settings: + base: + SWIFT_VERSION: "5.9" + IPHONEOS_DEPLOYMENT_TARGET: "16.0" + ENABLE_PREVIEWS: "YES" + +targets: + EncryptedChat: + type: application + platform: iOS + sources: + - path: EncryptedChat + settings: + base: + GENERATE_INFOPLIST_FILE: "YES" + PRODUCT_BUNDLE_IDENTIFIER: com.encryptedchat.app + INFOPLIST_KEY_UIApplicationSceneManifest_Generation: "YES" + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents: "YES" + INFOPLIST_KEY_UILaunchScreen_Generation: "YES" + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone: "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight" + INFOPLIST_KEY_NSPhotoLibraryUsageDescription: "Select photos to share in chat" + INFOPLIST_KEY_NSCameraUsageDescription: "Take photos to share in chat" + INFOPLIST_KEY_CFBundleDisplayName: "Encrypted Chat" + INFOPLIST_KEY_LSApplicationCategoryType: "public.app-category.social-networking" + CODE_SIGN_STYLE: Automatic diff --git a/ios_client/v0.8.4_changes.md b/ios_client/v0.8.4_changes.md new file mode 100644 index 0000000..56ad6ab --- /dev/null +++ b/ios_client/v0.8.4_changes.md @@ -0,0 +1,1036 @@ +# iOS Client — v0.8.4 Changes + +Reakce (1 per user), Pinned Messages (banner), Forwarding, @Mentions, mark_read optimalizace. + +--- + +## 1. `Models/Message.swift` — Nové fieldy + +```swift +struct Message: Identifiable, Equatable { + let id: String + let conversationId: String + let senderId: String + var senderUsername: String + let createdAt: Date + var text: String? + var replyTo: String? + var imageFileId: String? + var file: FileInfo? + var isDeleted: Bool + var readBy: Set + + // --- v0.8.4 NEW --- + var reactions: [Reaction] // reakce na zprávu + var pinnedAt: String? // ISO timestamp pokud pinnutá, nil jinak + var pinnedBy: String? // user_id kdo pinnul + var forwardedFrom: ForwardInfo? // info o přeposlání + + func isMine(currentUserId: String) -> Bool { + senderId == currentUserId + } + + /// Vrátí reakci aktuálního uživatele (max 1 per user) + func myReaction(currentUserId: String) -> String? { + reactions.first(where: { $0.userId == currentUserId })?.reaction + } + + var isPinned: Bool { pinnedAt != nil } + + static func == (lhs: Message, rhs: Message) -> Bool { + lhs.id == rhs.id + } +} + +// --- v0.8.4 NEW structs --- + +struct Reaction: Equatable { + let userId: String + let reaction: String // "thumbsup", "heart", "laugh", "surprised", "sad", "thumbsdown" +} + +struct ForwardInfo: Equatable { + let sender: String // original sender username + let conversationId: String // original conversation + let messageId: String // original message id +} + +struct FileInfo: Equatable, Codable { + let fileId: String + let aesKey: String + let iv: String + let filename: String + let size: Int + let mimeType: String +} +``` + +--- + +## 2. `Core/ChatClient.swift` — Nové notifikace + metody + +### 2a. ChatNotification enum — přidat 3 nové case + +```swift +enum ChatNotification { + // ... existující cases ... + case sessionReset(data: [String: Any]) + case connectionStateChanged(connected: Bool) + + // --- v0.8.4 NEW --- + case messageReacted(data: [String: Any]) + case messagePinned(data: [String: Any]) + case messageUnpinned(data: [String: Any]) +} +``` + +### 2b. routeMessage() — přidat do notificationTypes setu a switch + +```swift +// V notificationTypes Set přidat: +let notificationTypes = Set([ + "new_message", "messages_read", "message_deleted", + "conversation_created", "member_added", "member_removed", + "user_online", "user_offline", "online_users", + "group_invitation", "conversation_renamed", "session_reset", + // v0.8.4: + "message_reacted", "message_pinned", "message_unpinned" +]) + +// V switch přidat: +case "message_reacted": + notificationContinuation?.yield(.messageReacted(data: data)) +case "message_pinned": + notificationContinuation?.yield(.messagePinned(data: data)) +case "message_unpinned": + notificationContinuation?.yield(.messageUnpinned(data: data)) +``` + +### 2c. getMessages() — parsovat reactions, pinned, forwarded_from + +V `getMessages()`, při vytváření Message objektu (cca řádek 1289), parsovat nová pole: + +```swift +// Po dekrypci JSON payloadu (jsonObj), před vytvořením Message: +// --- v0.8.4: Parse forwarded_from --- +var forwardedFrom: ForwardInfo? +if let fwd = jsonObj["forwarded_from"] as? [String: Any] { + forwardedFrom = ForwardInfo( + sender: fwd["sender"] as? String ?? "?", + conversationId: fwd["conversation_id"] as? String ?? "", + messageId: fwd["message_id"] as? String ?? "" + ) +} + +// --- v0.8.4: Parse reactions from server response (on msgDict, not jsonObj!) --- +var reactions: [Reaction] = [] +if let reactionsRaw = msgDict["reactions"] as? [[String: Any]] { + for r in reactionsRaw { + if let uid = r["user_id"] as? String, let rtype = r["reaction"] as? String { + reactions.append(Reaction(userId: uid, reaction: rtype)) + } + } +} + +// --- v0.8.4: Parse pinned --- +let pinnedAt = msgDict["pinned_at"] as? String // ISO string or nil +let pinnedBy = msgDict["pinned_by"] as? String + +// Pak v Message(...) přidat: +messages.append(Message( + id: msgId, conversationId: convId, senderId: senderId, + senderUsername: msgDict.string(for: "sender_username") ?? "", + createdAt: createdAt, text: messageText, replyTo: replyTo, + imageFileId: msgDict.string(for: "image_file_id"), file: file, + isDeleted: false, readBy: [], + reactions: reactions, // NEW + pinnedAt: pinnedAt, // NEW + pinnedBy: pinnedBy, // NEW + forwardedFrom: forwardedFrom // NEW +)) +``` + +**POZOR:** Taky pro deleted messages a fallback append volat s defaultními hodnotami: +```swift +reactions: [], pinnedAt: nil, pinnedBy: nil, forwardedFrom: nil +``` + +### 2d. Nové metody — react, pin, get_pinned, forward + +```swift +// MARK: - Reactions (v0.8.4) + +static let allowedReactions = ["thumbsup", "heart", "laugh", "surprised", "sad", "thumbsdown"] + +func reactMessage(messageId: String, reaction: String, action: String = "add") async -> Bool { + let resp = await sendAndReceive(type: "react_message", params: [ + "message_id": messageId, + "reaction": reaction, + "action": action, + ]) + return resp.string(for: "status") == "ok" +} + +// MARK: - Pins (v0.8.4) + +func pinMessage(messageId: String, conversationId: String, action: String = "pin") async -> Bool { + let resp = await sendAndReceive(type: "pin_message", params: [ + "message_id": messageId, + "conversation_id": conversationId, + "action": action, + ]) + return resp.string(for: "status") == "ok" +} + +func getPinnedMessages(conversationId: String) async -> [String] { + let resp = await sendAndReceive(type: "get_pinned_messages", params: [ + "conversation_id": conversationId, + ]) + guard resp.string(for: "status") == "ok", + let data = resp.dict(for: "data"), + let msgs = data["messages"] as? [[String: Any]] else { + return [] + } + return msgs.compactMap { $0["message_id"] as? String } +} + +// MARK: - Forward (v0.8.4) + +func forwardMessage(targetConvId: String, originalMsg: Message, + targetMembers: [ConversationMember]) async -> Bool { + // Forward = normální send_message s forwarded_from v payloadu + var text = originalMsg.text ?? "" + if originalMsg.imageFileId != nil { + text = "[Forwarded image]" + } + if originalMsg.file != nil { + text = "[Forwarded file: \(originalMsg.file?.filename ?? "file")]" + } + + // Sestavit payload JSON s forwarded_from + // Tady záleží na implementaci sendMessage — buď přidat parametr forwardedFrom, + // nebo vytvořit payload ručně. Nejjednodušší: přidat optional parametr do sendMessage. + let (success, _) = await sendMessage( + convId: targetConvId, text: text, members: targetMembers, + forwardedFrom: ForwardInfo( + sender: originalMsg.senderUsername, + conversationId: originalMsg.conversationId, + messageId: originalMsg.id + ) + ) + return success +} +``` + +### 2e. sendMessage() — přidat optional forwardedFrom parametr + +V existující `sendMessage()` funkci přidat parametr a propagovat do payloadu: + +```swift +func sendMessage(convId: String, text: String, members: [ConversationMember], + replyTo: String? = nil, + forwardedFrom: ForwardInfo? = nil // NEW +) async -> (Bool, String) { + // ... existující kód ... + + // Kde se sestavuje payload dict (jsonObj), přidat: + if let fwd = forwardedFrom { + payload["forwarded_from"] = [ + "sender": fwd.sender, + "conversation_id": fwd.conversationId, + "message_id": fwd.messageId, + ] + } + + // ... zbytek beze změny ... +} +``` + +### 2f. markRead optimalizace + +V `getMessages()`, **po** sestavení `messages` pole a **před** return, změnit mark_read logiku: + +```swift +// STARÉ (řádky 21-25 v ChatViewModel.loadMessages): +let unreadIds = messages.filter { !$0.isMine(currentUserId: ...) }.map(\.id) + +// NOVÉ — filtrovat jen zprávy co ještě nejsou přečtené: +// V getMessages() zpracovat read_by z server response: +let readByRaw = msgDict["read_by"] as? [[String: Any]] ?? [] +let readBySet = Set(readByRaw.compactMap { $0["user_id"] as? String }) +// ... a předat do Message(... readBy: readBySet ...) + +// Pak v ChatViewModel.loadMessages: +let myId = await chatClient.userId ?? "" +let unreadIds = messages.filter { + !$0.isMine(currentUserId: myId) && !$0.readBy.contains(myId) +}.map(\.id) +if !unreadIds.isEmpty { + await chatClient.markRead(convId: convId, messageIds: unreadIds) +} +``` + +--- + +## 3. `ViewModels/ChatViewModel.swift` — Notification handling + nové metody + +```swift +@Observable +final class ChatViewModel { + var messages: [Message] = [] + var isLoading = false + var isSending = false + var errorMessage: String? + var searchQuery = "" + var searchResults: [String] = [] + var currentSearchIndex = 0 + var pinnedMessage: Message? // NEW — pro banner + + private var notificationTask: Task? + + func loadMessages(convId: String, chatClient: ChatClient) async { + isLoading = true + messages = await chatClient.getMessages(convId: convId, limit: 50) + isLoading = false + + // v0.8.4: Jen nepřečtené zprávy od jiných + let myId = await chatClient.userId ?? "" + let unreadIds = messages.filter { + !$0.isMine(currentUserId: myId) && !$0.readBy.contains(myId) + }.map(\.id) + if !unreadIds.isEmpty { + await chatClient.markRead(convId: convId, messageIds: unreadIds) + } + + // v0.8.4: Update pin banner + updatePinnedBanner() + } + + // --- v0.8.4 NEW --- + + /// Aktualizovat pin banner z aktuálních zpráv + func updatePinnedBanner() { + pinnedMessage = messages.last(where: { $0.isPinned }) + } + + /// Reakce — optimistický update + server call + func react(messageId: String, reaction: String, chatClient: ChatClient) async { + let myId = await chatClient.userId ?? "" + + // Optimistický update + if let idx = messages.firstIndex(where: { $0.id == messageId }) { + let existing = messages[idx].myReaction(currentUserId: myId) + if existing == reaction { + // Toggle off + messages[idx].reactions.removeAll { $0.userId == myId } + await chatClient.reactMessage(messageId: messageId, reaction: reaction, action: "remove") + } else { + // Nahradit (1 per user) + messages[idx].reactions.removeAll { $0.userId == myId } + messages[idx].reactions.append(Reaction(userId: myId, reaction: reaction)) + await chatClient.reactMessage(messageId: messageId, reaction: reaction, action: "add") + } + } + } + + /// Pin/Unpin — optimistický update + server call + func togglePin(messageId: String, convId: String, chatClient: ChatClient) async { + let myId = await chatClient.userId ?? "" + if let idx = messages.firstIndex(where: { $0.id == messageId }) { + if messages[idx].isPinned { + messages[idx].pinnedAt = nil + messages[idx].pinnedBy = nil + await chatClient.pinMessage(messageId: messageId, conversationId: convId, action: "unpin") + } else { + messages[idx].pinnedAt = "now" + messages[idx].pinnedBy = myId + await chatClient.pinMessage(messageId: messageId, conversationId: convId, action: "pin") + } + updatePinnedBanner() + } + } + + // --- Notification handler — přidat nové cases --- + + @MainActor + private func handleNotification(_ notification: ChatNotification, convId: String, chatClient: ChatClient) { + switch notification { + // ... existující cases (newMessage, messageDeleted, messagesRead) ... + + // --- v0.8.4 NEW --- + case .messageReacted(let data): + guard data["conversation_id"] as? String == convId else { break } + let msgId = data["message_id"] as? String ?? "" + let userId = data["user_id"] as? String ?? "" + let reaction = data["reaction"] as? String ?? "" + let action = data["action"] as? String ?? "add" + + if let idx = messages.firstIndex(where: { $0.id == msgId }) { + if action == "add" { + // Remove old (1 per user) + add new + messages[idx].reactions.removeAll { $0.userId == userId } + messages[idx].reactions.append(Reaction(userId: userId, reaction: reaction)) + } else { + messages[idx].reactions.removeAll { $0.userId == userId } + } + } + + case .messagePinned(let data): + guard data["conversation_id"] as? String == convId else { break } + let msgId = data["message_id"] as? String ?? "" + let userId = data["user_id"] as? String ?? "" + if let idx = messages.firstIndex(where: { $0.id == msgId }) { + messages[idx].pinnedAt = "now" + messages[idx].pinnedBy = userId + updatePinnedBanner() + } + + case .messageUnpinned(let data): + guard data["conversation_id"] as? String == convId else { break } + let msgId = data["message_id"] as? String ?? "" + if let idx = messages.firstIndex(where: { $0.id == msgId }) { + messages[idx].pinnedAt = nil + messages[idx].pinnedBy = nil + updatePinnedBanner() + } + + default: + break + } + } +} +``` + +--- + +## 4. `Views/Chat/MessageBubbleView.swift` — Reakce, forwarded, pin, context menu + +```swift +import SwiftUI + +struct MessageBubbleView: View { + let message: Message + let isMine: Bool + let currentUserId: String // NEW — pro reaction check + var isHighlighted: Bool = false + var isCurrentSearchResult: Bool = false + var onReply: (() -> Void)? + var onDelete: (() -> Void)? + var onReact: ((String) -> Void)? // NEW — reaction callback + var onPin: (() -> Void)? // NEW — pin callback + var onForward: (() -> Void)? // NEW — forward callback + + // Emoji mapa + private static let reactionEmoji: [String: String] = [ + "thumbsup": "👍", "heart": "❤️", "laugh": "😂", + "surprised": "😮", "sad": "😢", "thumbsdown": "👎", + ] + + var body: some View { + HStack { + if isMine { Spacer(minLength: 60) } + + VStack(alignment: isMine ? .trailing : .leading, spacing: 4) { + if !isMine { + Text(message.senderUsername) + .font(.caption.bold()) + .foregroundStyle(.secondary) + } + + if message.isDeleted { + Text("Message deleted") + .font(.body.italic()) + .foregroundStyle(.secondary) + .padding(12) + .background(Color(.systemGray6)) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } else { + // --- v0.8.4: Forwarded from header --- + if let fwd = message.forwardedFrom { + HStack(spacing: 4) { + Rectangle() + .fill(.cyan.opacity(0.6)) + .frame(width: 2) + VStack(alignment: .leading, spacing: 0) { + Text("Forwarded from") + .font(.caption2) + .foregroundStyle(.secondary) + Text(fwd.sender) + .font(.caption.bold()) + .foregroundStyle(.cyan) + } + } + .padding(.horizontal, 8) + .padding(.vertical, 2) + } + + // Reply reference + if let _ = message.replyTo { + HStack(spacing: 4) { + Rectangle() + .fill(.blue.opacity(0.5)) + .frame(width: 2) + Text("Reply to message") + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(.horizontal, 8) + } + + // File card + if let file = message.file { + VStack(alignment: .leading, spacing: 4) { + HStack { + Image(systemName: "paperclip") + Text(file.filename).lineLimit(1) + } + .font(.subheadline) + Text(formatFileSize(file.size)) + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(12) + .background(Color(.systemGray5)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } + + // Text content + pin indicator + if let text = message.text { + HStack(alignment: .top, spacing: 4) { + Text(highlightMentions(text)) + .padding(12) + + // --- v0.8.4: Pin indicator --- + if message.isPinned { + Text("📌") + .font(.caption2) + .padding(.top, 8) + } + } + .background(isMine ? Color.blue : Color(.systemGray5)) + .foregroundStyle(isMine ? .white : .primary) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } + + // --- v0.8.4: Reaction badges --- + if !message.reactions.isEmpty { + reactionBadges + } + + // Timestamp + Text(formatTime(message.createdAt)) + .font(.caption2) + .foregroundStyle(.secondary) + } + } + .background( + isCurrentSearchResult ? Color.orange.opacity(0.3) : + isHighlighted ? Color.yellow.opacity(0.2) : Color.clear + ) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .contextMenu { + if !message.isDeleted { + Button(action: { onReply?() }) { + Label("Reply", systemImage: "arrowshape.turn.up.left") + } + + Button(action: { UIPasteboard.general.string = message.text ?? "" }) { + Label("Copy", systemImage: "doc.on.doc") + } + + // --- v0.8.4: Forward --- + Button(action: { onForward?() }) { + Label("Forward", systemImage: "arrowshape.turn.up.right") + } + + // --- v0.8.4: Pin/Unpin --- + Button(action: { onPin?() }) { + Label(message.isPinned ? "Unpin" : "Pin", + systemImage: message.isPinned ? "pin.slash" : "pin") + } + + Divider() + + // --- v0.8.4: Reactions submenu --- + Menu { + ForEach(Array(Self.reactionEmoji.sorted(by: { $0.key < $1.key })), id: \.key) { key, emoji in + Button(action: { onReact?(key) }) { + let isMine = message.myReaction(currentUserId: currentUserId) == key + Label( + "\(emoji) \(isMine ? "✓" : "")", + systemImage: isMine ? "checkmark.circle.fill" : "face.smiling" + ) + } + } + } label: { + Label("React", systemImage: "face.smiling") + } + + if isMine { + Divider() + Button(role: .destructive, action: { onDelete?() }) { + Label("Delete", systemImage: "trash") + } + } + } + } + + if !isMine { Spacer(minLength: 60) } + } + } + + // --- v0.8.4: Reaction badges view --- + private var reactionBadges: some View { + // Seskupit reakce: [reaction: [userId]] + let grouped = Dictionary(grouping: message.reactions, by: \.reaction) + + return HStack(spacing: 4) { + ForEach(grouped.sorted(by: { $0.key < $1.key }), id: \.key) { reaction, users in + let emoji = Self.reactionEmoji[reaction] ?? reaction + let isMine = users.contains(where: { $0.userId == currentUserId }) + + HStack(spacing: 2) { + Text(emoji) + .font(.caption2) + if users.count > 1 { + Text("\(users.count)") + .font(.caption2) + } + } + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(isMine ? Color.blue.opacity(0.2) : Color(.systemGray5)) + .clipShape(Capsule()) + .overlay( + Capsule() + .stroke(isMine ? Color.blue.opacity(0.5) : Color.clear, lineWidth: 1) + ) + } + } + } + + // --- v0.8.4: @mention highlighting --- + private func highlightMentions(_ text: String) -> AttributedString { + var result = AttributedString(text) + // Najít @username patterny a zvýraznit modře + let pattern = try? NSRegularExpression(pattern: "@(\\w+)") + let nsText = text as NSString + let matches = pattern?.matches(in: text, range: NSRange(location: 0, length: nsText.length)) ?? [] + for match in matches.reversed() { + if let range = Range(match.range, in: text), + let attrRange = Range(range, in: result) { + result[attrRange].foregroundColor = .blue + result[attrRange].font = .body.bold() + } + } + return result + } + + private func formatTime(_ date: Date) -> String { + let formatter = DateFormatter() + if Calendar.current.isDateInToday(date) { + formatter.dateFormat = "HH:mm" + } else { + formatter.dateFormat = "MMM d, HH:mm" + } + return formatter.string(from: date) + } + + private func formatFileSize(_ bytes: Int) -> String { + if bytes < 1024 { return "\(bytes) B" } + if bytes < 1024 * 1024 { return "\(bytes / 1024) KB" } + return String(format: "%.1f MB", Double(bytes) / (1024 * 1024)) + } +} +``` + +--- + +## 5. `Views/Chat/ChatView.swift` — Pin banner, forward dialog, nové callbacky + +```swift +import SwiftUI + +struct ChatView: View { + let conversation: Conversation + var appState: AppState + @State private var viewModel = ChatViewModel() + @State private var inputText = "" + @State private var replyTo: Message? + @State private var showGroupInfo = false + @State private var showSearch = false + @State private var showDeleteConfirm = false + @State private var showForwardPicker: Message? // NEW — zpráva k přeposlání + @State private var showPinnedList = false // NEW — dialog pinnutých zpráv + + var body: some View { + VStack(spacing: 0) { + // Search bar + if showSearch { + SearchOverlayView( + query: $viewModel.searchQuery, + matchCount: viewModel.searchResults.count, + currentIndex: viewModel.currentSearchIndex, + onSearch: { viewModel.search(query: $0) }, + onNext: { viewModel.nextSearchResult() }, + onPrev: { viewModel.prevSearchResult() }, + onClose: { showSearch = false; viewModel.search(query: "") } + ) + } + + // --- v0.8.4: Pinned message banner --- + if let pinned = viewModel.pinnedMessage { + PinnedBannerView(message: pinned) { + // Scroll to pinned message + // (proxy reference needed — viz ScrollViewReader níže) + } + .onTapGesture { + showPinnedList = true + } + } + + // Messages + ScrollViewReader { proxy in + ScrollView { + LazyVStack(spacing: 8) { + if viewModel.messages.count >= 50 { + Button("Load older messages") { + Task { + await viewModel.loadOlderMessages( + convId: conversation.id, + chatClient: appState.chatClient + ) + } + } + .font(.caption) + .padding() + } + + ForEach(viewModel.messages) { message in + MessageBubbleView( + message: message, + isMine: message.isMine(currentUserId: appState.currentUser?.id ?? ""), + currentUserId: appState.currentUser?.id ?? "", // NEW + isHighlighted: viewModel.searchResults.contains(message.id), + isCurrentSearchResult: viewModel.searchResults.indices.contains(viewModel.currentSearchIndex) && + viewModel.searchResults[viewModel.currentSearchIndex] == message.id, + onReply: { replyTo = message }, + onDelete: { + Task { + await viewModel.deleteMessage( + messageId: message.id, + convId: conversation.id, + chatClient: appState.chatClient + ) + } + }, + // --- v0.8.4 NEW callbacks --- + onReact: { reaction in + Task { + await viewModel.react( + messageId: message.id, + reaction: reaction, + chatClient: appState.chatClient + ) + } + }, + onPin: { + Task { + await viewModel.togglePin( + messageId: message.id, + convId: conversation.id, + chatClient: appState.chatClient + ) + } + }, + onForward: { + showForwardPicker = message + } + ) + .id(message.id) + } + } + .padding(.horizontal) + .padding(.vertical, 8) + } + .onChange(of: viewModel.messages.count) { + if let lastId = viewModel.messages.last?.id { + withAnimation { + proxy.scrollTo(lastId, anchor: .bottom) + } + } + } + // --- v0.8.4: Scroll to pinned on banner tap --- + .onChange(of: showPinnedList) { + if !showPinnedList, let pinId = viewModel.pinnedMessage?.id { + withAnimation { + proxy.scrollTo(pinId, anchor: .center) + } + } + } + } + + // Reply preview + if let reply = replyTo { + HStack { + Rectangle().fill(.blue).frame(width: 3) + VStack(alignment: .leading) { + Text(reply.senderUsername).font(.caption.bold()) + Text(reply.text ?? "").font(.caption).lineLimit(1) + } + Spacer() + Button(action: { replyTo = nil }) { + Image(systemName: "xmark.circle.fill").foregroundStyle(.secondary) + } + } + .padding(.horizontal) + .padding(.vertical, 6) + .background(.ultraThinMaterial) + } + + // Input + MessageInputView( + text: $inputText, + isSending: viewModel.isSending, + onSend: { + Task { + let text = inputText + inputText = "" + let reply = replyTo?.id + replyTo = nil + await viewModel.sendMessage( + convId: conversation.id, + text: text, + members: conversation.members, + chatClient: appState.chatClient, + replyTo: reply + ) + } + } + ) + } + .navigationTitle(conversation.displayName(currentUserId: appState.currentUser?.id ?? "")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + HStack(spacing: 16) { + // --- v0.8.4: Pinned messages button --- + Button(action: { showPinnedList = true }) { + Image(systemName: "pin") + } + + Button(action: { showSearch.toggle() }) { + Image(systemName: "magnifyingglass") + } + if conversation.isGroup { + Button(action: { showGroupInfo = true }) { + Image(systemName: "info.circle") + } + } + if !conversation.isGroup || conversation.createdBy == appState.currentUser?.id { + Button(action: { showDeleteConfirm = true }) { + Image(systemName: "trash").foregroundStyle(.red) + } + } + } + } + } + .alert("Delete Conversation?", isPresented: $showDeleteConfirm) { + Button("Cancel", role: .cancel) {} + Button("Delete", role: .destructive) { + Task { await appState.chatClient.deleteConversation(convId: conversation.id) } + } + } message: { + Text(conversation.isGroup + ? "This will remove all members and delete the conversation." + : "This will remove you from the conversation.") + } + // --- v0.8.4: Forward picker sheet --- + .sheet(item: $showForwardPicker) { message in + ForwardPickerView( + message: message, + appState: appState, + onForward: { targetConv in + Task { + await appState.chatClient.forwardMessage( + targetConvId: targetConv.id, + originalMsg: message, + targetMembers: targetConv.members + ) + } + showForwardPicker = nil + } + ) + } + // --- v0.8.4: Pinned messages list sheet --- + .sheet(isPresented: $showPinnedList) { + PinnedMessagesView( + messages: viewModel.messages.filter(\.isPinned), + onSelect: { msg in + showPinnedList = false + // ScrollViewReader scroll handled by onChange above + } + ) + } + .sheet(isPresented: $showGroupInfo) { + GroupInfoView(conversation: conversation, appState: appState) + } + .task { + await viewModel.loadMessages(convId: conversation.id, chatClient: appState.chatClient) + viewModel.startNotificationListener(convId: conversation.id, chatClient: appState.chatClient) + } + .onDisappear { + viewModel.stop() + } + } +} +``` + +**POZNÁMKA:** `Message` musí být `Identifiable` (už je) pro `.sheet(item:)` na `showForwardPicker`. + +--- + +## 6. Nové pomocné views + +### `Views/Chat/PinnedBannerView.swift` (NOVÝ SOUBOR) + +```swift +import SwiftUI + +struct PinnedBannerView: View { + let message: Message + var onTap: (() -> Void)? + + var body: some View { + HStack(spacing: 8) { + Image(systemName: "pin.fill") + .foregroundStyle(.yellow) + .font(.caption) + + VStack(alignment: .leading, spacing: 1) { + Text(message.senderUsername) + .font(.caption.bold()) + Text(message.text ?? "") + .font(.caption) + .lineLimit(1) + .foregroundStyle(.secondary) + } + + Spacer() + } + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(Color(.systemGray5)) + .contentShape(Rectangle()) + .onTapGesture { onTap?() } + } +} +``` + +### `Views/Chat/ForwardPickerView.swift` (NOVÝ SOUBOR) + +```swift +import SwiftUI + +struct ForwardPickerView: View { + let message: Message + var appState: AppState + var onForward: (Conversation) -> Void + + @State private var conversations: [Conversation] = [] + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + List(conversations) { conv in + Button(action: { onForward(conv) }) { + HStack { + Text(conv.displayName(currentUserId: appState.currentUser?.id ?? "")) + Spacer() + Image(systemName: "arrowshape.turn.up.right") + .foregroundStyle(.secondary) + } + } + } + .navigationTitle("Forward to...") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } + } + } + .task { + conversations = await appState.chatClient.listConversations() + .filter { $0.id != message.conversationId } + } + } + } +} +``` + +### `Views/Chat/PinnedMessagesView.swift` (NOVÝ SOUBOR) + +```swift +import SwiftUI + +struct PinnedMessagesView: View { + let messages: [Message] + var onSelect: (Message) -> Void + + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + Group { + if messages.isEmpty { + ContentUnavailableView( + "No Pinned Messages", + systemImage: "pin.slash", + description: Text("Pin important messages to find them easily.") + ) + } else { + List(messages) { msg in + Button(action: { onSelect(msg) }) { + VStack(alignment: .leading, spacing: 4) { + HStack { + Image(systemName: "pin.fill") + .foregroundStyle(.yellow) + .font(.caption) + Text(msg.senderUsername) + .font(.subheadline.bold()) + } + Text(msg.text ?? "") + .font(.subheadline) + .lineLimit(2) + .foregroundStyle(.secondary) + } + } + } + } + } + .navigationTitle("Pinned Messages") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Close") { dismiss() } + } + } + } + } +} +``` + +--- + +## 7. Shrnutí všech souborů k úpravě/přidání + +| Soubor | Akce | Popis | +|--------|------|-------| +| `Models/Message.swift` | EDIT | +Reaction, +ForwardInfo structs, +reactions/pinnedAt/forwardedFrom fieldy | +| `Core/ChatClient.swift` | EDIT | +3 notification types, +routeMessage dispatch, +getMessages parsing, +react/pin/forward metody, +sendMessage forwardedFrom param | +| `ViewModels/ChatViewModel.swift` | EDIT | +pinnedMessage, +updatePinnedBanner, +react(), +togglePin(), +3 notification cases, +mark_read optimalizace | +| `Views/Chat/MessageBubbleView.swift` | EDIT | +currentUserId, +onReact/onPin/onForward callbacky, +forwarded header, +pin indicator, +reaction badges, +@mention highlighting, +context menu items | +| `Views/Chat/ChatView.swift` | EDIT | +pin banner, +showForwardPicker, +showPinnedList, +nové callbacky, +pin toolbar button | +| `Views/Chat/PinnedBannerView.swift` | NEW | Pin banner component | +| `Views/Chat/ForwardPickerView.swift` | NEW | Forward conversation picker | +| `Views/Chat/PinnedMessagesView.swift` | NEW | Pinned messages list dialog | diff --git a/protocol.py b/protocol.py new file mode 100644 index 0000000..8135a6a --- /dev/null +++ b/protocol.py @@ -0,0 +1,142 @@ +"""Newline-delimited JSON protocol with base64 encoding for binary data.""" + +import asyncio +import base64 +import binascii +import json +import os + + +def encode_binary(data: bytes) -> str: + """Encode bytes to base64 string.""" + return base64.b64encode(data).decode("ascii") + + +def decode_binary(data: str) -> bytes: + """Decode base64 string to bytes.""" + try: + return base64.b64decode(data, validate=True) + except (TypeError, binascii.Error) as e: + raise ValueError(f"Invalid base64: {e}") + + +VERSION = "0.8.5" +MIN_CLIENT_VERSION = "0.8.5" # server rejects clients older than this + + +def version_gte(version: str, minimum: str) -> bool: + """Return True if version >= minimum (compares numeric tuples, e.g. '0.8.1' >= '0.8'). + + Returns False for malformed version strings (instead of silently treating them as 0). + """ + def _parse(v: str) -> tuple[int, ...] | None: + if not isinstance(v, str) or not v: + return None + parts = v.split(".") + try: + return tuple(int(x) for x in parts) + except (ValueError, AttributeError): + return None + parsed_ver = _parse(version) + parsed_min = _parse(minimum) + if parsed_ver is None or parsed_min is None: + return False + return parsed_ver >= parsed_min + + +MAX_MESSAGE_BYTES = int(os.getenv("MAX_MESSAGE_BYTES", "65536")) # 64 KiB default +MAX_IMAGE_BYTES = int(os.getenv("MAX_IMAGE_BYTES", str(5 * 1024 * 1024))) # 5 MiB default, 0 = no limit +MAX_FILE_BYTES = int(os.getenv("MAX_FILE_BYTES", str(50 * 1024 * 1024))) # 50 MiB default +IMAGE_CHUNK_SIZE = 32768 # 32 KiB raw chunk size for image upload/download + + +def build_request(msg_type: str, request_id: str | None = None, **kwargs) -> bytes: + """Build a protocol message (newline-terminated JSON).""" + msg = {"type": msg_type, **kwargs} + if request_id: + msg["request_id"] = request_id + return json.dumps(msg, ensure_ascii=False).encode("utf-8") + b"\n" + + +def build_response( + msg_type: str, + status: str, + data: dict | None = None, + request_id: str | None = None, +) -> bytes: + """Build a server response.""" + msg = {"type": msg_type, "status": status} + if data is not None: + msg["data"] = data + if request_id: + msg["request_id"] = request_id + return json.dumps(msg, ensure_ascii=False).encode("utf-8") + b"\n" + + +def parse_message(line: bytes) -> dict: + """Parse a single protocol message from bytes.""" + try: + return json.loads(line.decode("utf-8")) + except (json.JSONDecodeError, UnicodeDecodeError) as e: + raise ValueError(f"Invalid message: {e}") + + +class ProtocolReader: + """Read newline-delimited JSON messages from an asyncio StreamReader.""" + + def __init__(self, reader: asyncio.StreamReader): + self._reader = reader + + async def read_message(self) -> dict | None: + """Read and parse one message. Returns None on EOF.""" + try: + line = await self._reader.readuntil(b"\n") + except (asyncio.IncompleteReadError, ConnectionError): + return None + except asyncio.LimitOverrunError as e: + # Message exceeded StreamReader limit — drain oversized data + # using public read() API (consumed=e.consumed bytes before limit). + # Read in chunks until newline found or EOF, then signal error. + remaining = e.consumed + while True: + chunk = await self._reader.read(max(remaining, 4096)) + if not chunk: + return None # EOF while draining + if b"\n" in chunk: + break # found delimiter, oversized message fully drained + raise ValueError("Message exceeds maximum size") + if not line: + return None + return parse_message(line.strip()) + + +class ProtocolWriter: + """Write newline-delimited JSON messages to an asyncio StreamWriter.""" + + def __init__(self, writer: asyncio.StreamWriter): + self._writer = writer + + async def send_request(self, msg_type: str, request_id: str | None = None, **kwargs): + """Send a request message.""" + payload = build_request(msg_type, request_id=request_id, **kwargs) + if len(payload) > MAX_MESSAGE_BYTES: + raise ValueError(f"Message exceeds limit ({len(payload)} > {MAX_MESSAGE_BYTES})") + self._writer.write(payload) + await self._writer.drain() + + async def send_response( + self, + msg_type: str, + status: str, + data: dict | None = None, + request_id: str | None = None, + ): + """Send a response message.""" + payload = build_response(msg_type, status, data, request_id=request_id) + if len(payload) > MAX_MESSAGE_BYTES: + raise ValueError(f"Message exceeds limit ({len(payload)} > {MAX_MESSAGE_BYTES})") + self._writer.write(payload) + await self._writer.drain() + + def close(self): + self._writer.close() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b4d7cb7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +cryptography>=42.0.0 +mysql-connector-python>=8.3.0 +python-dotenv>=1.0.0 +# GUI client (optional, needed for gui_client.py) +PyQt6>=6.6.0 +# Image sharing (optional, needed for send_image feature) +Pillow>=10.0.0 +# QR code generation for contact verification (optional) +qrcode[pil]>=7.4 +# QR code scanning (needed for gui_client.py QR scan feature) +pyzbar>=0.1.9 diff --git a/scaling.md b/scaling.md new file mode 100644 index 0000000..742df17 --- /dev/null +++ b/scaling.md @@ -0,0 +1,252 @@ +# Škálování serveru — plán kapacitního růstu + +## Cílový hardware + +- **CPU:** Intel Xeon E5-2630v4 (10 cores / 20 threads, 2.2 GHz) +- **RAM:** 256 GB REG ECC +- **Disk:** 500 GB SSD (boot/OS/DB) + 4 TB HDD (soubory) +- **Síť:** 1 Gbit + +Odhadovaná kapacita po optimalizaci: **10 000–20 000 uživatelů**, **2000–5000 zpráv/s** + +--- + +## Krok 1: Okamžité změny (hotovo v kódu) + +### 1a. Thread pool — `server.py` + +```env +THREAD_POOL_SIZE=40 +``` + +Nastavuje `ThreadPoolExecutor(max_workers=40)` jako default executor pro `asyncio.to_thread()`. +S 20 HW thready a DB latencí ~2–5ms je 40 workerů optimální (2x HW threads — workery čekají na I/O). + +### 1b. DB pool — `.env` + +```env +DB_POOL_SIZE=30 +``` + +30 simultánních MySQL spojení. S 40 thread workers a ~2ms query je 30 pool konexí dostatek. + +### 1c. Chybějící DB indexy — `schema.sql` + +Přidány 5 nových indexů pro nejčastější dotazy: + +| Index | Tabulka | Dotaz který zrychlí | +|-------|---------|---------------------| +| `idx_cm_user (user_id)` | `conversation_members` | `list_user_conversations` — **kritický**, bez něj full table scan | +| `idx_inv_user (user_id)` | `group_invitations` | `get_pending_invitations` | +| `idx_messages_deleted (conversation_id, deleted_at)` | `messages` | `get_deleted_messages_since` | +| `idx_messages_pinned (conversation_id, pinned_at)` | `messages` | `get_pinned_messages` | +| `idx_reads_user (user_id)` | `message_reads` | `get_unread_counts` | + +**SQL migrace pro existující databázi:** + +```sql +ALTER TABLE conversation_members ADD INDEX idx_cm_user (user_id); +ALTER TABLE group_invitations ADD INDEX idx_inv_user (user_id); +ALTER TABLE messages ADD INDEX idx_messages_deleted (conversation_id, deleted_at); +ALTER TABLE messages ADD INDEX idx_messages_pinned (conversation_id, pinned_at); +ALTER TABLE message_reads ADD INDEX idx_reads_user (user_id); +``` + +### 1d. Upload adresář na HDD + +```env +UPLOAD_DIR=/mnt/hdd/encrypted_chat/uploads +``` + +Šifrované soubory a avatary na 4TB HDD — SSD zůstane pro OS a MySQL data. + +```bash +mkdir -p /mnt/hdd/encrypted_chat/uploads +chmod 700 /mnt/hdd/encrypted_chat/uploads +``` + +--- + +## Krok 2: MySQL tuning pro 256 GB RAM + +### `/etc/mysql/mysql.conf.d/tuning.cnf` (nebo ekvivalent v Dockeru) + +```ini +[mysqld] +# === Buffer Pool — hlavní cache pro data + indexy === +# 96 GB = ~37% RAM (MySQL + app na stejném stroji) +innodb_buffer_pool_size = 96G +innodb_buffer_pool_instances = 16 + +# === Redo Log — větší = méně I/O, rychlejší zápisy === +innodb_redo_log_capacity = 4G + +# === Flush strategie === +# 2 = flush do OS cache každou sekundu (ne každý commit) +# Ztráta max 1s dat při pádu OS, ale 10x rychlejší zápisy +innodb_flush_log_at_trx_commit = 2 +# O_DIRECT = bypass OS page cache (InnoDB má vlastní) +innodb_flush_method = O_DIRECT + +# === I/O kapacita (SSD) === +innodb_io_capacity = 2000 +innodb_io_capacity_max = 4000 + +# === Connections === +max_connections = 200 + +# === Sort/Join buffery === +sort_buffer_size = 4M +join_buffer_size = 4M +read_buffer_size = 2M +read_rnd_buffer_size = 2M + +# === Temporary tables === +tmp_table_size = 256M +max_heap_table_size = 256M + +# === Query cache (MySQL 8.0+ nemá, pro 5.7) === +# query_cache_type = 0 + +# === Thread cache === +thread_cache_size = 64 + +# === Binary logging (pro budoucí repliky) === +# server-id = 1 +# log_bin = /var/log/mysql/mysql-bin +# binlog_expire_logs_seconds = 604800 +# max_binlog_size = 256M +``` + +**Pokud MySQL běží v Dockeru:** + +```yaml +# docker-compose.yml +services: + mysql: + image: mysql:8.0 + volumes: + - /var/lib/mysql:/var/lib/mysql # data na SSD + - ./tuning.cnf:/etc/mysql/conf.d/tuning.cnf + deploy: + resources: + limits: + memory: 128G # limitovat aby zbylo pro app + environment: + MYSQL_DATABASE: encrypted_chat +``` + +### Po aplikaci restartovat MySQL a ověřit: + +```sql +SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; +SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; +SHOW ENGINE INNODB STATUS\G +``` + +--- + +## Krok 3: Doporučená `.env` pro produkci + +```env +# Server +SERVER_HOST=0.0.0.0 +SERVER_PORT=9999 + +# MySQL +MYSQL_HOST=127.0.0.1 +MYSQL_PORT=3306 +MYSQL_USER=sifrator +MYSQL_PASSWORD= +MYSQL_DATABASE=encrypted_chat +DB_POOL_SIZE=30 + +# Performance +THREAD_POOL_SIZE=40 + +# Storage +UPLOAD_DIR=/mnt/hdd/encrypted_chat/uploads + +# TLS (zapnout pro produkci) +TLS_ENABLED=true +TLS_CERT_FILE=/etc/letsencrypt/live/chat.example.com/fullchain.pem +TLS_KEY_FILE=/etc/letsencrypt/live/chat.example.com/privkey.pem + +# Logging +LOG_LEVEL=INFO +``` + +--- + +## Krok 4: Monitoring (doporučeno) + +### Jednoduché metriky bez externích nástrojů + +Přidat do serveru periodické logování: + +```python +# V _periodic_cleanup() (každých 10 min): +async with _clients_lock: + total_connections = sum(len(v) for v in connected_clients.values()) + unique_users = len(connected_clients) +logger.info("[STATS] users=%d connections=%d", unique_users, total_connections) +``` + +### S externími nástroji (volitelně) + +- **htop** — CPU / RAM využití procesu +- **mysqladmin status** — queries/s, slow queries, connections +- **Prometheus + Grafana** — dlouhodobé trendy (přidat až při potřebě) + +--- + +## Budoucí škálování + +### Fáze A: Separace MySQL (15K+ uživatelů) + +MySQL na separátní stroj (nebo managed DB). App server + Redis na jednom, DB na druhém. + +``` +[Server: App + Redis] ──TCP──▶ [Server: MySQL] + │ + └──▶ [HDD/S3: soubory] +``` + +### Fáze B: Horizontální škálování (50K+ uživatelů) + +Více app serverů za load balancerem + Redis Pub/Sub pro cross-server notifikace. + +``` + ┌─── App server 1 ───┐ +Client ──▶ │ connected_clients │──┐ + └─────────────────────┘ │ + ├──▶ Redis Pub/Sub ──▶ MySQL + ┌─── App server 2 ───┐ │ +Client ──▶ │ connected_clients │──┘ + └─────────────────────┘ + ▲ + Load Balancer (HAProxy / nginx stream) + (sticky sessions by user_id) +``` + +Hlavní změna: `_notify_users()` posílá do Redis místo lokálního `connected_clients` pokud uživatel není na tomto serveru. + +### Fáze C: DB škálování (100K+ uživatelů) + +- Read replicas pro SELECT dotazy +- Partitioning tabulky `messages` podle měsíce +- Sharding podle `conversation_id` + +--- + +## Přehled — co je hotovo + +| Krok | Stav | Popis | +|------|------|-------| +| asyncio.to_thread() pro DB | **Hotovo** | 131 DB volání offloadováno do thread poolu | +| ThreadPoolExecutor(40) | **Hotovo** | Konfigurovatelný přes `THREAD_POOL_SIZE` | +| DB indexy (5 nových) | **Hotovo** | Schema + SQL migrace připraveny | +| UPLOAD_DIR na HDD | **Konfigurace** | Nastavit v `.env` | +| MySQL tuning | **Konfigurace** | Aplikovat `tuning.cnf` | +| TLS certifikát | **TODO** | Let's Encrypt nebo vlastní CA | +| Monitoring | **Volitelné** | Periodické logování stats | diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..b7d8426 --- /dev/null +++ b/schema.sql @@ -0,0 +1,189 @@ +CREATE DATABASE IF NOT EXISTS encrypted_chat + CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE encrypted_chat; + +-- Users: identity_key is Ed25519 (32B), rsa_public_key for login challenge only +CREATE TABLE IF NOT EXISTS users ( + id CHAR(36) NOT NULL PRIMARY KEY, + username VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + rsa_public_key TEXT NOT NULL, + identity_key BLOB NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB; + +-- Devices: each user can have multiple devices +CREATE TABLE IF NOT EXISTS devices ( + id CHAR(36) NOT NULL PRIMARY KEY, + user_id CHAR(36) NOT NULL, + device_name VARCHAR(255) DEFAULT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + last_seen_at DATETIME DEFAULT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_devices_user (user_id) +) ENGINE=InnoDB; + +-- Signed Pre-Keys (X25519, signed by Ed25519 identity key) — per device +CREATE TABLE IF NOT EXISTS signed_prekeys ( + id CHAR(36) NOT NULL PRIMARY KEY, + user_id CHAR(36) NOT NULL, + device_id CHAR(36) DEFAULT NULL, + public_key BLOB NOT NULL, + signature BLOB NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_spk_user_device (user_id, device_id) +) ENGINE=InnoDB; + +-- One-Time Pre-Keys (consumed on use) — per device +CREATE TABLE IF NOT EXISTS one_time_prekeys ( + id CHAR(36) NOT NULL PRIMARY KEY, + user_id CHAR(36) NOT NULL, + device_id CHAR(36) DEFAULT NULL, + public_key BLOB NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_opk_user_device (user_id, device_id) +) ENGINE=InnoDB; + +-- Conversations +CREATE TABLE IF NOT EXISTS conversations ( + id CHAR(36) NOT NULL PRIMARY KEY, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + name VARCHAR(255) DEFAULT NULL, + created_by CHAR(36) DEFAULT NULL, + avatar_file VARCHAR(255) DEFAULT NULL +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS conversation_members ( + conversation_id CHAR(36) NOT NULL, + user_id CHAR(36) NOT NULL, + joined_at DATETIME NULL, + PRIMARY KEY (conversation_id, user_id), + FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_cm_user (user_id) +) ENGINE=InnoDB; + +-- Group invitations (pending invitations to join a group) +CREATE TABLE IF NOT EXISTS group_invitations ( + id CHAR(36) NOT NULL PRIMARY KEY, + conversation_id CHAR(36) NOT NULL, + user_id CHAR(36) NOT NULL, + invited_by CHAR(36) NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uq_conv_user (conversation_id, user_id), + FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (invited_by) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_inv_user (user_id) +) ENGINE=InnoDB; + +-- Messages: per-recipient ciphertext (Double Ratchet = each recipient has different ciphertext) +CREATE TABLE IF NOT EXISTS messages ( + id CHAR(36) NOT NULL PRIMARY KEY, + conversation_id CHAR(36) NOT NULL, + sender_id CHAR(36) NOT NULL, + sender_device_id CHAR(36) DEFAULT NULL, + ratchet_header BLOB NOT NULL, + x3dh_header BLOB DEFAULT NULL, + sender_chain_id BLOB DEFAULT NULL, + sender_chain_n INT DEFAULT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at DATETIME DEFAULT NULL, + image_file_id CHAR(36) DEFAULT NULL, + pinned_at DATETIME DEFAULT NULL, + pinned_by CHAR(36) DEFAULT NULL, + FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, + FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_messages_conv_created (conversation_id, created_at), + INDEX idx_messages_deleted (conversation_id, deleted_at), + INDEX idx_messages_pinned (conversation_id, pinned_at) +) ENGINE=InnoDB; + +-- Per-recipient encrypted content — per device +-- device_id '00000000-0000-0000-0000-000000000000' = self-encrypted / legacy +CREATE TABLE IF NOT EXISTS message_recipients ( + message_id CHAR(36) NOT NULL, + user_id CHAR(36) NOT NULL, + device_id CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + encrypted_content BLOB NOT NULL, + nonce BLOB NOT NULL, + ratchet_header BLOB DEFAULT NULL, + x3dh_header BLOB DEFAULT NULL, + PRIMARY KEY (message_id, user_id, device_id), + FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +) ENGINE=InnoDB; + +-- Sender Keys for groups (distributed via pairwise ratchet) — per device +CREATE TABLE IF NOT EXISTS group_sender_keys ( + conversation_id CHAR(36) NOT NULL, + sender_id CHAR(36) NOT NULL, + device_id CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + chain_id BLOB NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (conversation_id, sender_id, device_id), + FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, + FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE +) ENGINE=InnoDB; + +-- Read receipts +CREATE TABLE IF NOT EXISTS message_reads ( + message_id CHAR(36) NOT NULL, + user_id CHAR(36) NOT NULL, + read_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (message_id, user_id), + FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_reads_user (user_id), + INDEX idx_reads_read_at (read_at) +) ENGINE=InnoDB; + +-- Delivery receipts +CREATE TABLE IF NOT EXISTS message_deliveries ( + message_id CHAR(36) NOT NULL, + user_id CHAR(36) NOT NULL, + delivered_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (message_id, user_id), + FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +) ENGINE=InnoDB; + +-- User profiles +CREATE TABLE IF NOT EXISTS user_profiles ( + user_id CHAR(36) NOT NULL PRIMARY KEY, + phone VARCHAR(50) DEFAULT NULL, + phone_visible TINYINT(1) NOT NULL DEFAULT 0, + email_visible TINYINT(1) NOT NULL DEFAULT 1, + location VARCHAR(255) DEFAULT NULL, + location_visible TINYINT(1) NOT NULL DEFAULT 0, + avatar_file VARCHAR(255) DEFAULT NULL, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +) ENGINE=InnoDB; + +-- Message reactions (emoji reactions on messages) +CREATE TABLE IF NOT EXISTS message_reactions ( + id CHAR(36) NOT NULL PRIMARY KEY, + message_id CHAR(36) NOT NULL, + user_id CHAR(36) NOT NULL, + reaction VARCHAR(32) NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uq_reaction (message_id, user_id), + FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_reactions_created_at (created_at) +) ENGINE=InnoDB; + +-- Image uploads +CREATE TABLE IF NOT EXISTS image_uploads ( + file_id CHAR(36) NOT NULL PRIMARY KEY, + conversation_id CHAR(36) NOT NULL, + uploader_id CHAR(36) NOT NULL, + file_size BIGINT NOT NULL DEFAULT 0, + completed BOOLEAN NOT NULL DEFAULT FALSE, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, + FOREIGN KEY (uploader_id) REFERENCES users(id) ON DELETE CASCADE +) ENGINE=InnoDB; diff --git a/server.py b/server.py new file mode 100644 index 0000000..75a7c3f --- /dev/null +++ b/server.py @@ -0,0 +1,2933 @@ +"""Asyncio TCP server — stores and relays encrypted blobs without seeing content.""" + +import asyncio +from concurrent.futures import ThreadPoolExecutor +import hashlib +import hmac +import ipaddress +import json +import logging +import os +import re +import secrets +import signal +import smtplib +import ssl +import subprocess +import sys +from email.mime.text import MIMEText +from pathlib import Path +from datetime import datetime, timezone + +from dotenv import load_dotenv + +load_dotenv() + +import db +from crypto_utils import load_public_key, rsa_verify, load_ed25519_public, ed25519_verify, serialize_x25519_public +from protocol import VERSION, MIN_CLIENT_VERSION, version_gte, ProtocolReader, ProtocolWriter, encode_binary, decode_binary, MAX_MESSAGE_BYTES, MAX_IMAGE_BYTES, MAX_FILE_BYTES, IMAGE_CHUNK_SIZE + + +class _AsyncDB: + """Async proxy — offloads every synchronous db.* call to a thread via asyncio.to_thread(). + + This prevents blocking the asyncio event loop during MySQL I/O. + Wrapper functions are cached after first access for efficiency. + """ + + def __getattr__(self, name: str): + func = getattr(db, name) + + async def wrapper(*args, **kwargs): + return await asyncio.to_thread(func, *args, **kwargs) + + wrapper.__name__ = name + wrapper.__qualname__ = f"_AsyncDB.{name}" + setattr(self, name, wrapper) + return wrapper + + +adb = _AsyncDB() + + +# Connected clients: user_id -> list[ProtocolWriter] +connected_clients: dict[str, list[ProtocolWriter]] = {} +# Writer -> device_id mapping (id(writer) -> device_id) +writer_device_map: dict[int, str] = {} +# Pairing sessions: code -> data +pairing_sessions: dict[str, dict] = {} +pending_registrations: dict[str, dict] = {} +# Used PoW challenges (prevents replay within validity window) +_used_pow_challenges: dict[str, float] = {} # challenge -> used_at +# Pending image uploads: file_id -> {temp_path, received_bytes, file_size, conv_id} +pending_uploads: dict[str, dict] = {} +# Phantom user IDs (loaded at startup, updated on create/delete) +phantom_user_ids: set[str] = set() + +# Locks for shared mutable state (H4 race condition fix) +_clients_lock = asyncio.Lock() # Protects: connected_clients, writer_device_map, phantom_user_ids +_conn_lock = asyncio.Lock() # Protects: connection_counts, current_connections, rate_limits +_pairing_lock = asyncio.Lock() # Protects: pairing_sessions, pending_registrations, _used_pow_challenges +_uploads_lock = asyncio.Lock() # Protects: pending_uploads +_phantom_lock = asyncio.Lock() # Serializes phantom user creation (cap check + DB create + set add) + +UPLOAD_DIR = Path(os.getenv("UPLOAD_DIR", "uploads")) + + +def _secure_delete(p: Path): + """Overwrite file with random data before deletion (anti-forensic wipe).""" + try: + if not p.exists(): + return + size = p.stat().st_size + if size > 0: + with open(p, "r+b") as f: + f.write(os.urandom(size)) + f.flush() + os.fsync(f.fileno()) + p.unlink() + except Exception: + try: + p.unlink(missing_ok=True) + except Exception: + pass + + +# C6 fix: UUID validation + safe path construction to prevent path traversal +_UUID_RE = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', re.IGNORECASE) + + +def _valid_uuid(value: str) -> bool: + """Validate that value is a canonical UUID (no path components).""" + return bool(_UUID_RE.match(value)) + + +# L8 fix: email validation to prevent phantom DB inflation +_EMAIL_RE = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") + + +def _valid_email(email: str) -> bool: + """Validate basic email format (L8).""" + return bool(_EMAIL_RE.match(email)) and len(email) <= 254 + + +# C2 fix: ratchet/x3dh header validation +_RATCHET_HEADER_KEYS = {"dh_pub", "n", "pn"} +_MAX_HEADER_BYTES = 4096 + + +def _validate_header(raw, name: str) -> bytes | None: + """Validate and serialize a ratchet/x3dh header. + + Accepts only dict with expected keys, rejects str/bytes to prevent + poisoned headers from being stored. Validates that ratchet headers + contain the required keys (dh_pub, n, pn) with correct types. + Returns UTF-8 encoded JSON bytes or None if invalid. + """ + if not isinstance(raw, dict): + return None + serialized = json.dumps(raw) + if len(serialized) > _MAX_HEADER_BYTES: + return None + # Validate ratchet header keys/types if this looks like one + if name in ("ratchet_header", "recipient_ratchet_header"): + # Accept self-encrypted marker {"self": true} + if raw.get("self") is True and len(raw) == 1: + return serialized.encode() + if not _RATCHET_HEADER_KEYS.issubset(raw.keys()): + return None + if not isinstance(raw.get("dh_pub"), str): + return None + if type(raw.get("n")) is not int or type(raw.get("pn")) is not int: + return None + return serialized.encode() + + +def _append_file(path: Path, data: bytes): + """Append data to file (runs in thread pool to avoid blocking event loop).""" + with open(path, "ab") as f: + f.write(data) + + +def _read_file_chunk(path: Path, offset: int, size: int) -> bytes: + """Read a chunk from file (runs in thread pool to avoid blocking event loop).""" + with open(path, "rb") as f: + f.seek(offset) + return f.read(size) + + +def _safe_upload_path(file_id: str, suffix: str) -> Path | None: + """Return resolved path inside UPLOAD_DIR, or None if traversal detected.""" + p = (UPLOAD_DIR / f"{file_id}{suffix}").resolve() + if not p.is_relative_to(UPLOAD_DIR.resolve()): + return None + return p + + +def _safe_avatar_path(filename: str) -> Path | None: + """Return resolved avatar path inside UPLOAD_DIR/avatars, or None if traversal detected.""" + avatar_dir = (UPLOAD_DIR / "avatars").resolve() + p = (UPLOAD_DIR / "avatars" / filename).resolve() + if not p.is_relative_to(avatar_dir): + return None + return p + + +PAIRING_TTL_SECONDS = 120 +REGISTER_TTL_SECONDS = 600 # 10 min (was 3600) — faster slot release under load +PAIRING_MAX_POLL_ATTEMPTS = 90 +PAIRING_MAX_SESSIONS = 100 # global cap on concurrent pairing sessions +MAX_PENDING_REGISTRATIONS = 1000 # global cap on pending registration codes +MAX_PENDING_PER_IP = 5 # per-IP cap on pending registrations +MAX_PENDING_PER_SUBNET = 20 # per-/24 (IPv4) or /64 (IPv6) cap +REGISTRATION_PRESSURE_THRESHOLD = 0.8 # 80% → tighten limits + require PoW +POW_DIFFICULTY = 20 # leading zero bits in SHA-256 (~1M hashes, ~0.5-2s) +SMTP_RATE_GLOBAL = 30 # registration emails per minute (global) +SMTP_RATE_PER_IP = 3 # registration emails per minute (per IP) +SMTP_RATE_PER_TARGET = 2 # registration emails per minute (per target email) +MAX_PHANTOM_USERS = 500 # global cap on phantom user count +MAX_UPLOADS_GLOBAL = 200 # global cap on concurrent in-flight uploads +MAX_UPLOADS_PER_USER = 5 # per-user cap on concurrent in-flight uploads +UPLOAD_STALE_SECONDS = 600 # stale upload threshold (10 min) + +# SMTP configuration for registration codes +SMTP_HOST = os.getenv("SMTP_HOST", "") +SMTP_PORT = int(os.getenv("SMTP_PORT", "587")) +SMTP_USER = os.getenv("SMTP_USER", "") +SMTP_PASS = os.getenv("SMTP_PASS", "") +SMTP_FROM = os.getenv("SMTP_FROM", "") +RATE_LIMIT_WINDOW = 60.0 # seconds +CONNECTION_RL_WINDOW = 1.0 # seconds +CONNECTION_RL_MAX = 20 # max requests per window per connection +MAX_CONNECTIONS_PER_IP = 10 +MAX_CONNECTIONS_GLOBAL = 200 +METADATA_RETENTION_DAYS = int(os.getenv("METADATA_RETENTION_DAYS", "90")) + + +def setup_logging(): + level_name = os.getenv("LOG_LEVEL", "INFO").upper() + level = getattr(logging, level_name, logging.WARNING) + logging.basicConfig(level=level, format="%(asctime)s %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + + +logger = logging.getLogger("encrypted_chat.server") + + +def _who(session: dict | None) -> str: + """Format session info for logging: truncated user_id + device prefix. + + Avoids leaking usernames and emails into log files. + """ + if not session: + return "" + uid = session.get("user_id", "?")[:8] + dev = session.get("device_id", "")[:8] if session.get("device_id") else "" + return f"u={uid} d={dev}" if dev else f"u={uid}" + + +rate_limits: dict[str, list[float]] = {} +connection_counts: dict[str, int] = {} +current_connections = 0 + + +def _rate_limit_key(action: str, addr: str, email: str | None = None) -> str: + if email: + return f"{action}|{addr}|{email.lower()}" + return f"{action}|{addr}" + + +async def _is_rate_limited(key: str, limit: int) -> bool: + async with _conn_lock: + now = asyncio.get_event_loop().time() + window_start = now - RATE_LIMIT_WINDOW + times = rate_limits.get(key, []) + times = [t for t in times if t >= window_start] + if len(times) >= limit: + rate_limits[key] = times + return True + times.append(now) + rate_limits[key] = times + return False + + +async def _create_phantom_guarded(email: str, addr: str, user_id: str) -> tuple[dict | None, str]: + """Check limits + create phantom user atomically (serialized via _phantom_lock). + + Returns (user_dict, error_message). user_dict is None on rejection. + """ + # Rate limit checks outside _phantom_lock (they acquire _conn_lock) + if await _is_rate_limited(f"phantom_create|{user_id}", 10): + return None, "Too many new contacts. Try later." + if await _is_rate_limited(f"phantom_create_ip|{addr}", 10): + return None, "Too many new contacts. Try later." + async with _phantom_lock: + async with _clients_lock: + phantom_count = len(phantom_user_ids) + if phantom_count >= MAX_PHANTOM_USERS: + return None, "Server limit reached. Try later." + u = await adb.create_phantom_user(email) + async with _clients_lock: + phantom_user_ids.add(u["id"]) + return u, "" + + +def _get_peer_addr(writer: ProtocolWriter) -> str: + try: + return str(writer._writer.get_extra_info("peername")[0]) + except Exception: + return "unknown" + + +async def _notify_users(user_ids, msg_type, data, exclude_writer=None): + """Snapshot writers under lock, send notifications outside lock.""" + targets = [] + async with _clients_lock: + for uid in user_ids: + for w in connected_clients.get(uid, []): + targets.append(w) + for w in targets: + if w is exclude_writer: + continue + try: + await w.send_response(msg_type, "ok", data) + except Exception: + pass + + +async def _notify_users_individual(notifications, exclude_writer=None): + """Send per-user data. notifications: list of (user_id, msg_type, data).""" + targets = [] + async with _clients_lock: + for uid, mt, d in notifications: + for w in connected_clients.get(uid, []): + targets.append((w, mt, d)) + for w, mt, d in targets: + if w is exclude_writer: + continue + try: + await w.send_response(mt, "ok", d) + except Exception: + pass + + +async def _cleanup_pairings(): + async with _pairing_lock: + now = asyncio.get_event_loop().time() + expired = [code for code, p in pairing_sessions.items() if now - p["created_at"] > PAIRING_TTL_SECONDS] + for code in expired: + pairing_sessions.pop(code, None) + + +async def _cleanup_registrations(): + async with _pairing_lock: + now = asyncio.get_event_loop().time() + expired = [code for code, p in pending_registrations.items() if now - p["created_at"] > REGISTER_TTL_SECONDS] + for code in expired: + pending_registrations.pop(code, None) + # Purge used PoW challenges older than 120s (validity window) + stale = [ch for ch, ts in _used_pow_challenges.items() if now - ts > 120] + for ch in stale: + _used_pow_challenges.pop(ch, None) + + +def _generate_pairing_code() -> str: + for _ in range(10): + code = f"{int.from_bytes(os.urandom(4), 'big') % 100000000:08d}" + if code not in pairing_sessions: + return code + return f"{int.from_bytes(os.urandom(4), 'big') % 100000000:08d}" + + +def _generate_register_code() -> str: + for _ in range(10): + code = f"{int.from_bytes(os.urandom(3), 'big') % 1000000:06d}" + if code not in pending_registrations: + return code + return f"{int.from_bytes(os.urandom(3), 'big') % 1000000:06d}" + +def _validate_public_key_pem(pem_str: str) -> bool: + """Validate that a string is a valid RSA public key PEM.""" + try: + key = load_public_key(pem_str.encode("utf-8")) + if key.key_size < 2048: + return False + return True + except Exception: + return False + + +def _send_registration_email(to_email: str, code: str) -> bool: + """Send registration code via SMTP. Returns True on success.""" + if not SMTP_HOST: + return False + try: + msg = MIMEText(f"Your registration code is: {code}\n\nThis code expires in 10 minutes.") + msg["Subject"] = "Encrypted Chat - Registration Code" + msg["From"] = SMTP_FROM or SMTP_USER + msg["To"] = to_email + with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=10) as server: + server.starttls() + if SMTP_USER: + server.login(SMTP_USER, SMTP_PASS) + server.send_message(msg) + return True + except Exception as e: + logger.warning("Failed to send registration email: %s", e) + return False + + +async def send_resp(msg: dict, writer: ProtocolWriter, msg_type: str, status: str, data: dict | None = None): + await writer.send_response(msg_type, status, data, request_id=msg.get("request_id")) + + +# --- Registration admission control --- + +_POW_SECRET = os.urandom(32) # per-process; restarts invalidate outstanding challenges + + +def _get_subnet(addr: str) -> str: + """Extract /24 for IPv4, /64 for IPv6.""" + try: + ip = ipaddress.ip_address(addr) + if ip.version == 4: + return str(ipaddress.ip_network(f"{ip}/24", strict=False)) + return str(ipaddress.ip_network(f"{ip}/64", strict=False)) + except ValueError: + return addr + + +def _pending_counts_by_origin(addr: str) -> tuple[int, int]: + """Count pending registrations by IP and subnet. Caller must hold _pairing_lock.""" + subnet = _get_subnet(addr) + ip_count = 0 + subnet_count = 0 + for p in pending_registrations.values(): + p_addr = p.get("addr", "") + if p_addr == addr: + ip_count += 1 + if _get_subnet(p_addr) == subnet: + subnet_count += 1 + return ip_count, subnet_count + + +def _generate_pow_challenge() -> tuple[str, str]: + """Generate a stateless PoW challenge (challenge, mac). + + The challenge embeds a timestamp so the server can reject stale solutions. + The HMAC proves the challenge was issued by this server instance. + """ + ts = str(int(asyncio.get_event_loop().time())) + nonce = secrets.token_hex(16) + challenge = f"{ts}:{nonce}" + mac = hmac.new(_POW_SECRET, challenge.encode(), hashlib.sha256).hexdigest() + return challenge, mac + + +def _verify_pow(challenge: str, mac: str, nonce: str, difficulty: int) -> bool: + """Verify a PoW solution: HMAC authentic, timestamp fresh, hash has leading zeros.""" + # Verify HMAC + expected = hmac.new(_POW_SECRET, challenge.encode(), hashlib.sha256).hexdigest() + if not hmac.compare_digest(expected, mac): + return False + # Check timestamp freshness (120s window) + try: + ts = int(challenge.split(":")[0]) + except (ValueError, IndexError): + return False + now = int(asyncio.get_event_loop().time()) + if abs(now - ts) > 120: + return False + # Verify PoW: SHA-256(challenge + nonce) must have `difficulty` leading zero bits + digest = hashlib.sha256(f"{challenge}{nonce}".encode()).digest() + # Check leading zero bits + bits_needed = difficulty + for byte in digest: + if bits_needed <= 0: + break + if bits_needed >= 8: + if byte != 0: + return False + bits_needed -= 8 + else: + mask = (0xFF << (8 - bits_needed)) & 0xFF + if byte & mask: + return False + bits_needed = 0 + return True + + +async def handle_register_start(msg: dict, writer: ProtocolWriter) -> dict | None: + await _cleanup_registrations() + username = msg.get("username", "").strip() + public_key = msg.get("public_key", "").strip() + identity_key_b64 = msg.get("identity_key", "").strip() + email = msg.get("email", "").strip() + addr = _get_peer_addr(writer) + if await _is_rate_limited(_rate_limit_key("register_start", addr, email), 3): + await send_resp(msg, writer, "register_start", "error", {"message": "Too many attempts. Try later."}) + return None + # Per-IP limit (regardless of email) to prevent SMTP spam via email rotation + if await _is_rate_limited(f"register_start_ip|{addr}", 6): + await send_resp(msg, writer, "register_start", "error", {"message": "Too many attempts. Try later."}) + return None + if not username or not public_key or not email or not identity_key_b64: + await send_resp(msg, writer, "register_start", "error", {"message": "Missing fields"}) + return None + if not _validate_public_key_pem(public_key): + await send_resp(msg, writer, "register_start", "error", {"message": "Invalid public key format"}) + return None + # Validate identity key is 32 bytes + try: + ik_bytes = decode_binary(identity_key_b64) + if len(ik_bytes) != 32: + raise ValueError("Identity key must be 32 bytes") + load_ed25519_public(ik_bytes) + except Exception: + await send_resp(msg, writer, "register_start", "error", {"message": "Invalid identity key"}) + return None + existing_email = await adb.get_user_by_email(email) + phantom_id = None + is_existing_real_user = False + if existing_email: + if existing_email.get("rsa_public_key") == "PHANTOM": + phantom_id = existing_email["id"] + else: + is_existing_real_user = True + # --- Admission control (all checks under lock, I/O outside) --- + # Existing-email goes through the same path so responses are + # indistinguishable from new-email (H3 anti-enumeration). + # Both allocate a slot so per-IP/subnet cap counting is identical. + async with _pairing_lock: + total = len(pending_registrations) + # Hard cap + if total >= MAX_PENDING_REGISTRATIONS: + reject_reason = "cap" + else: + # Per-IP / per-subnet slot limits + ip_count, subnet_count = _pending_counts_by_origin(addr) + if ip_count >= MAX_PENDING_PER_IP: + reject_reason = "ip" + elif subnet_count >= MAX_PENDING_PER_SUBNET: + reject_reason = "subnet" + else: + reject_reason = None + # Pressure mode: require PoW when >80% full + under_pressure = total >= MAX_PENDING_REGISTRATIONS * REGISTRATION_PRESSURE_THRESHOLD + need_pow = under_pressure and reject_reason is None + # If PoW required, verify the client's solution (one-time use) + pow_ok = False + if need_pow: + pow_challenge = msg.get("pow_challenge", "") + pow_mac = msg.get("pow_mac", "") + pow_nonce = msg.get("pow_nonce", "") + if pow_challenge and pow_mac and pow_nonce: + if pow_challenge in _used_pow_challenges: + pow_ok = False # replay + elif _verify_pow(pow_challenge, pow_mac, pow_nonce, POW_DIFFICULTY): + _used_pow_challenges[pow_challenge] = asyncio.get_event_loop().time() + pow_ok = True + # Decide: admit, challenge, or reject + if reject_reason: + admit = False + send_challenge = False + code = None + elif need_pow and not pow_ok: + admit = False + send_challenge = True + code = None + else: + # Both existing and new emails allocate a slot so per-IP/subnet + # counting behaves identically (anti-enumeration via slot side-channel). + # Existing-email slots are inert — register_confirm silently fails. + admit = True + send_challenge = False + code = _generate_register_code() + pending_registrations[code] = { + "username": username, + "public_key": public_key, + "identity_key": ik_bytes, + "email": email, + "created_at": asyncio.get_event_loop().time(), + "phantom_id": phantom_id, + "addr": addr, + "fake": is_existing_real_user, + } + # --- I/O outside lock --- + if not admit: + if send_challenge: + challenge, mac = _generate_pow_challenge() + await send_resp(msg, writer, "register_start", "pow_required", { + "challenge": challenge, "mac": mac, "difficulty": POW_DIFFICULTY, + }) + else: + await send_resp(msg, writer, "register_start", "error", {"message": "Server busy. Try later."}) + return None + logger.info("[REGISTER] registration started") + is_dev = os.getenv("ENVIRONMENT", "").lower() in ("dev", "development") + # SMTP rate limiting + smtp_blocked = False + if SMTP_HOST: + if await _is_rate_limited("smtp_send|global", SMTP_RATE_GLOBAL): + smtp_blocked = True + elif await _is_rate_limited(f"smtp_send_ip|{addr}", SMTP_RATE_PER_IP): + smtp_blocked = True + elif await _is_rate_limited(f"smtp_send_target|{email.lower()}", SMTP_RATE_PER_TARGET): + smtp_blocked = True + if smtp_blocked: + if is_dev: + logger.warning("[REGISTER] SMTP rate limit hit — returning code (dev mode)") + await send_resp(msg, writer, "register_start", "ok", {"code": code}) + else: + logger.warning("[REGISTER] SMTP rate limit hit — revoking slot silently") + async with _pairing_lock: + pending_registrations.pop(code, None) + await send_resp(msg, writer, "register_start", "ok", + {"message": "Code sent to your email."}) + return None + # Send registration email in a thread (non-blocking) for both real + # and fake registrations. For existing emails we still call SMTP so + # the response timing is indistinguishable (anti-enumeration). + # The email goes to the real address either way — existing users just + # won't be able to confirm (code is for a fake slot). + email_sent = await asyncio.to_thread(_send_registration_email, email, code) + if email_sent: + await send_resp(msg, writer, "register_start", "ok", {"message": "Code sent to your email."}) + elif is_dev: + logger.warning("[REGISTER] No SMTP / send failed — returning code (dev mode)") + await send_resp(msg, writer, "register_start", "ok", {"code": code}) + else: + logger.warning("[REGISTER] SMTP send failed — revoking slot silently") + async with _pairing_lock: + pending_registrations.pop(code, None) + await send_resp(msg, writer, "register_start", "ok", + {"message": "Code sent to your email."}) + return None + + +async def handle_register_confirm(msg: dict, writer: ProtocolWriter) -> dict | None: + await _cleanup_registrations() + email = msg.get("email", "").strip() + code = msg.get("code", "").strip() + addr = _get_peer_addr(writer) + if await _is_rate_limited(_rate_limit_key("register_confirm", addr, email), 3): + await send_resp(msg, writer, "register_confirm", "error", {"message": "Too many attempts. Try later."}) + return None + if not email or not code: + await send_resp(msg, writer, "register_confirm", "error", {"message": "Missing email or code"}) + return None + async with _pairing_lock: + pending = pending_registrations.get(code) + if pending and pending.get("email") == email: + pending_registrations.pop(code, None) + else: + pending = None + if not pending: + await send_resp(msg, writer, "register_confirm", "error", {"message": "Invalid or expired code"}) + return None + # H3 anti-enumeration: fake slot (existing email) — reject with same + # generic message so attacker can't distinguish from a wrong code + if pending.get("fake"): + await send_resp(msg, writer, "register_confirm", "error", {"message": "Invalid or expired code"}) + return None + phantom_id = pending.get("phantom_id") + if phantom_id: + # Upgrade phantom in-place — preserves FK references (invitations, memberships) + user_id = await adb.upgrade_phantom_user( + phantom_id, + pending["username"], + pending["public_key"], + pending["identity_key"], + ) + if user_id: + async with _clients_lock: + phantom_user_ids.discard(phantom_id) + else: + # Phantom was deleted concurrently — fall back to normal create + user_id = await adb.create_user( + pending["username"], + pending["email"], + pending["public_key"], + pending["identity_key"], + ) + else: + user_id = await adb.create_user( + pending["username"], + pending["email"], + pending["public_key"], + pending["identity_key"], + ) + await adb.create_default_profile(user_id) + logger.info("[REGISTER] confirmed (user_id=%s)", user_id[:8]) + await send_resp(msg, writer, "register_confirm", "ok", {"user_id": user_id}) + return None + + +async def handle_login_start(msg: dict, writer: ProtocolWriter, state: dict): + email = msg.get("email", "").strip() + addr = _get_peer_addr(writer) + if await _is_rate_limited(_rate_limit_key("login_start", addr, email), 10): + await send_resp(msg, writer, "login_start", "error", {"message": "Too many attempts. Try later."}) + return + if await _is_rate_limited(f"login_start_ip|{addr}", 20): + await send_resp(msg, writer, "login_start", "error", {"message": "Too many attempts. Try later."}) + return + if not email: + await send_resp(msg, writer, "login_start", "error", {"message": "Missing email"}) + return + user = await adb.get_user_by_email(email) + challenge = os.urandom(32) + state["login_email"] = email + state["login_challenge"] = challenge + if not user: + # H3 anti-enumeration: return a fake challenge so attacker can't distinguish + # "user not found" from "user exists". login_finish will fail with generic error. + state["_login_fake"] = True + await send_resp(msg, writer, "login_start", "ok", {"challenge": encode_binary(challenge)}) + + +async def handle_login_finish(msg: dict, writer: ProtocolWriter, state: dict) -> dict | None: + email = msg.get("email", "").strip() + signature_b64 = msg.get("signature", "") + challenge = state.get("login_challenge") + expected_email = state.get("login_email") + addr = _get_peer_addr(writer) + if await _is_rate_limited(_rate_limit_key("login_finish", addr, email), 10): + await send_resp(msg, writer, "login_finish", "error", {"message": "Too many attempts. Try later."}) + return None + if not email or not signature_b64: + await send_resp(msg, writer, "login_finish", "error", {"message": "Missing email or signature"}) + return None + if not challenge or expected_email != email: + await send_resp(msg, writer, "login_finish", "error", {"message": "Invalid credentials"}) + return None + + # H3: if login_start was for a non-existent user, fail with generic error + is_fake = state.pop("_login_fake", False) + + try: + if is_fake: + await send_resp(msg, writer, "login_finish", "error", {"message": "Invalid credentials"}) + return None + + user = await adb.get_user_by_email(email) + if not user: + await send_resp(msg, writer, "login_finish", "error", {"message": "Invalid credentials"}) + return None + + public_key = load_public_key(user["rsa_public_key"].encode("utf-8")) + signature = decode_binary(signature_b64) + if not rsa_verify(public_key, signature, challenge): + await send_resp(msg, writer, "login_finish", "error", {"message": "Invalid credentials"}) + return None + except ValueError: + # H5: invalid base64 in signature + await send_resp(msg, writer, "login_finish", "error", {"message": "Invalid credentials"}) + return None + finally: + state.pop("login_challenge", None) + state.pop("login_email", None) + + user_id = user["id"] + + # Version check: reject outdated clients + client_version = msg.get("client_version", "") + if client_version and not version_gte(client_version, MIN_CLIENT_VERSION): + await send_resp(msg, writer, "login_finish", "error", { + "message": f"Client version {client_version} is too old. Minimum required: {MIN_CLIENT_VERSION}", + "min_version": MIN_CLIENT_VERSION, + "server_version": VERSION, + }) + return None + + # Device registration: client may send device_id to reuse an existing device + client_device_id = msg.get("device_id") + device_id = None + if client_device_id: + dev = await adb.get_device(client_device_id) + if dev and dev["user_id"] == user_id: + device_id = client_device_id + if not device_id: + device_name = msg.get("device_name", "Unknown") + device_id = await adb.create_device(user_id, device_name) + await adb.update_device_last_seen(device_id) + + async with _clients_lock: + if user_id not in connected_clients: + connected_clients[user_id] = [] + connected_clients[user_id].append(writer) + writer_device_map[id(writer)] = device_id + logger.info("[LOGIN] u=%s d=%s client_v=%s", + user_id[:8], device_id[:8] if device_id else "?", client_version or "unknown") + await send_resp(msg, writer, "login_finish", "ok", { + "user_id": user_id, "username": user["username"], "email": user["email"], + "device_id": device_id, "server_version": VERSION, + }) + + # Send online status notifications + contacts = await adb.get_user_contacts(user_id) + online_targets = [] + async with _clients_lock: + online_contacts = [cid for cid in contacts if cid in connected_clients and connected_clients[cid]] + # Always notify contacts (handles reconnect where old writer is still lingering) + for contact_id in contacts: + for cw in connected_clients.get(contact_id, []): + online_targets.append(cw) + await writer.send_response("online_users", "ok", {"user_ids": online_contacts}) + # Send online notifications outside lock + for cw in online_targets: + try: + await cw.send_response("user_online", "ok", {"user_id": user_id}) + except Exception: + pass + + return {"user_id": user_id, "username": user["username"], "email": user["email"], + "device_id": device_id} + + +async def handle_get_user_info(msg: dict, session: dict, writer: ProtocolWriter): + """Get user info including identity key (for X3DH). Requires login.""" + email = msg.get("email", "").strip() + user_id = msg.get("user_id", "").strip() + addr = _get_peer_addr(writer) + if await _is_rate_limited(_rate_limit_key("get_user_info", addr, email or user_id), 30): + await send_resp(msg, writer, "get_user_info", "error", {"message": "Too many attempts. Try later."}) + return + if user_id and not _valid_uuid(user_id): + await send_resp(msg, writer, "get_user_info", "error", {"message": "User not found"}) + return + user = None + if email: + user = await adb.get_user_by_email(email) + elif user_id: + user = await adb.get_user_by_id(user_id) + if not user: + await send_resp(msg, writer, "get_user_info", "error", {"message": "User not found"}) + return + # H4 fix: restrict lookups to self or contacts (shared conversation) + target_id = user["id"] + if target_id != session["user_id"]: + if not await adb.shares_conversation(session["user_id"], target_id): + await send_resp(msg, writer, "get_user_info", "error", {"message": "User not found"}) + return + ik = user.get("identity_key") + await send_resp(msg, writer, "get_user_info", "ok", { + "user_id": user["id"], + "username": user["username"], + "email": user["email"], + "identity_key": encode_binary(ik) if ik else "", + }) + + +async def handle_upload_prekeys(msg: dict, session: dict, writer: ProtocolWriter): + """Upload signed prekey + batch of one-time prekeys.""" + if await _is_rate_limited(f"upload_prekeys|{session['user_id']}", 5): + await send_resp(msg, writer, "upload_prekeys", "error", {"message": "Too many requests. Try later."}) + return + spk_data = msg.get("signed_prekey") + otps = msg.get("one_time_prekeys", []) + if not spk_data: + await send_resp(msg, writer, "upload_prekeys", "error", {"message": "Missing signed_prekey"}) + return + + spk_id = spk_data.get("id", "") + spk_pub_b64 = spk_data.get("public_key", "") + spk_sig_b64 = spk_data.get("signature", "") + if not spk_id or not spk_pub_b64 or not spk_sig_b64: + await send_resp(msg, writer, "upload_prekeys", "error", {"message": "Incomplete signed_prekey"}) + return + + spk_pub = decode_binary(spk_pub_b64) + spk_sig = decode_binary(spk_sig_b64) + + # Verify SPK signature with user's identity key + user = await adb.get_user_by_id(session["user_id"]) + if not user or not user.get("identity_key"): + await send_resp(msg, writer, "upload_prekeys", "error", {"message": "No identity key"}) + return + ik_pub = load_ed25519_public(user["identity_key"]) + if not ed25519_verify(ik_pub, spk_sig, spk_pub): + await send_resp(msg, writer, "upload_prekeys", "error", {"message": "Invalid SPK signature"}) + return + + device_id = session.get("device_id") + await adb.store_signed_prekey(session["user_id"], spk_id, spk_pub, spk_sig, device_id=device_id) + + # Store OTPs + otp_records = [] + for otp in otps: + otp_id = otp.get("id", "") + otp_pub_b64 = otp.get("public_key", "") + if otp_id and otp_pub_b64: + otp_records.append({"id": otp_id, "public_key": decode_binary(otp_pub_b64)}) + if otp_records: + await adb.store_one_time_prekeys(session["user_id"], otp_records, device_id=device_id) + + logger.info("[PREKEYS] %s uploaded 1 SPK + %d OTPs", _who(session), len(otp_records)) + await send_resp(msg, writer, "upload_prekeys", "ok", {"message": "OK"}) + + +async def handle_get_key_bundle(msg: dict, session: dict, writer: ProtocolWriter): + """Fetch key bundle for X3DH. Returns per-device bundles. Consumes one OTP per device.""" + target_user_id = msg.get("user_id", "").strip() + if not target_user_id: + await send_resp(msg, writer, "get_key_bundle", "error", {"message": "Missing user_id"}) + return + if not _valid_uuid(target_user_id): + await send_resp(msg, writer, "get_key_bundle", "error", {"message": "Invalid user_id"}) + return + # M4: rate limit + authorization (prevents OPK depletion) + if await _is_rate_limited(f"get_key_bundle|{session['user_id']}", 10): + await send_resp(msg, writer, "get_key_bundle", "error", {"message": "Too many requests. Try later."}) + return + # Auth check before per-target rate limit so unauthorized requests don't burn target's bucket + if target_user_id != session["user_id"]: + if not await adb.shares_conversation(session["user_id"], target_user_id): + await send_resp(msg, writer, "get_key_bundle", "error", {"message": "Key bundle not available"}) + return + if await _is_rate_limited(f"get_key_bundle_target|{target_user_id}", 20): + await send_resp(msg, writer, "get_key_bundle", "error", {"message": "Too many requests. Try later."}) + return + result = await adb.get_key_bundles_for_user(target_user_id) + if not result or not result.get("device_bundles"): + await send_resp(msg, writer, "get_key_bundle", "error", {"message": "Key bundle not available"}) + return + + device_bundles_data = [] + for b in result["device_bundles"]: + entry = { + "device_id": b.get("device_id"), + "signed_prekey_id": b["signed_prekey_id"], + "signed_prekey": encode_binary(b["signed_prekey_pub"]), + "spk_signature": encode_binary(b["spk_signature"]), + } + if b.get("opk_pub"): + entry["one_time_prekey_id"] = b["opk_id"] + entry["one_time_prekey"] = encode_binary(b["opk_pub"]) + device_bundles_data.append(entry) + + # Build response with both new multi-device format and legacy flat fields + first = device_bundles_data[0] if device_bundles_data else {} + data = { + "identity_key": encode_binary(result["identity_key"]), + "device_bundles": device_bundles_data, + # Legacy flat fields from first device bundle (backward compat) + "signed_prekey_id": first.get("signed_prekey_id", ""), + "signed_prekey": first.get("signed_prekey", ""), + "spk_signature": first.get("spk_signature", ""), + } + if first.get("one_time_prekey"): + data["one_time_prekey_id"] = first["one_time_prekey_id"] + data["one_time_prekey"] = first["one_time_prekey"] + logger.info("[X3DH] %s fetched key bundle for user=%s (%d devices)", + _who(session), target_user_id[:8], len(device_bundles_data)) + await send_resp(msg, writer, "get_key_bundle", "ok", data) + + +async def handle_get_prekey_count(msg: dict, session: dict, writer: ProtocolWriter): + """How many OPKs does user have left (for this device)? Also returns SPK age for rotation.""" + device_id = session.get("device_id") + count = await adb.count_one_time_prekeys(session["user_id"], device_id=device_id) + spk_created_at = "" + spk = await adb.get_signed_prekey(session["user_id"], device_id=device_id) + if spk and spk.get("created_at"): + spk_created_at = spk["created_at"].isoformat() if hasattr(spk["created_at"], "isoformat") else str(spk["created_at"]) + await send_resp(msg, writer, "get_prekey_count", "ok", + {"count": count, "spk_created_at": spk_created_at}) + + +async def handle_ensure_prekeys(msg: dict, session: dict, writer: ProtocolWriter): + """Combined get_prekey_count + upload_prekeys in one round-trip. + + Client sends current OPK/SPK data; server checks count and SPK age, + stores new keys if provided, and returns the current status. + """ + if await _is_rate_limited(f"ensure_prekeys|{session['user_id']}", 5): + await send_resp(msg, writer, "ensure_prekeys", "error", {"message": "Too many requests. Try later."}) + return + device_id = session.get("device_id") + user_id = session["user_id"] + + # Step 1: Get current count + SPK age + count = await adb.count_one_time_prekeys(user_id, device_id=device_id) + spk_created_at = "" + spk = await adb.get_signed_prekey(user_id, device_id=device_id) + if spk and spk.get("created_at"): + spk_created_at = spk["created_at"].isoformat() if hasattr(spk["created_at"], "isoformat") else str(spk["created_at"]) + + # Step 2: If client included new keys, store them + uploaded_spk = False + uploaded_otps = 0 + spk_data = msg.get("signed_prekey") + if spk_data: + spk_id = spk_data.get("id", "") + spk_pub_b64 = spk_data.get("public_key", "") + spk_sig_b64 = spk_data.get("signature", "") + if spk_id and spk_pub_b64 and spk_sig_b64: + spk_pub = decode_binary(spk_pub_b64) + spk_sig = decode_binary(spk_sig_b64) + user = await adb.get_user_by_id(user_id) + if user and user.get("identity_key"): + ik_pub = load_ed25519_public(user["identity_key"]) + if ed25519_verify(ik_pub, spk_sig, spk_pub): + await adb.store_signed_prekey(user_id, spk_id, spk_pub, spk_sig, device_id=device_id) + uploaded_spk = True + + otps = msg.get("one_time_prekeys", []) + if otps: + otp_records = [] + for otp in otps: + otp_id = otp.get("id", "") + otp_pub_b64 = otp.get("public_key", "") + if otp_id and otp_pub_b64: + otp_records.append({"id": otp_id, "public_key": decode_binary(otp_pub_b64)}) + if otp_records: + await adb.store_one_time_prekeys(user_id, otp_records, device_id=device_id) + uploaded_otps = len(otp_records) + + # Recount after upload + if uploaded_spk or uploaded_otps: + count = await adb.count_one_time_prekeys(user_id, device_id=device_id) + spk = await adb.get_signed_prekey(user_id, device_id=device_id) + if spk and spk.get("created_at"): + spk_created_at = spk["created_at"].isoformat() if hasattr(spk["created_at"], "isoformat") else str(spk["created_at"]) + logger.info("[PREKEYS] %s ensure_prekeys: uploaded SPK=%s, OTPs=%d, new count=%d", + _who(session), uploaded_spk, uploaded_otps, count) + + await send_resp(msg, writer, "ensure_prekeys", "ok", + {"count": count, "spk_created_at": spk_created_at, + "uploaded_spk": uploaded_spk, "uploaded_otps": uploaded_otps}) + + +async def handle_rotate_keys(msg: dict, session: dict, writer: ProtocolWriter): + if await _is_rate_limited(f"rotate_keys|{session['user_id']}", 3): + await send_resp(msg, writer, "rotate_keys", "error", {"message": "Too many requests. Try later."}) + return + public_key = msg.get("public_key", "").strip() + if not public_key: + await send_resp(msg, writer, "rotate_keys", "error", {"message": "Missing public_key"}) + return + if not _validate_public_key_pem(public_key): + await send_resp(msg, writer, "rotate_keys", "error", {"message": "Invalid public key format"}) + return + await adb.update_user_rsa_key(session["user_id"], public_key) + logger.info("[ROTATE] %s rotated RSA key", _who(session)) + await send_resp(msg, writer, "rotate_keys", "ok", {"message": "OK"}) + # Disconnect other sessions + async with _clients_lock: + writers = connected_clients.get(session["user_id"], []) + others = [w for w in writers if w is not writer] + connected_clients[session["user_id"]] = [writer] + for w in others: + try: + w.close() + except Exception: + pass + + +async def handle_change_username(msg: dict, session: dict, writer: ProtocolWriter): + if await _is_rate_limited(f"change_username|{session['user_id']}", 5): + await send_resp(msg, writer, "change_username", "error", {"message": "Too many requests. Try later."}) + return + new_username = msg.get("username", "").strip() + if not new_username or len(new_username) > 100: + await send_resp(msg, writer, "change_username", "error", {"message": "Invalid username (1-100 chars)"}) + return + user_id = session["user_id"] + await adb.update_username(user_id, new_username) + session["username"] = new_username + logger.info("[ACCOUNT] %s changed username", _who(session)) + await send_resp(msg, writer, "change_username", "ok", {"username": new_username}) + # Notify contacts + contacts = await adb.get_user_contacts(user_id) + targets = [] + async with _clients_lock: + for cid in contacts: + for cw in connected_clients.get(cid, []): + targets.append(cw) + for cw in targets: + try: + await cw.send_response("username_changed", "ok", { + "user_id": user_id, "username": new_username, + }) + except Exception: + pass + + +async def handle_pairing_start(msg: dict, writer: ProtocolWriter): + await _cleanup_pairings() + email = msg.get("email", "").strip() + temp_public_key = msg.get("temp_public_key", "").strip() + addr = _get_peer_addr(writer) + # H4 fix: rate limit per IP only (not per email) to prevent enumeration via email rotation + if await _is_rate_limited(_rate_limit_key("pairing_start", addr), 10): + await send_resp(msg, writer, "pairing_start", "error", {"message": "Too many attempts. Try later."}) + return + if not email or not temp_public_key: + await send_resp(msg, writer, "pairing_start", "error", {"message": "Missing email or temp_public_key"}) + return + poll_token = secrets.token_hex(16) + cap_hit = False + async with _pairing_lock: + # H4 fix: global cap prevents memory exhaustion from dummy sessions + if len(pairing_sessions) >= PAIRING_MAX_SESSIONS: + cap_hit = True + else: + code = _generate_pairing_code() + # H4 fix: always create session (anti-enumeration). For non-existent users + # the session behaves identically (poll returns ready:false, claim never matches + # because no real account can log in to claim it). TTL cleanup handles expiry. + pairing_sessions[code] = { + "email": email, + "temp_public_key": temp_public_key, + "created_at": asyncio.get_event_loop().time(), + "payload": None, + "poll_token": poll_token, + } + if cap_hit: + await send_resp(msg, writer, "pairing_start", "error", {"message": "Too many attempts. Try later."}) + return + await send_resp(msg, writer, "pairing_start", "ok", {"code": code, "poll_token": poll_token}) + + +async def handle_pairing_claim(msg: dict, session: dict, writer: ProtocolWriter): + await _cleanup_pairings() + code = msg.get("code", "").strip() + if not code: + await send_resp(msg, writer, "pairing_claim", "error", {"message": "Missing code"}) + return + async with _pairing_lock: + p = pairing_sessions.get(code) + p_email = p["email"] if p else None + temp_pub = p["temp_public_key"] if p else None + if p: + # Extend TTL — re-encryption may run between claim and send + p["created_at"] = asyncio.get_event_loop().time() + # H4 fix: unified error message (anti-enumeration) + if not p or p_email != session.get("email"): + await send_resp(msg, writer, "pairing_claim", "error", {"message": "Invalid or expired code"}) + return + await send_resp(msg, writer, "pairing_claim", "ok", {"temp_public_key": temp_pub}) + + +async def handle_pairing_send(msg: dict, session: dict, writer: ProtocolWriter): + await _cleanup_pairings() + code = msg.get("code", "").strip() + payload = msg.get("payload") + if not code or not payload: + await send_resp(msg, writer, "pairing_send", "error", {"message": "Missing code or payload"}) + return + error_msg = None + async with _pairing_lock: + p = pairing_sessions.get(code) + # H4 fix: unified error message (anti-enumeration) + if not p or p["email"] != session.get("email"): + error_msg = "Invalid or expired code" + else: + p["payload"] = payload + if error_msg: + await send_resp(msg, writer, "pairing_send", "error", {"message": error_msg}) + else: + await send_resp(msg, writer, "pairing_send", "ok", {"message": "OK"}) + + +async def handle_pairing_poll(msg: dict, writer: ProtocolWriter): + await _cleanup_pairings() + code = msg.get("code", "").strip() + poll_token = msg.get("poll_token", "").strip() + addr = _get_peer_addr(writer) + if await _is_rate_limited(_rate_limit_key("pairing_poll", addr), 120): + await send_resp(msg, writer, "pairing_poll", "error", {"message": "Too many attempts. Try later."}) + return + if not code: + await send_resp(msg, writer, "pairing_poll", "error", {"message": "Missing code"}) + return + if not poll_token: + await send_resp(msg, writer, "pairing_poll", "error", {"message": "Missing poll_token"}) + return + error_msg = None + ready = False + payload = None + async with _pairing_lock: + p = pairing_sessions.get(code) + if not p: + error_msg = "Invalid or expired code" + elif not secrets.compare_digest(p.get("poll_token", ""), poll_token): + error_msg = "Invalid poll_token" + else: + poll_attempts = p.get("poll_attempts", 0) + 1 + p["poll_attempts"] = poll_attempts + if poll_attempts > PAIRING_MAX_POLL_ATTEMPTS and not p.get("payload"): + pairing_sessions.pop(code, None) + error_msg = "Code invalidated due to too many attempts" + elif p.get("payload"): + ready = True + payload = p["payload"] + pairing_sessions.pop(code, None) + if error_msg: + await send_resp(msg, writer, "pairing_poll", "error", {"message": error_msg}) + elif ready: + await send_resp(msg, writer, "pairing_poll", "ok", {"ready": True, "payload": payload}) + else: + await send_resp(msg, writer, "pairing_poll", "ok", {"ready": False}) + + +async def handle_create_conversation(msg: dict, session: dict, writer: ProtocolWriter): + member_emails = msg.get("members", []) + name = msg.get("name") + addr = _get_peer_addr(writer) + if await _is_rate_limited(f"create_conversation|{session['user_id']}", 10): + await send_resp(msg, writer, "create_conversation", "error", {"message": "Too many attempts. Try later."}) + return + # Resolve all member user IDs + other_users = [] + for email in member_emails: + u = await adb.get_user_by_email(email) + if not u: + if not _valid_email(email): + await send_resp(msg, writer, "create_conversation", "error", {"message": f"Invalid email format: {email}"}) + return + # H5: atomic phantom creation (cap check + DB create + set add) + u, err_msg = await _create_phantom_guarded(email, addr, session["user_id"]) + if u is None: + await send_resp(msg, writer, "create_conversation", "error", {"message": err_msg}) + return + if u["id"] != session["user_id"]: + other_users.append(u) + is_dm = len(other_users) == 1 and not name + joined_at = datetime.now(timezone.utc) + if is_dm: + # DMs: add both members directly (no invitation) + all_ids = [session["user_id"]] + [u["id"] for u in other_users] + conv_id = await adb.create_conversation(all_ids, joined_at=joined_at, name=name, created_by=session["user_id"]) + logger.info("[CONV] %s created DM conv=%s", _who(session), conv_id[:8]) + await send_resp(msg, writer, "create_conversation", "ok", {"conversation_id": conv_id}) + # Notify the other member + members_info = await adb.get_conversation_members(conv_id) + member_list = [{"user_id": m["id"], "username": m["username"], "email": m["email"]} for m in members_info] + notif_data = { + "conversation_id": conv_id, + "name": name, + "created_by": session["user_id"], + "members": member_list, + } + await _notify_users([u["id"] for u in other_users], "conversation_created", notif_data) + else: + # Groups: only add creator, create invitations for others + conv_id = await adb.create_conversation([session["user_id"]], joined_at=joined_at, name=name, created_by=session["user_id"]) + logger.info("[CONV] %s created group conv=%s", + _who(session), conv_id[:8]) + # Create invitations for other members + creator_user = await adb.get_user_by_id(session["user_id"]) + creator_name = creator_user["username"] if creator_user else "Unknown" + invited_ids = [] + async with _clients_lock: + phantom_snapshot = set(phantom_user_ids) + for u in other_users: + await adb.create_invitation(conv_id, u["id"], session["user_id"]) + if u["id"] not in phantom_snapshot: + invited_ids.append(u["id"]) # only notify non-phantoms + inv_notif = { + "conversation_id": conv_id, + "conversation_name": name, + "invited_by": session["user_id"], + "invited_by_username": creator_name, + } + await _notify_users(invited_ids, "group_invitation", inv_notif) + await send_resp(msg, writer, "create_conversation", "ok", {"conversation_id": conv_id}) + + +async def handle_find_conversation(msg: dict, session: dict, writer: ProtocolWriter): + email = msg.get("email", "").strip() + if not email: + await send_resp(msg, writer, "find_conversation", "error", {"message": "Invalid request"}) + return + addr = _get_peer_addr(writer) + if await _is_rate_limited(_rate_limit_key("find_conversation", addr, email), 30): + await send_resp(msg, writer, "find_conversation", "error", {"message": "Too many attempts. Try later."}) + return + other = await adb.get_user_by_email(email) + if not other: + if not _valid_email(email): + await send_resp(msg, writer, "find_conversation", "error", {"message": "Invalid email format"}) + return + # H5: atomic phantom creation (cap check + DB create + set add) + other, err_msg = await _create_phantom_guarded(email, addr, session["user_id"]) + if other is None: + await send_resp(msg, writer, "find_conversation", "error", {"message": err_msg}) + return + conv_id = await adb.find_direct_conversation(session["user_id"], other["id"]) + await send_resp(msg, writer, "find_conversation", "ok", { + "conversation_id": conv_id, + "user_id": other["id"], + }) + + +async def handle_add_member(msg: dict, session: dict, writer: ProtocolWriter): + conv_id = msg.get("conversation_id", "") + email = msg.get("email", "").strip() + if not conv_id or not email: + await send_resp(msg, writer, "add_member", "error", {"message": "Invalid request"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "add_member", "error", {"message": "Invalid conversation_id"}) + return + # L8: validate email format before phantom creation + addr = _get_peer_addr(writer) + if await _is_rate_limited(_rate_limit_key("add_member", addr, email), 10): + await send_resp(msg, writer, "add_member", "error", {"message": "Too many attempts. Try later."}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "add_member", "error", {"message": "Not a member"}) + return + user = await adb.get_user_by_email(email) + if not user: + # Create phantom for unregistered email (same as create_conversation) + if not _valid_email(email): + await send_resp(msg, writer, "add_member", "error", {"message": "Invalid email format"}) + return + # H5: atomic phantom creation (cap check + DB create + set add) + user, err_msg = await _create_phantom_guarded(email, addr, session["user_id"]) + if user is None: + await send_resp(msg, writer, "add_member", "error", {"message": err_msg}) + return + if await adb.is_conversation_member(conv_id, user["id"]): + await send_resp(msg, writer, "add_member", "error", {"message": "Already a member"}) + return + if await adb.has_pending_invitation(conv_id, user["id"]): + await send_resp(msg, writer, "add_member", "error", {"message": "Invitation already pending"}) + return + # Create invitation (for both real and phantom users) + await adb.create_invitation(conv_id, user["id"], session["user_id"]) + logger.info("[INVITE] %s invited u=%s to conv=%s", _who(session), user["id"][:8], conv_id[:8]) + await send_resp(msg, writer, "add_member", "ok", {"user_id": user["id"]}) + # Push invitation notification only to non-phantom users + async with _clients_lock: + is_phantom = user["id"] in phantom_user_ids + if not is_phantom: + conv = await adb.get_conversation(conv_id) + creator_user = await adb.get_user_by_id(session["user_id"]) + creator_name = creator_user["username"] if creator_user else "Unknown" + inv_notif = { + "conversation_id": conv_id, + "conversation_name": conv.get("name") if conv else None, + "invited_by": session["user_id"], + "invited_by_username": creator_name, + } + await _notify_users([user["id"]], "group_invitation", inv_notif) + + +async def handle_accept_invitation(msg: dict, session: dict, writer: ProtocolWriter): + """Accept a group invitation — add user to conversation members.""" + conv_id = msg.get("conversation_id", "") + if not conv_id: + await send_resp(msg, writer, "accept_invitation", "error", {"message": "Missing conversation_id"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "accept_invitation", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.has_pending_invitation(conv_id, session["user_id"]): + await send_resp(msg, writer, "accept_invitation", "error", {"message": "No pending invitation"}) + return + joined_at = datetime.now(timezone.utc) + await adb.add_conversation_member(conv_id, session["user_id"], joined_at=joined_at) + await adb.delete_invitation(conv_id, session["user_id"]) + logger.info("[INVITE] %s accepted invitation to conv=%s", _who(session), conv_id[:8]) + await send_resp(msg, writer, "accept_invitation", "ok", {"conversation_id": conv_id}) + # Notify existing members about the new member + user = await adb.get_user_by_id(session["user_id"]) + notif_data = { + "conversation_id": conv_id, + "user_id": session["user_id"], + "username": user["username"] if user else "", + "email": user["email"] if user else "", + } + members = await adb.get_conversation_members(conv_id) + member_ids = [m["id"] for m in members if m["id"] != session["user_id"]] + await _notify_users(member_ids, "member_added", notif_data) + + +async def handle_decline_invitation(msg: dict, session: dict, writer: ProtocolWriter): + """Decline a group invitation.""" + conv_id = msg.get("conversation_id", "") + if not conv_id: + await send_resp(msg, writer, "decline_invitation", "error", {"message": "Missing conversation_id"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "decline_invitation", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.has_pending_invitation(conv_id, session["user_id"]): + await send_resp(msg, writer, "decline_invitation", "error", {"message": "No pending invitation"}) + return + await adb.delete_invitation(conv_id, session["user_id"]) + logger.info("[INVITE] %s declined invitation to conv=%s", _who(session), conv_id[:8]) + await send_resp(msg, writer, "decline_invitation", "ok", {"message": "OK"}) + + +async def handle_list_invitations(msg: dict, session: dict, writer: ProtocolWriter): + """List pending group invitations for the current user.""" + invitations = await adb.get_pending_invitations(session["user_id"]) + result = [] + for inv in invitations: + entry = { + "conversation_id": inv["conversation_id"], + "conversation_name": inv.get("conversation_name"), + "invited_by": inv["invited_by"], + "invited_by_username": inv.get("invited_by_username", ""), + "created_at": inv["created_at"].isoformat() if hasattr(inv["created_at"], "isoformat") else str(inv["created_at"]), + } + result.append(entry) + await send_resp(msg, writer, "list_invitations", "ok", {"invitations": result}) + + +async def handle_list_conversations(msg: dict, session: dict, writer: ProtocolWriter): + convs = await adb.list_user_conversations(session["user_id"]) + unread = await adb.get_unread_counts(session["user_id"], max_age_days=METADATA_RETENTION_DAYS) + result = [] + for c in convs: + result.append({ + "conversation_id": c["id"], + "created_at": c["created_at"].isoformat() if hasattr(c["created_at"], "isoformat") else str(c["created_at"]), + "members": c["members"], + "name": c.get("name"), + "created_by": c.get("created_by"), + "avatar_file": c.get("avatar_file"), + "unread_count": unread.get(c["id"], 0), + }) + logger.info("[LIST] %s listed %d conversations", _who(session), len(result)) + await send_resp(msg, writer, "list_conversations", "ok", {"conversations": result}) + + +async def handle_send_message(msg: dict, session: dict, writer: ProtocolWriter): + conv_id = msg.get("conversation_id", "") + if not conv_id: + await send_resp(msg, writer, "send_message", "error", {"message": "Missing conversation_id"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "send_message", "error", {"message": "Invalid conversation_id"}) + return + addr = _get_peer_addr(writer) + if await _is_rate_limited(_rate_limit_key("send_message", addr, session.get("email")), 20): + await send_resp(msg, writer, "send_message", "error", {"message": "Too many attempts. Try later."}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "send_message", "error", {"message": "Not a member"}) + return + + # New protocol: ratchet_header + recipients[] with per-user ciphertext + ratchet_header_raw = msg.get("ratchet_header") + recipients_raw = msg.get("recipients") + if not ratchet_header_raw or not recipients_raw: + await send_resp(msg, writer, "send_message", "error", {"message": "Missing ratchet_header or recipients"}) + return + + # C2 fix: validate header is a dict (reject raw str/bytes) + ratchet_header = _validate_header(ratchet_header_raw, "ratchet_header") + if ratchet_header is None: + await send_resp(msg, writer, "send_message", "error", {"message": "Invalid ratchet_header format"}) + return + + x3dh_header_raw = msg.get("x3dh_header") + x3dh_header = None + if x3dh_header_raw: + x3dh_header = _validate_header(x3dh_header_raw, "x3dh_header") + if x3dh_header is None: + await send_resp(msg, writer, "send_message", "error", {"message": "Invalid x3dh_header format"}) + return + + sender_chain_id_b64 = msg.get("sender_chain_id") + sender_chain_id = decode_binary(sender_chain_id_b64) if sender_chain_id_b64 else None + sender_chain_n = msg.get("sender_chain_n") + + # Validate recipients are actual members + conv_members = await adb.get_conversation_members(conv_id) + member_ids = {m["id"] for m in conv_members} + async with _clients_lock: + phantom_snapshot = set(phantom_user_ids) + db_recipients = [] + for r in recipients_raw: + uid = r.get("user_id", "") + if uid not in member_ids: + continue + if uid in phantom_snapshot: + continue + ct_b64 = r.get("encrypted_content", "") + nonce_b64 = r.get("nonce", "") + if not ct_b64 or not nonce_b64: + continue + entry = { + "user_id": uid, + "encrypted_content": decode_binary(ct_b64), + "nonce": decode_binary(nonce_b64), + } + # Per-recipient device_id (multi-device support) + r_device_id = r.get("device_id") + if r_device_id: + entry["device_id"] = r_device_id + # Per-recipient ratchet header and x3dh header (C2 fix: validate dict) + r_rh = r.get("ratchet_header") + if r_rh: + r_rh_bytes = _validate_header(r_rh, "recipient_ratchet_header") + if r_rh_bytes: + entry["ratchet_header"] = r_rh_bytes + r_x3dh = r.get("x3dh_header") + if r_x3dh: + r_x3dh_bytes = _validate_header(r_x3dh, "recipient_x3dh_header") + if r_x3dh_bytes: + entry["x3dh_header"] = r_x3dh_bytes + db_recipients.append(entry) + if not db_recipients: + await send_resp(msg, writer, "send_message", "error", {"message": "No valid recipients"}) + return + + image_file_id = msg.get("image_file_id") + + # Metadata privacy: for group messages (sender_chain_id present), store chain + # metadata in per-recipient ratchet_header instead of the messages table. + # This avoids persisting sender correlation data at the message level. + # Skip sender's own self-copy entry — it uses a different decrypt path + # (self-encryption key) and must keep its own ratchet_header ({"self":true}). + db_sender_chain_id = None + db_sender_chain_n = None + if sender_chain_id: + chain_meta = json.dumps({ + "chain_id": encode_binary(sender_chain_id), + "chain_n": sender_chain_n, + }).encode() + sender_uid = session["user_id"] + for r in db_recipients: + # Skip self-copy (sender's own entry) — uses self-encryption, not sender key + if r["user_id"] == sender_uid: + continue + if not r.get("ratchet_header"): + r["ratchet_header"] = chain_meta + + msg_id, created_at = await adb.store_message( + conv_id, session["user_id"], ratchet_header, db_recipients, + x3dh_header=x3dh_header, + sender_chain_id=db_sender_chain_id, + sender_chain_n=db_sender_chain_n, + image_file_id=image_file_id, + sender_device_id=session.get("device_id"), + ) + + # Link image upload to message if present + if image_file_id: + upload = await adb.get_image_upload(image_file_id) + if upload and upload["completed"] and upload["uploader_id"] == session["user_id"]: + await adb.set_message_image_file_id(msg_id, image_file_id) + + logger.info("[MSG] %s msg=%s conv=%s", _who(session), msg_id[:8], conv_id[:8]) + await send_resp(msg, writer, "send_message", "ok", {"message_id": msg_id, "created_at": created_at}) + + # Notify connected recipients — group all per-device entries by user_id + # Use validated db_recipients (not raw input) to prevent unvalidated headers in push + msg_ratchet_header_dict = json.loads(ratchet_header.decode()) + msg_x3dh_header_dict = json.loads(x3dh_header.decode()) if x3dh_header else None + + from collections import defaultdict + user_entries = defaultdict(list) + for r in db_recipients: + uid = r["user_id"] + # Per-recipient headers are stored as bytes; decode back to dict for notification JSON + r_rh = r.get("ratchet_header") + r_rh_dict = json.loads(r_rh.decode()) if r_rh else None + r_x3dh = r.get("x3dh_header") + r_x3dh_dict = json.loads(r_x3dh.decode()) if r_x3dh else None + user_entries[uid].append({ + "device_id": r.get("device_id", db.SELF_DEVICE_ID), + "encrypted_content": encode_binary(r["encrypted_content"]), + "nonce": encode_binary(r["nonce"]), + "ratchet_header": r_rh_dict or msg_ratchet_header_dict, + "x3dh_header": r_x3dh_dict or msg_x3dh_header_dict, + }) + + notifications = [] + for uid, entries in user_entries.items(): + notif_data = { + "message_id": msg_id, + "conversation_id": conv_id, + "sender_id": session["user_id"], + "sender_device_id": session.get("device_id"), + "device_entries": entries, + } + if sender_chain_id_b64: + notif_data["sender_chain_id"] = sender_chain_id_b64 + if sender_chain_n is not None: + notif_data["sender_chain_n"] = sender_chain_n + # Also include flat fields for backward compat with old clients + # (first entry's data as fallback) + if entries: + first = entries[0] + notif_data["ratchet_header"] = first.get("ratchet_header") or msg_ratchet_header_dict + notif_data["encrypted_content"] = first.get("encrypted_content", "") + notif_data["nonce"] = first.get("nonce", "") + if first.get("x3dh_header"): + notif_data["x3dh_header"] = first["x3dh_header"] + notifications.append((uid, "new_message", notif_data)) + await _notify_users_individual(notifications, exclude_writer=writer) + + +async def handle_get_messages(msg: dict, session: dict, writer: ProtocolWriter): + if await _is_rate_limited(f"get_messages|{session['user_id']}", 30): + await send_resp(msg, writer, "get_messages", "error", {"message": "Too many requests. Try later."}) + return + conv_id = msg.get("conversation_id", "") + if not conv_id: + await send_resp(msg, writer, "get_messages", "error", {"message": "Missing conversation_id"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "get_messages", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "get_messages", "error", {"message": "Not a member"}) + return + + limit = min(max(int(msg.get("limit", 50)), 1), 200) + offset = max(int(msg.get("offset", 0)), 0) + device_id = session.get("device_id") + after_ts = msg.get("after_ts") # ISO timestamp string or None + messages = await adb.get_messages(conv_id, session["user_id"], limit, offset, + device_id=device_id, after_ts=after_ts) + + # Deduplicate: when both device-specific and SELF_DEVICE_ID rows exist for the + # same message, prefer device-specific (non-sentinel). Keep first seen per message_id. + seen_ids = {} + deduped = [] + for m in messages: + mid = m["id"] + mr_dev = m.get("mr_device_id", "") + if mid not in seen_ids: + seen_ids[mid] = len(deduped) + deduped.append(m) + elif mr_dev != db.SELF_DEVICE_ID: + # Replace SELF_DEVICE_ID entry with device-specific one + deduped[seen_ids[mid]] = m + messages = deduped + + result = [] + message_ids = [m["id"] for m in messages] + read_status = await adb.get_message_read_status(message_ids) if message_ids else {} + delivery_status = await adb.get_message_delivery_status(message_ids) if message_ids else {} + reactions_map = await adb.get_reactions(message_ids) if message_ids else {} + for m in messages: + read_by = read_status.get(m["id"], []) + # Prefer per-recipient headers (mr_*) over message-level headers + rh_raw = m.get("mr_ratchet_header") or m.get("ratchet_header") + x3dh_raw = m.get("mr_x3dh_header") or m.get("x3dh_header") + # C2 fix: defensive JSON parsing — corrupted headers don't break fetch + try: + rh_parsed = json.loads(rh_raw) if rh_raw else {} + except (json.JSONDecodeError, TypeError, UnicodeDecodeError): + logger.warning("[FETCH] Corrupted ratchet_header in message %s, skipping", m["id"]) + rh_parsed = {} + try: + x3dh_parsed = json.loads(x3dh_raw) if x3dh_raw else None + except (json.JSONDecodeError, TypeError, UnicodeDecodeError): + logger.warning("[FETCH] Corrupted x3dh_header in message %s, skipping", m["id"]) + x3dh_parsed = None + entry = { + "message_id": m["id"], + "sender_id": m.get("sender_id") or "", + "ratchet_header": rh_parsed, + "encrypted_content": encode_binary(m["encrypted_content"]) if m.get("encrypted_content") else "", + "nonce": encode_binary(m["nonce"]) if m.get("nonce") else "", + "created_at": m["created_at"].isoformat() if hasattr(m["created_at"], "isoformat") else str(m["created_at"]), + "read_by": read_by, + "delivered_to": delivery_status.get(m["id"], []), + } + if x3dh_parsed: + entry["x3dh_header"] = x3dh_parsed + # Sender chain metadata: check message-level first (backward compat), + # then per-recipient ratchet_header (new metadata-private format). + # Only extract from per-recipient header if message-level ratchet_header + # is the group dummy (dh_pub all-zeros) — prevents DM header injection. + if m.get("sender_chain_id"): + entry["sender_chain_id"] = encode_binary(m["sender_chain_id"]) + elif isinstance(rh_parsed, dict) and rh_parsed.get("chain_id"): + # Verify this is a group message by checking the message-level header + msg_rh_raw = m.get("ratchet_header") + is_group = False + if msg_rh_raw: + try: + msg_rh = json.loads(msg_rh_raw) if isinstance(msg_rh_raw, (bytes, str)) else msg_rh_raw + is_group = isinstance(msg_rh, dict) and msg_rh.get("dh_pub") == "00" * 32 + except (json.JSONDecodeError, TypeError, UnicodeDecodeError): + pass + if is_group: + entry["sender_chain_id"] = rh_parsed["chain_id"] + if m.get("sender_chain_n") is not None: + entry["sender_chain_n"] = m["sender_chain_n"] + elif isinstance(rh_parsed, dict) and rh_parsed.get("chain_n") is not None: + # Same group-only guard + if "sender_chain_id" in entry: + entry["sender_chain_n"] = rh_parsed["chain_n"] + if m.get("sender_device_id"): + entry["sender_device_id"] = m["sender_device_id"] + if m.get("deleted_at"): + entry["deleted_at"] = m["deleted_at"].isoformat() if hasattr(m["deleted_at"], "isoformat") else str(m["deleted_at"]) + # Pin metadata + if m.get("pinned_at"): + entry["pinned_at"] = m["pinned_at"].isoformat() if hasattr(m["pinned_at"], "isoformat") else str(m["pinned_at"]) + entry["pinned_by"] = m.get("pinned_by") or "" + # Reactions + msg_reactions = reactions_map.get(m["id"]) + if msg_reactions: + entry["reactions"] = msg_reactions + result.append(entry) + total_count = await adb.count_messages(conv_id, session["user_id"]) + logger.info("[FETCH] %s fetched %d/%d msgs from conv=%s (limit=%d, offset=%d%s)", + _who(session), len(result), total_count, conv_id[:8], limit, offset, + f", after={after_ts}" if after_ts else "") + await send_resp(msg, writer, "get_messages", "ok", + {"messages": result, "total_count": total_count}) + + +async def handle_remove_member(msg: dict, session: dict, writer: ProtocolWriter): + if await _is_rate_limited(f"remove_member|{session['user_id']}", 10): + await send_resp(msg, writer, "remove_member", "error", {"message": "Too many requests. Try later."}) + return + conv_id = msg.get("conversation_id", "") + user_id = msg.get("user_id", "") + if not conv_id or not user_id: + await send_resp(msg, writer, "remove_member", "error", {"message": "Missing conversation_id or user_id"}) + return + if not _valid_uuid(conv_id) or not _valid_uuid(user_id): + await send_resp(msg, writer, "remove_member", "error", {"message": "Invalid conversation_id or user_id"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "remove_member", "error", {"message": "Not a member"}) + return + convs = await adb.list_user_conversations(session["user_id"]) + conv_data = None + for c in convs: + if c["id"] == conv_id: + conv_data = c + break + if not conv_data or conv_data.get("created_by") != session["user_id"]: + await send_resp(msg, writer, "remove_member", "error", {"message": "Only the group creator can remove members"}) + return + if user_id == session["user_id"]: + await send_resp(msg, writer, "remove_member", "error", {"message": "Cannot remove yourself"}) + return + # Get remaining members before removing (to notify them) + members_before = await adb.get_conversation_members(conv_id) + # M6: atomic removal — return value confirms row existed + removed = await adb.remove_conversation_member_atomic(conv_id, user_id) + if not removed: + await send_resp(msg, writer, "remove_member", "error", {"message": "Member already removed"}) + return + logger.info("[MEMBER] %s removed user=%s from conv=%s", _who(session), user_id[:8], conv_id[:8]) + await send_resp(msg, writer, "remove_member", "ok", {"message": "OK"}) + + # Notify removed member and remaining members + notif_data = { + "conversation_id": conv_id, + "user_id": user_id, + } + member_ids = [m["id"] for m in members_before if m["id"] != session["user_id"]] + await _notify_users(member_ids, "member_removed", notif_data) + + +async def handle_leave_group(msg: dict, session: dict, writer: ProtocolWriter): + """Leave a group conversation voluntarily.""" + conv_id = msg.get("conversation_id", "") + if not conv_id: + await send_resp(msg, writer, "leave_group", "error", {"message": "Missing conversation_id"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "leave_group", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "leave_group", "error", {"message": "Not a member"}) + return + # Don't allow leaving DMs (2 members without a name) + conv = await adb.get_conversation(conv_id) + members = await adb.get_conversation_members(conv_id) + if len(members) <= 2 and not (conv and conv.get("name")): + await send_resp(msg, writer, "leave_group", "error", {"message": "Cannot leave a DM conversation"}) + return + # If creator is leaving, transfer to first remaining member + if conv and conv.get("created_by") == session["user_id"]: + remaining = [m for m in members if m["id"] != session["user_id"]] + if remaining: + await adb.update_conversation_creator(conv_id, remaining[0]["id"]) + # M6: atomic removal + await adb.remove_conversation_member_atomic(conv_id, session["user_id"]) + logger.info("[LEAVE] %s left group conv=%s", _who(session), conv_id[:8]) + await send_resp(msg, writer, "leave_group", "ok", {"message": "OK"}) + # Notify remaining members + notif_data = { + "conversation_id": conv_id, + "user_id": session["user_id"], + } + member_ids = [m["id"] for m in members if m["id"] != session["user_id"]] + await _notify_users(member_ids, "member_removed", notif_data) + + +async def handle_rename_conversation(msg: dict, session: dict, writer: ProtocolWriter): + """Rename a group conversation (creator only).""" + if await _is_rate_limited(f"rename_conv|{session['user_id']}", 5): + await send_resp(msg, writer, "rename_conversation", "error", {"message": "Too many requests. Try later."}) + return + conv_id = msg.get("conversation_id", "") + new_name = msg.get("name", "").strip() + if not conv_id or not new_name: + await send_resp(msg, writer, "rename_conversation", "error", {"message": "Missing conversation_id or name"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "rename_conversation", "error", {"message": "Invalid conversation_id"}) + return + if len(new_name) > 100: + await send_resp(msg, writer, "rename_conversation", "error", {"message": "Name too long (max 100)"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "rename_conversation", "error", {"message": "Not a member"}) + return + conv = await adb.get_conversation(conv_id) + if not conv or not conv.get("name"): + await send_resp(msg, writer, "rename_conversation", "error", {"message": "Cannot rename a DM conversation"}) + return + if conv.get("created_by") != session["user_id"]: + await send_resp(msg, writer, "rename_conversation", "error", {"message": "Only the group creator can rename"}) + return + await adb.update_conversation_name(conv_id, new_name) + logger.info("[RENAME] %s renamed conv=%s", _who(session), conv_id[:8]) + await send_resp(msg, writer, "rename_conversation", "ok", {"message": "OK"}) + # Notify all members + members = await adb.get_conversation_members(conv_id) + member_ids = [m["id"] for m in members if m["id"] != session["user_id"]] + await _notify_users(member_ids, "conversation_renamed", { + "conversation_id": conv_id, + "name": new_name, + "renamed_by": session["user_id"], + }) + + +async def handle_delete_conversation(msg: dict, session: dict, writer: ProtocolWriter): + """Delete a conversation for the current user. Removes user from members, + deletes the conversation if no members remain.""" + if await _is_rate_limited(f"delete_conv|{session['user_id']}", 5): + await send_resp(msg, writer, "delete_conversation", "error", {"message": "Too many requests. Try later."}) + return + conv_id = msg.get("conversation_id", "") + if not conv_id: + await send_resp(msg, writer, "delete_conversation", "error", {"message": "Missing conversation_id"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "delete_conversation", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "delete_conversation", "error", {"message": "Not a member"}) + return + conv = await adb.get_conversation(conv_id) + members = await adb.get_conversation_members(conv_id) + is_group = len(members) > 2 or (conv and conv.get("name")) + # Groups can only be deleted by the creator (admin) + if is_group and (not conv or conv.get("created_by") != session["user_id"]): + await send_resp(msg, writer, "delete_conversation", "error", {"message": "Only the group creator can delete this conversation"}) + return + if is_group: + # Group: creator deletes for everyone — remove all members, clean up, delete + for member in members: + await adb.remove_conversation_member(conv_id, member["id"]) + else: + # DM: only remove self; other user keeps the conversation + await adb.remove_conversation_member(conv_id, session["user_id"]) + remaining_count = await adb.count_conversation_members(conv_id) + if remaining_count == 0: + # Clean up uploaded files from disk + file_ids = await adb.get_conversation_file_ids(conv_id) + for fid in file_ids: + for ext in (".enc", ".tmp"): + p = _safe_upload_path(fid, ext) + if not p: + continue + _secure_delete(p) + await adb.delete_conversation(conv_id) + logger.info("[DELETE] %s deleted conv=%s", _who(session), conv_id[:8]) + await send_resp(msg, writer, "delete_conversation", "ok", {"message": "OK"}) + # Notify other members they were removed + notif_data = { + "conversation_id": conv_id, + "user_id": session["user_id"], + } + member_ids = [m["id"] for m in members if m["id"] != session["user_id"]] + await _notify_users(member_ids, "member_removed", notif_data) + + +async def handle_mark_read(msg: dict, session: dict, writer: ProtocolWriter): + conv_id = msg.get("conversation_id", "") + message_ids = msg.get("message_ids", []) + if not conv_id or not message_ids: + await send_resp(msg, writer, "mark_read", "error", {"message": "Missing conversation_id or message_ids"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "mark_read", "error", {"message": "Invalid conversation_id"}) + return + if len(message_ids) > 500: + await send_resp(msg, writer, "mark_read", "error", {"message": "Too many message_ids (max 500)"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "mark_read", "error", {"message": "Not a member"}) + return + # M1 fix: filter to only message_ids that belong to this conversation + valid_ids = await adb.filter_message_ids_by_conversation(conv_id, message_ids) + if not valid_ids: + await send_resp(msg, writer, "mark_read", "ok", {"message": "OK"}) + return + await adb.mark_messages_read(conv_id, session["user_id"], valid_ids) + logger.info("[READ] %s marked %d msgs read in conv=%s", _who(session), len(valid_ids), conv_id[:8]) + await send_resp(msg, writer, "mark_read", "ok", {"message": "OK"}) + members = await adb.get_conversation_members(conv_id) + notif_data = { + "conversation_id": conv_id, + "user_id": session["user_id"], + "message_ids": valid_ids, + } + member_ids = [m["id"] for m in members if m["id"] != session["user_id"]] + await _notify_users(member_ids, "messages_read", notif_data) + + +async def handle_mark_conversation_read(msg: dict, session: dict, writer: ProtocolWriter): + conv_id = msg.get("conversation_id", "") + if not conv_id: + await send_resp(msg, writer, "mark_conversation_read", "error", {"message": "Missing conversation_id"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "mark_conversation_read", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "mark_conversation_read", "error", {"message": "Not a member"}) + return + count = await adb.mark_conversation_read(conv_id, session["user_id"]) + logger.info("[READ] %s marked conv=%s all-read (%d msgs)", _who(session), conv_id[:8], count) + await send_resp(msg, writer, "mark_conversation_read", "ok", {"marked_count": count}) + if count > 0: + members = await adb.get_conversation_members(conv_id) + member_ids = [m["id"] for m in members if m["id"] != session["user_id"]] + await _notify_users(member_ids, "messages_read", { + "conversation_id": conv_id, + "user_id": session["user_id"], + "message_ids": [], + }) + + +async def handle_confirm_delivery(msg: dict, session: dict, writer: ProtocolWriter): + conv_id = msg.get("conversation_id", "") + message_ids = msg.get("message_ids", []) + if not conv_id or not message_ids: + await send_resp(msg, writer, "confirm_delivery", "error", {"message": "Missing conversation_id or message_ids"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "confirm_delivery", "error", {"message": "Invalid conversation_id"}) + return + if len(message_ids) > 500: + await send_resp(msg, writer, "confirm_delivery", "error", {"message": "Too many message_ids (max 500)"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "confirm_delivery", "error", {"message": "Not a member"}) + return + # M1 fix: filter to only message_ids that belong to this conversation + valid_ids = await adb.filter_message_ids_by_conversation(conv_id, message_ids) + if not valid_ids: + await send_resp(msg, writer, "confirm_delivery", "ok", {"message": "OK"}) + return + await adb.mark_messages_delivered(conv_id, session["user_id"], valid_ids) + logger.info("[DELIVERY] %s confirmed %d msgs delivered in conv=%s", _who(session), len(valid_ids), conv_id[:8]) + await send_resp(msg, writer, "confirm_delivery", "ok", {"message": "OK"}) + + # Notify senders — batch lookup sender_id per message, push to each sender + sender_msgs: dict[str, list[str]] = {} + for mid in valid_ids: + sid = await adb.get_message_sender(mid) + if sid and sid != session["user_id"]: + sender_msgs.setdefault(sid, []).append(mid) + for sender_id, mids in sender_msgs.items(): + await _notify_users([sender_id], "message_delivered", { + "conversation_id": conv_id, + "user_id": session["user_id"], + "message_ids": mids, + }) + + +async def handle_delete_message(msg: dict, session: dict, writer: ProtocolWriter): + if await _is_rate_limited(f"delete_msg|{session['user_id']}", 20): + await send_resp(msg, writer, "delete_message", "error", {"message": "Too many requests. Try later."}) + return + message_id = msg.get("message_id", "") + if not message_id: + await send_resp(msg, writer, "delete_message", "error", {"message": "Missing message_id"}) + return + if not _valid_uuid(message_id): + await send_resp(msg, writer, "delete_message", "error", {"message": "Invalid message_id"}) + return + conv_id = await adb.get_message_conversation(message_id) + if not conv_id: + await send_resp(msg, writer, "delete_message", "error", {"message": "Message not found"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "delete_message", "error", {"message": "Not a member"}) + return + result = await adb.soft_delete_message(message_id, session["user_id"]) + if result is None: + await send_resp(msg, writer, "delete_message", "error", {"message": "Cannot delete this message"}) + return + image_file_id = result.get("image_file_id") + if image_file_id: + image_path = _safe_upload_path(image_file_id, ".enc") + if image_path: + _secure_delete(image_path) + await adb.delete_image_upload(image_file_id) + logger.info("[MSG] %s deleted message=%s", _who(session), message_id[:8]) + await send_resp(msg, writer, "delete_message", "ok", {"message_id": message_id}) + members = await adb.get_conversation_members(conv_id) + notif_data = {"message_id": message_id, "conversation_id": conv_id} + member_ids = [m["id"] for m in members if m["id"] != session["user_id"]] + await _notify_users(member_ids, "message_deleted", notif_data) + + +async def handle_react_message(msg: dict, session: dict, writer: ProtocolWriter): + if await _is_rate_limited(f"react|{session['user_id']}", 20): + await send_resp(msg, writer, "react_message", "error", {"message": "Too many requests. Try later."}) + return + message_id = msg.get("message_id", "") + reaction = msg.get("reaction", "") + action = msg.get("action", "add") # "add" or "remove" + + if not message_id or not reaction: + await send_resp(msg, writer, "react_message", "error", {"message": "Missing fields"}) + return + if not _valid_uuid(message_id): + await send_resp(msg, writer, "react_message", "error", {"message": "Invalid message_id"}) + return + if reaction not in db.ALLOWED_REACTIONS: + await send_resp(msg, writer, "react_message", "error", {"message": "Invalid reaction"}) + return + if action not in ("add", "remove"): + await send_resp(msg, writer, "react_message", "error", {"message": "Invalid action"}) + return + + conv_id = await adb.get_message_conversation(message_id) + if not conv_id: + await send_resp(msg, writer, "react_message", "error", {"message": "Message not found"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "react_message", "error", {"message": "Not a member"}) + return + + old_reaction = None + if action == "add": + changed, old_reaction = await adb.add_reaction(message_id, session["user_id"], reaction) + if not changed: + await send_resp(msg, writer, "react_message", "ok", {"message_id": message_id}) + return + else: + await adb.remove_reaction(message_id, session["user_id"]) + + logger.info("[MSG] %s %s reaction '%s' on message=%s", _who(session), action, reaction, message_id[:8]) + resp_data = {"message_id": message_id} + if old_reaction: + resp_data["old_reaction"] = old_reaction + await send_resp(msg, writer, "react_message", "ok", resp_data) + + members = await adb.get_conversation_members(conv_id) + member_ids = [m["id"] for m in members if m["id"] != session["user_id"]] + + # If replacing an old reaction, notify removal first + if old_reaction: + remove_data = { + "message_id": message_id, + "conversation_id": conv_id, + "user_id": session["user_id"], + "username": session.get("username", ""), + "reaction": old_reaction, + "action": "remove", + } + await _notify_users(member_ids, "message_reacted", remove_data) + + notif_data = { + "message_id": message_id, + "conversation_id": conv_id, + "user_id": session["user_id"], + "username": session.get("username", ""), + "reaction": reaction, + "action": action, + } + await _notify_users(member_ids, "message_reacted", notif_data) + + +async def handle_pin_message(msg: dict, session: dict, writer: ProtocolWriter): + message_id = msg.get("message_id", "") + action = msg.get("action", "pin") # "pin" or "unpin" + conversation_id = msg.get("conversation_id", "") + + if not message_id or not conversation_id: + await send_resp(msg, writer, "pin_message", "error", {"message": "Missing fields"}) + return + if not _valid_uuid(message_id) or not _valid_uuid(conversation_id): + await send_resp(msg, writer, "pin_message", "error", {"message": "Invalid ID"}) + return + if action not in ("pin", "unpin"): + await send_resp(msg, writer, "pin_message", "error", {"message": "Invalid action"}) + return + if not await adb.is_conversation_member(conversation_id, session["user_id"]): + await send_resp(msg, writer, "pin_message", "error", {"message": "Not a member"}) + return + + if action == "pin": + ok = await adb.pin_message(message_id, session["user_id"], conversation_id) + else: + ok = await adb.unpin_message(message_id, conversation_id) + + if not ok: + await send_resp(msg, writer, "pin_message", "error", + {"message": "Already pinned" if action == "pin" else "Not pinned"}) + return + + logger.info("[MSG] %s %s message=%s in conv=%s", _who(session), action, message_id[:8], conversation_id[:8]) + await send_resp(msg, writer, "pin_message", "ok", {"message_id": message_id, "action": action}) + + members = await adb.get_conversation_members(conversation_id) + notif_type = "message_pinned" if action == "pin" else "message_unpinned" + notif_data = { + "message_id": message_id, + "conversation_id": conversation_id, + "user_id": session["user_id"], + "username": session.get("username", ""), + } + member_ids = [m["id"] for m in members if m["id"] != session["user_id"]] + await _notify_users(member_ids, notif_type, notif_data) + + +async def handle_get_pinned_messages(msg: dict, session: dict, writer: ProtocolWriter): + conversation_id = msg.get("conversation_id", "") + if not conversation_id: + await send_resp(msg, writer, "get_pinned_messages", "error", {"message": "Missing conversation_id"}) + return + if not _valid_uuid(conversation_id): + await send_resp(msg, writer, "get_pinned_messages", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.is_conversation_member(conversation_id, session["user_id"]): + await send_resp(msg, writer, "get_pinned_messages", "error", {"message": "Not a member"}) + return + + pinned = await adb.get_pinned_messages(conversation_id, session["user_id"]) + await send_resp(msg, writer, "get_pinned_messages", "ok", {"messages": pinned}) + + +async def handle_upload_image_start(msg: dict, session: dict, writer: ProtocolWriter): + conv_id = msg.get("conversation_id", "") + file_size = msg.get("file_size", 0) + file_id = msg.get("file_id", "") + file_type = msg.get("file_type", "image") # "image" or "file" + if not conv_id or not file_id: + await send_resp(msg, writer, "upload_image_start", "error", {"message": "Missing fields"}) + return + if not _valid_uuid(file_id): + await send_resp(msg, writer, "upload_image_start", "error", {"message": "Invalid file_id"}) + return + # M5: rate limit + caps on in-flight uploads + addr = _get_peer_addr(writer) + if await _is_rate_limited(f"upload_start|{session['user_id']}", 10): + await send_resp(msg, writer, "upload_image_start", "error", {"message": "Too many uploads. Try later."}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "upload_image_start", "error", {"message": "Not a member"}) + return + max_bytes = MAX_FILE_BYTES if file_type == "file" else MAX_IMAGE_BYTES + if max_bytes > 0 and file_size > max_bytes: + await send_resp(msg, writer, "upload_image_start", "error", + {"message": f"File too large (max {max_bytes} bytes)"}) + return + UPLOAD_DIR.mkdir(parents=True, exist_ok=True) + temp_path = _safe_upload_path(file_id, ".tmp") + if not temp_path: + await send_resp(msg, writer, "upload_image_start", "error", {"message": "Invalid file_id"}) + return + # M5: atomic cap check + insert under single lock acquisition + cap_error = "" + async with _uploads_lock: + total = len(pending_uploads) + user_count = sum(1 for u in pending_uploads.values() if u.get("uploader_id") == session["user_id"]) + if total >= MAX_UPLOADS_GLOBAL: + cap_error = "Server upload limit reached. Try later." + elif user_count >= MAX_UPLOADS_PER_USER: + cap_error = "Too many active uploads. Finish or cancel existing ones." + else: + temp_path.write_bytes(b"") + pending_uploads[file_id] = { + "temp_path": str(temp_path), + "received_bytes": 0, + "file_size": file_size, + "max_bytes": max_bytes, + "conv_id": conv_id, + "uploader_id": session["user_id"], + } + if cap_error: + await send_resp(msg, writer, "upload_image_start", "error", {"message": cap_error}) + return + try: + await adb.create_image_upload(file_id, conv_id, session["user_id"], file_size) + except Exception: + # Rollback: remove from pending_uploads + delete temp file + async with _uploads_lock: + pending_uploads.pop(file_id, None) + _secure_delete(temp_path) + logger.exception("[UPLOAD] DB create failed for file=%s", file_id[:8]) + await send_resp(msg, writer, "upload_image_start", "error", {"message": "Upload failed"}) + return + logger.info("[UPLOAD] %s started upload file=%s (%s, %d bytes)", + _who(session), file_id[:8], file_type, file_size) + await send_resp(msg, writer, "upload_image_start", "ok", {"file_id": file_id}) + + +async def handle_upload_image_chunk(msg: dict, session: dict, writer: ProtocolWriter): + file_id = msg.get("file_id", "") + chunk_data = msg.get("data", "") + if not file_id or not chunk_data: + await send_resp(msg, writer, "upload_image_chunk", "error", {"message": "Missing fields"}) + return + async with _uploads_lock: + upload = pending_uploads.get(file_id) + if not upload or upload["uploader_id"] != session["user_id"]: + upload = None + else: + temp_path_str = upload["temp_path"] + upload_max = upload.get("max_bytes", 0) + if not upload: + await send_resp(msg, writer, "upload_image_chunk", "error", {"message": "No active upload"}) + return + raw = decode_binary(chunk_data) + temp_path = Path(temp_path_str) + await asyncio.to_thread(_append_file, temp_path, raw) + over_limit = False + async with _uploads_lock: + upload = pending_uploads.get(file_id) + if upload: + upload["received_bytes"] += len(raw) + if upload_max > 0 and upload["received_bytes"] > upload_max: + pending_uploads.pop(file_id, None) + over_limit = True + received = upload["received_bytes"] + if over_limit: + _secure_delete(temp_path) + await send_resp(msg, writer, "upload_image_chunk", "error", {"message": "Upload exceeds size limit"}) + return + await send_resp(msg, writer, "upload_image_chunk", "ok", {"received": received}) + + +async def handle_upload_image_end(msg: dict, session: dict, writer: ProtocolWriter): + file_id = msg.get("file_id", "") + if not file_id: + await send_resp(msg, writer, "upload_image_end", "error", {"message": "Missing file_id"}) + return + async with _uploads_lock: + upload = pending_uploads.pop(file_id, None) + if not upload or upload["uploader_id"] != session["user_id"]: + await send_resp(msg, writer, "upload_image_end", "error", {"message": "No active upload"}) + return + temp_path = Path(upload["temp_path"]) + if upload["received_bytes"] != upload["file_size"]: + _secure_delete(temp_path) + await send_resp(msg, writer, "upload_image_end", "error", + {"message": f"Incomplete upload: received {upload['received_bytes']} of {upload['file_size']} bytes"}) + return + final_path = _safe_upload_path(file_id, ".enc") + if not final_path: + _secure_delete(temp_path) + await send_resp(msg, writer, "upload_image_end", "error", {"message": "Invalid file_id"}) + return + def _move_file(): + try: + temp_path.rename(final_path) + except Exception: + import shutil + shutil.move(str(temp_path), str(final_path)) + await asyncio.to_thread(_move_file) + await adb.complete_image_upload(file_id) + logger.info("[UPLOAD] %s completed upload file=%s (%d bytes)", + _who(session), file_id[:8], upload["received_bytes"]) + await send_resp(msg, writer, "upload_image_end", "ok", {"file_id": file_id}) + + +async def handle_download_image(msg: dict, session: dict, writer: ProtocolWriter): + file_id = msg.get("file_id", "") + offset = msg.get("offset", 0) + if not file_id: + await send_resp(msg, writer, "download_image", "error", {"message": "Missing file_id"}) + return + if not _valid_uuid(file_id): + await send_resp(msg, writer, "download_image", "error", {"message": "Invalid file_id"}) + return + upload = await adb.get_image_upload(file_id) + if not upload or not upload["completed"]: + await send_resp(msg, writer, "download_image", "error", {"message": "File not found"}) + return + if not await adb.is_conversation_member(upload["conversation_id"], session["user_id"]): + await send_resp(msg, writer, "download_image", "error", {"message": "Not a member"}) + return + file_path = _safe_upload_path(file_id, ".enc") + if not file_path or not file_path.exists(): + await send_resp(msg, writer, "download_image", "error", {"message": "File not found"}) + return + file_size = file_path.stat().st_size + chunk = await asyncio.to_thread(_read_file_chunk, file_path, offset, IMAGE_CHUNK_SIZE) + done = (offset + len(chunk)) >= file_size + if offset == 0: + logger.info("[DOWNLOAD] %s downloading file=%s (%d bytes)", _who(session), file_id[:8], file_size) + await send_resp(msg, writer, "download_image", "ok", { + "file_id": file_id, + "data": encode_binary(chunk), + "offset": offset, + "done": done, + "total_size": file_size, + }) + + +MAX_AVATAR_BYTES = 2 * 1024 * 1024 # 2 MB + + +async def handle_get_profile(msg: dict, session: dict, writer: ProtocolWriter): + """Get user profile (respects visibility for other users).""" + target_user_id = msg.get("user_id", "").strip() + if not target_user_id: + target_user_id = session["user_id"] + elif not _valid_uuid(target_user_id): + await send_resp(msg, writer, "get_profile", "error", {"message": "Invalid user_id"}) + return + profile = await adb.get_user_profile(target_user_id, viewer_id=session["user_id"]) + if not profile: + await send_resp(msg, writer, "get_profile", "error", {"message": "User not found"}) + return + # Serialize datetime fields + for key in ("created_at", "updated_at"): + if profile.get(key) and hasattr(profile[key], "isoformat"): + profile[key] = profile[key].isoformat() + await send_resp(msg, writer, "get_profile", "ok", profile) + + +async def handle_update_profile(msg: dict, session: dict, writer: ProtocolWriter): + """Update own profile fields.""" + fields = {} + for key in ("phone", "phone_visible", "email_visible", "location", "location_visible"): + if key in msg: + fields[key] = msg[key] + if not fields: + await send_resp(msg, writer, "update_profile", "error", {"message": "No fields to update"}) + return + await adb.update_user_profile(session["user_id"], **fields) + await send_resp(msg, writer, "update_profile", "ok", {"message": "OK"}) + + +async def handle_update_avatar(msg: dict, session: dict, writer: ProtocolWriter): + """Upload avatar (base64 in single message, max 2MB).""" + if await _is_rate_limited(f"update_avatar|{session['user_id']}", 5): + await send_resp(msg, writer, "update_avatar", "error", {"message": "Too many requests. Try later."}) + return + avatar_b64 = msg.get("data", "") + if not avatar_b64: + await send_resp(msg, writer, "update_avatar", "error", {"message": "Missing data"}) + return + avatar_data = decode_binary(avatar_b64) + if len(avatar_data) > MAX_AVATAR_BYTES: + await send_resp(msg, writer, "update_avatar", "error", + {"message": f"Avatar too large (max {MAX_AVATAR_BYTES} bytes)"}) + return + # Detect format from magic bytes + ext = "jpg" + if avatar_data[:8] == b'\x89PNG\r\n\x1a\n': + ext = "png" + avatar_dir = UPLOAD_DIR / "avatars" + avatar_dir.mkdir(parents=True, exist_ok=True) + os.chmod(avatar_dir, 0o700) + filename = f"{session['user_id']}.{ext}" + avatar_path = _safe_avatar_path(filename) + if not avatar_path: + await send_resp(msg, writer, "update_avatar", "error", {"message": "Invalid path"}) + return + await asyncio.to_thread(avatar_path.write_bytes, avatar_data) + await adb.update_user_profile(session["user_id"], avatar_file=filename) + logger.info("[AVATAR] %s updated their avatar", _who(session)) + await send_resp(msg, writer, "update_avatar", "ok", {"avatar_file": filename}) + + +async def handle_get_avatar(msg: dict, session: dict, writer: ProtocolWriter): + """Download avatar for a user.""" + target_user_id = msg.get("user_id", "").strip() + if not target_user_id: + await send_resp(msg, writer, "get_avatar", "error", {"message": "Missing user_id"}) + return + if not _valid_uuid(target_user_id): + await send_resp(msg, writer, "get_avatar", "error", {"message": "Invalid user_id"}) + return + profile = await adb.get_user_profile(target_user_id) + if not profile or not profile.get("avatar_file"): + await send_resp(msg, writer, "get_avatar", "error", {"message": "No avatar"}) + return + avatar_path = _safe_avatar_path(profile["avatar_file"]) + if not avatar_path or not avatar_path.exists(): + await send_resp(msg, writer, "get_avatar", "error", {"message": "Avatar file not found"}) + return + avatar_data = await asyncio.to_thread(avatar_path.read_bytes) + await send_resp(msg, writer, "get_avatar", "ok", { + "user_id": target_user_id, + "data": encode_binary(avatar_data), + "filename": profile["avatar_file"], + }) + + +async def handle_update_group_avatar(msg: dict, session: dict, writer: ProtocolWriter): + """Upload avatar for a group conversation (base64, max 2MB). Only members can set it.""" + if await _is_rate_limited(f"update_avatar|{session['user_id']}", 5): + await send_resp(msg, writer, "update_group_avatar", "error", {"message": "Too many requests. Try later."}) + return + conv_id = msg.get("conversation_id", "").strip() + avatar_b64 = msg.get("data", "") + if not conv_id or not avatar_b64: + await send_resp(msg, writer, "update_group_avatar", "error", {"message": "Missing fields"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "update_group_avatar", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "update_group_avatar", "error", {"message": "Not a member"}) + return + avatar_data = decode_binary(avatar_b64) + if len(avatar_data) > MAX_AVATAR_BYTES: + await send_resp(msg, writer, "update_group_avatar", "error", + {"message": f"Avatar too large (max {MAX_AVATAR_BYTES} bytes)"}) + return + ext = "jpg" + if avatar_data[:8] == b'\x89PNG\r\n\x1a\n': + ext = "png" + avatar_dir = UPLOAD_DIR / "avatars" + avatar_dir.mkdir(parents=True, exist_ok=True) + os.chmod(avatar_dir, 0o700) + filename = f"group_{conv_id}.{ext}" + avatar_path = _safe_avatar_path(filename) + if not avatar_path: + await send_resp(msg, writer, "update_group_avatar", "error", {"message": "Invalid path"}) + return + await asyncio.to_thread(avatar_path.write_bytes, avatar_data) + await adb.update_conversation_avatar(conv_id, filename) + logger.info("[AVATAR] %s updated group avatar for conv=%s", _who(session), conv_id[:8]) + await send_resp(msg, writer, "update_group_avatar", "ok", {"avatar_file": filename}) + + +async def handle_get_group_avatar(msg: dict, session: dict, writer: ProtocolWriter): + """Download avatar for a group conversation.""" + conv_id = msg.get("conversation_id", "").strip() + if not conv_id: + await send_resp(msg, writer, "get_group_avatar", "error", {"message": "Missing conversation_id"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "get_group_avatar", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "get_group_avatar", "error", {"message": "Not a member"}) + return + conv = await adb.get_conversation(conv_id) + if not conv or not conv.get("avatar_file"): + await send_resp(msg, writer, "get_group_avatar", "error", {"message": "No avatar"}) + return + avatar_path = _safe_avatar_path(conv["avatar_file"]) + if not avatar_path or not avatar_path.exists(): + await send_resp(msg, writer, "get_group_avatar", "error", {"message": "Avatar file not found"}) + return + avatar_data = await asyncio.to_thread(avatar_path.read_bytes) + await send_resp(msg, writer, "get_group_avatar", "ok", { + "conversation_id": conv_id, + "data": encode_binary(avatar_data), + "filename": conv["avatar_file"], + }) + + +async def handle_list_devices(msg: dict, session: dict, writer: ProtocolWriter): + """List all devices for the current user.""" + devices = await adb.get_user_devices(session["user_id"]) + result = [] + for d in devices: + entry = { + "device_id": d["id"], + "device_name": d.get("device_name"), + "created_at": d["created_at"].isoformat() if hasattr(d["created_at"], "isoformat") else str(d["created_at"]), + "last_seen_at": d["last_seen_at"].isoformat() if d.get("last_seen_at") and hasattr(d["last_seen_at"], "isoformat") else (str(d["last_seen_at"]) if d.get("last_seen_at") else None), + "is_current": d["id"] == session.get("device_id"), + } + result.append(entry) + await send_resp(msg, writer, "list_devices", "ok", {"devices": result}) + + +async def handle_remove_device(msg: dict, session: dict, writer: ProtocolWriter): + """Remove a device (cannot remove current device).""" + device_id = msg.get("device_id", "").strip() + if not device_id: + await send_resp(msg, writer, "remove_device", "error", {"message": "Missing device_id"}) + return + if not _valid_uuid(device_id): + await send_resp(msg, writer, "remove_device", "error", {"message": "Invalid device_id"}) + return + if device_id == session.get("device_id"): + await send_resp(msg, writer, "remove_device", "error", {"message": "Cannot remove current device"}) + return + dev = await adb.get_device(device_id) + if not dev or dev["user_id"] != session["user_id"]: + await send_resp(msg, writer, "remove_device", "error", {"message": "Device not found"}) + return + await adb.delete_device(device_id) + logger.info("[DEVICE] %s removed device=%s", _who(session), device_id[:8]) + await send_resp(msg, writer, "remove_device", "ok", {"message": "OK"}) + + +async def handle_session_reset(msg: dict, session: dict, writer: ProtocolWriter): + """Notify peer to reset a corrupted Double Ratchet session.""" + peer_user_id = msg.get("peer_user_id", "").strip() + peer_device_id = msg.get("peer_device_id", "").strip() or None + if not peer_user_id or not _valid_uuid(peer_user_id): + await send_resp(msg, writer, "session_reset", "error", {"message": "Invalid peer_user_id"}) + return + if peer_device_id and not _valid_uuid(peer_device_id): + await send_resp(msg, writer, "session_reset", "error", {"message": "Invalid peer_device_id"}) + return + # H3 fix: rate limit (5/min per user, keyed by user_id only — IP-independent) + if await _is_rate_limited(f"session_reset|{session['user_id']}", 5): + await send_resp(msg, writer, "session_reset", "error", {"message": "Rate limit exceeded"}) + return + # H3 fix: verify users share at least one conversation + if not await adb.shares_conversation(session["user_id"], peer_user_id): + await send_resp(msg, writer, "session_reset", "error", {"message": "No shared conversation"}) + return + # Push notification to peer (target specific device if specified) + notif_data = { + "from_user_id": session["user_id"], + "from_device_id": session.get("device_id"), + } + if peer_device_id: + # Send only to the specific device + targets = [] + async with _clients_lock: + for w in connected_clients.get(peer_user_id, []): + if writer_device_map.get(id(w)) == peer_device_id: + targets.append(w) + for w in targets: + try: + await w.send_response("session_reset", "ok", notif_data) + except Exception: + pass + else: + await _notify_users([peer_user_id], "session_reset", notif_data) + logger.info("[SESSION] %s reset session with peer=%s", _who(session), peer_user_id[:8]) + await send_resp(msg, writer, "session_reset", "ok", {}) + + +async def handle_get_deleted_since(msg: dict, session: dict, writer: ProtocolWriter): + """Return message IDs deleted since a given timestamp.""" + conv_id = msg.get("conversation_id", "") + since_ts = msg.get("since_ts", "") + if not conv_id or not since_ts: + await send_resp(msg, writer, "get_deleted_since", "error", {"message": "Missing parameters"}) + return + if not _valid_uuid(conv_id): + await send_resp(msg, writer, "get_deleted_since", "error", {"message": "Invalid conversation_id"}) + return + if not await adb.is_conversation_member(conv_id, session["user_id"]): + await send_resp(msg, writer, "get_deleted_since", "error", {"message": "Not a member"}) + return + deleted_ids = await adb.get_deleted_messages_since(conv_id, session["user_id"], since_ts) + await send_resp(msg, writer, "get_deleted_since", "ok", {"deleted_ids": deleted_ids}) + + +async def handle_reencrypt_messages(msg: dict, session: dict, writer: ProtocolWriter): + """Re-encrypt message history with self-encryption key (for device pairing).""" + if await _is_rate_limited(f"reencrypt|{session['user_id']}", 10): + await send_resp(msg, writer, "reencrypt_messages", "error", {"message": "Too many requests. Try later."}) + return + updates_raw = msg.get("updates", []) + if not updates_raw: + await send_resp(msg, writer, "reencrypt_messages", "error", {"message": "No updates"}) + return + if len(updates_raw) > 500: + await send_resp(msg, writer, "reencrypt_messages", "error", + {"message": "Too many updates (max 500 per request)"}) + return + updates = [] + for u in updates_raw: + mid = u.get("message_id", "") + ct_b64 = u.get("encrypted_content", "") + nonce_b64 = u.get("nonce", "") + if not mid or not ct_b64 or not nonce_b64: + continue + updates.append({ + "message_id": mid, + "encrypted_content": decode_binary(ct_b64), + "nonce": decode_binary(nonce_b64), + }) + if not updates: + await send_resp(msg, writer, "reencrypt_messages", "error", {"message": "No valid updates"}) + return + await adb.batch_reencrypt_messages(session["user_id"], updates) + logger.info("[REENCRYPT] %s re-encrypted %d messages", _who(session), len(updates)) + await send_resp(msg, writer, "reencrypt_messages", "ok", {"count": len(updates)}) + + +async def _cleanup_uploads(): + stale = await adb.get_stale_uploads(UPLOAD_STALE_SECONDS) + for s in stale: + fid = s["file_id"] + for ext in (".tmp", ".enc"): + p = _safe_upload_path(fid, ext) + if not p: + continue + _secure_delete(p) + await adb.delete_image_upload(fid) + async with _uploads_lock: + pending_uploads.pop(fid, None) + if stale: + logger.info("Cleaned up %d stale uploads.", len(stale)) + + +async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): + global current_connections + addr = _get_peer_addr(ProtocolWriter(writer)) + async with _conn_lock: + current_connections += 1 + connection_counts[addr] = connection_counts.get(addr, 0) + 1 + over_limit = (current_connections > MAX_CONNECTIONS_GLOBAL or + connection_counts[addr] > MAX_CONNECTIONS_PER_IP) + if over_limit: + try: + writer.close() + except Exception: + pass + async with _conn_lock: + current_connections = max(0, current_connections - 1) + connection_counts[addr] = max(0, connection_counts.get(addr, 1) - 1) + return + logger.info("[CONN] Client connected from %s", addr) + proto_reader = ProtocolReader(reader) + proto_writer = ProtocolWriter(writer) + session = None + state = {"_req_times": []} + + try: + while True: + try: + msg = await proto_reader.read_message() + except ValueError as e: + try: + await proto_writer.send_response("protocol_error", "error", {"message": str(e)}) + except Exception: + pass + break + if msg is None: + break + + msg_type = msg.get("type", "") + now = asyncio.get_event_loop().time() + times = [t for t in state["_req_times"] if now - t <= CONNECTION_RL_WINDOW] + if len(times) >= CONNECTION_RL_MAX: + await send_resp(msg, proto_writer, msg_type, "error", {"message": "Too many requests. Slow down."}) + state["_req_times"] = times + continue + times.append(now) + state["_req_times"] = times + + try: + if msg_type == "register": + await handle_register_start(msg, proto_writer) + elif msg_type == "register_confirm": + await handle_register_confirm(msg, proto_writer) + elif msg_type == "login_start": + await handle_login_start(msg, proto_writer, state) + elif msg_type == "login_finish": + result = await handle_login_finish(msg, proto_writer, state) + if result: + session = result + elif msg_type == "pairing_start": + await handle_pairing_start(msg, proto_writer) + elif msg_type == "pairing_poll": + await handle_pairing_poll(msg, proto_writer) + elif session is None: + await send_resp(msg, proto_writer, msg_type, "error", {"message": "Not logged in"}) + elif msg_type == "get_user_info": + await handle_get_user_info(msg, session, proto_writer) + elif msg_type == "upload_prekeys": + await handle_upload_prekeys(msg, session, proto_writer) + elif msg_type == "get_key_bundle": + await handle_get_key_bundle(msg, session, proto_writer) + elif msg_type == "get_prekey_count": + await handle_get_prekey_count(msg, session, proto_writer) + elif msg_type == "ensure_prekeys": + await handle_ensure_prekeys(msg, session, proto_writer) + elif msg_type == "create_conversation": + await handle_create_conversation(msg, session, proto_writer) + elif msg_type == "find_conversation": + await handle_find_conversation(msg, session, proto_writer) + elif msg_type == "add_member": + await handle_add_member(msg, session, proto_writer) + elif msg_type == "accept_invitation": + await handle_accept_invitation(msg, session, proto_writer) + elif msg_type == "decline_invitation": + await handle_decline_invitation(msg, session, proto_writer) + elif msg_type == "list_invitations": + await handle_list_invitations(msg, session, proto_writer) + elif msg_type == "list_conversations": + await handle_list_conversations(msg, session, proto_writer) + elif msg_type == "send_message": + await handle_send_message(msg, session, proto_writer) + elif msg_type == "get_messages": + await handle_get_messages(msg, session, proto_writer) + elif msg_type == "rotate_keys": + await handle_rotate_keys(msg, session, proto_writer) + elif msg_type == "change_username": + await handle_change_username(msg, session, proto_writer) + elif msg_type == "remove_member": + await handle_remove_member(msg, session, proto_writer) + elif msg_type == "leave_group": + await handle_leave_group(msg, session, proto_writer) + elif msg_type == "rename_conversation": + await handle_rename_conversation(msg, session, proto_writer) + elif msg_type == "delete_conversation": + await handle_delete_conversation(msg, session, proto_writer) + elif msg_type == "mark_read": + await handle_mark_read(msg, session, proto_writer) + elif msg_type == "mark_conversation_read": + await handle_mark_conversation_read(msg, session, proto_writer) + elif msg_type == "confirm_delivery": + await handle_confirm_delivery(msg, session, proto_writer) + elif msg_type == "pairing_claim": + await handle_pairing_claim(msg, session, proto_writer) + elif msg_type == "pairing_send": + await handle_pairing_send(msg, session, proto_writer) + elif msg_type == "delete_message": + await handle_delete_message(msg, session, proto_writer) + elif msg_type == "upload_image_start": + await handle_upload_image_start(msg, session, proto_writer) + elif msg_type == "upload_image_chunk": + await handle_upload_image_chunk(msg, session, proto_writer) + elif msg_type == "upload_image_end": + await handle_upload_image_end(msg, session, proto_writer) + elif msg_type == "download_image": + await handle_download_image(msg, session, proto_writer) + elif msg_type == "get_profile": + await handle_get_profile(msg, session, proto_writer) + elif msg_type == "update_profile": + await handle_update_profile(msg, session, proto_writer) + elif msg_type == "update_avatar": + await handle_update_avatar(msg, session, proto_writer) + elif msg_type == "get_avatar": + await handle_get_avatar(msg, session, proto_writer) + elif msg_type == "update_group_avatar": + await handle_update_group_avatar(msg, session, proto_writer) + elif msg_type == "get_group_avatar": + await handle_get_group_avatar(msg, session, proto_writer) + elif msg_type == "get_deleted_since": + await handle_get_deleted_since(msg, session, proto_writer) + elif msg_type == "reencrypt_messages": + await handle_reencrypt_messages(msg, session, proto_writer) + elif msg_type == "list_devices": + await handle_list_devices(msg, session, proto_writer) + elif msg_type == "remove_device": + await handle_remove_device(msg, session, proto_writer) + elif msg_type == "session_reset": + await handle_session_reset(msg, session, proto_writer) + elif msg_type == "react_message": + await handle_react_message(msg, session, proto_writer) + elif msg_type == "pin_message": + await handle_pin_message(msg, session, proto_writer) + elif msg_type == "get_pinned_messages": + await handle_get_pinned_messages(msg, session, proto_writer) + else: + await send_resp(msg, proto_writer, msg_type, "error", {"message": "Unknown type"}) + except Exception as e: + logger.warning("[ERROR] %s handler '%s' failed: %s", _who(session), msg_type, e, exc_info=True) + try: + await send_resp(msg, proto_writer, msg_type, "error", {"message": "Internal server error"}) + except Exception: + break # Can't send response — connection is dead + except Exception as e: + logger.warning("Client connection error: %s", e) + finally: + async with _conn_lock: + current_connections = max(0, current_connections - 1) + connection_counts[addr] = max(0, connection_counts.get(addr, 1) - 1) + offline_targets = [] + if session: + uid = session["user_id"] + contacts = await adb.get_user_contacts(uid) + async with _clients_lock: + writer_device_map.pop(id(proto_writer), None) + if uid in connected_clients: + remaining = [w for w in connected_clients[uid] if w is not proto_writer] + if remaining: + connected_clients[uid] = remaining + else: + del connected_clients[uid] + # User fully offline — snapshot targets under lock + for contact_id in contacts: + for cw in connected_clients.get(contact_id, []): + offline_targets.append(cw) + # Send offline notifications outside lock + for cw in offline_targets: + try: + await cw.send_response("user_offline", "ok", {"user_id": uid}) + except Exception: + pass + writer.close() + logger.info("[CONN] %s disconnected", _who(session) if session else addr) + + +async def main(): + setup_logging() + host = os.getenv("SERVER_HOST", "127.0.0.1") + port = int(os.getenv("SERVER_PORT", "9999")) + tls_enabled = os.getenv("TLS_ENABLED", "false").lower() in ("1", "true", "yes") + tls_required = os.getenv("TLS_REQUIRED", "false").lower() in ("1", "true", "yes") + tls_autogen = os.getenv("TLS_AUTOGEN", "false").lower() in ("1", "true", "yes") + + is_dev = os.getenv("ENVIRONMENT", "").lower() in ("dev", "development") + ssl_context = None + if tls_required and not tls_enabled: + raise RuntimeError("TLS_REQUIRED is enabled but TLS is not enabled.") + if tls_enabled: + cert_file = os.getenv("TLS_CERT_FILE", "").strip() + key_file = os.getenv("TLS_KEY_FILE", "").strip() + if not cert_file or not key_file: + if tls_autogen: + if not is_dev: + raise RuntimeError("TLS_AUTOGEN is only allowed when ENVIRONMENT=dev") + cert_dir = Path(__file__).resolve().parent / "certs" + cert_dir.mkdir(parents=True, exist_ok=True) + cert_file = str(cert_dir / "server.crt") + key_file = str(cert_dir / "server.key") + if not (os.path.exists(cert_file) and os.path.exists(key_file)): + try: + subprocess.run( + [ + "openssl", "req", "-x509", "-newkey", "rsa:4096", + "-keyout", key_file, "-out", cert_file, + "-days", "365", "-nodes", "-subj", "/CN=localhost", + ], + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + ) + os.chmod(key_file, 0o600) + except FileNotFoundError: + raise RuntimeError("OpenSSL not found.") + except subprocess.CalledProcessError: + raise RuntimeError("Failed to auto-generate TLS cert.") + logger.warning("Using auto-generated self-signed certificate — not for production use.") + else: + raise RuntimeError("TLS is enabled but TLS_CERT_FILE or TLS_KEY_FILE is missing.") + ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file) + else: + logger.warning("TLS is disabled — traffic is unencrypted. Set TLS_ENABLED=true for production.") + + UPLOAD_DIR.mkdir(parents=True, exist_ok=True) + + # Thread pool for asyncio.to_thread() — DB calls + file I/O + pool_workers = int(os.getenv("THREAD_POOL_SIZE", "40")) + asyncio.get_event_loop().set_default_executor(ThreadPoolExecutor(max_workers=pool_workers)) + logger.info("Thread pool executor: %d workers", pool_workers) + + # Load phantom user IDs from DB into in-memory cache + phantom_user_ids.update(await adb.get_all_phantom_user_ids()) + if phantom_user_ids: + logger.info("Loaded %d phantom user IDs.", len(phantom_user_ids)) + + server = await asyncio.start_server( + handle_client, host, port, limit=MAX_MESSAGE_BYTES, ssl=ssl_context, + ) + logger.info("Encrypted chat server v%s listening on %s:%s", VERSION, host, port) + + async def _cleanup_rate_limits(): + async with _conn_lock: + now = asyncio.get_event_loop().time() + window_start = now - RATE_LIMIT_WINDOW + stale_keys = [k for k, times in rate_limits.items() + if not any(t >= window_start for t in times)] + for k in stale_keys: + del rate_limits[k] + stale_conns = [k for k, v in connection_counts.items() if v <= 0] + for k in stale_conns: + del connection_counts[k] + + _cleanup_cycle = 0 + + async def _periodic_cleanup(): + nonlocal _cleanup_cycle + while True: + await asyncio.sleep(120) + _cleanup_cycle += 1 + try: + await _cleanup_uploads() + except Exception as e: + logger.warning("Upload cleanup error: %s", e) + try: + await _cleanup_rate_limits() + except Exception as e: + logger.warning("Rate limit cleanup error: %s", e) + try: + await _cleanup_registrations() + except Exception as e: + logger.warning("Registration cleanup error: %s", e) + # L8: clean up stale phantom users (>30 days, no real conversations) + try: + deleted = await adb.cleanup_stale_phantoms(30) + if deleted: + async with _clients_lock: + phantom_user_ids.clear() + phantom_user_ids.update(await adb.get_all_phantom_user_ids()) + logger.info("Cleaned up %d stale phantom users.", deleted) + except Exception as e: + logger.warning("Phantom cleanup error: %s", e) + # Metadata retention: purge old reads and reactions (every 30 cycles = ~1 hour) + if _cleanup_cycle % 30 == 0: + try: + reads_del = await adb.cleanup_old_reads(METADATA_RETENTION_DAYS) + reactions_del = await adb.cleanup_old_reactions(METADATA_RETENTION_DAYS) + if reads_del or reactions_del: + logger.info("Metadata cleanup: %d reads, %d reactions purged", + reads_del, reactions_del) + except Exception as e: + logger.warning("Metadata cleanup error: %s", e) + + asyncio.create_task(_periodic_cleanup()) + + loop = asyncio.get_running_loop() + stop = loop.create_future() + + def signal_handler(): + if not stop.done(): + stop.set_result(None) + + for sig in (signal.SIGINT, signal.SIGTERM): + loop.add_signal_handler(sig, signal_handler) + + async with server: + await stop + logger.info("Shutting down — closing %d client connections...", sum(len(ws) for ws in connected_clients.values())) + # Stop accepting new connections + server.close() + # Force-close all connected client writers + async with _clients_lock: + all_writers = [w for writers in connected_clients.values() for w in writers] + connected_clients.clear() + writer_device_map.clear() + for w in all_writers: + try: + w.close() + except Exception: + pass + # Give handle_client loops a moment to notice closed connections + await asyncio.sleep(0.1) + # Cancel any remaining handle_client tasks that are still blocked + tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] + for t in tasks: + t.cancel() + await asyncio.gather(*tasks, return_exceptions=True) + logger.info("Server shut down.") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/PENTEST_CLIENT.md b/tests/PENTEST_CLIENT.md new file mode 100644 index 0000000..3cd3202 --- /dev/null +++ b/tests/PENTEST_CLIENT.md @@ -0,0 +1,79 @@ +# `tests/pentest_client.py` + +Automatizovaný pentest/integration harness nad živým serverem s reálnými účty. + +## Co test dělá + +1. **Conversation Isolation (AuthZ)** + - Účet `outsider` zkouší `get_messages`, `mark_read` a `send_message` do konverzace, kde není členem. + - Očekávání: server vrátí `error` + `"Not a member"`. + +2. **Malformed Header Rejection** + - Platný člen konverzace pošle `send_message` s obřím `ratchet_header`. + - Očekávání: server odmítne request (`Invalid ratchet_header format`), tj. funguje `_validate_header` limit. + +3. **Session Reset Authorization** + - `outsider` pošle `session_reset` na `peer_user_id`. + - Očekávání: `error` + `"No shared conversation"`. + - Pokud účty sdílenou konverzaci opravdu mají, test se označí jako `SKIP` (setup issue, ne nutně bezpečnostní chyba). + +4. **Login Rate Limits (volitelné)** + - Anonymní klient spamuje `login_start`: + - stejný email v různých kombinacích velikosti písmen (test case-normalization bucketu), + - potom rotace různých emailů ze stejné IP (test per-IP bucketu). + - Očekávání: aktivuje se jak per-email limit, tak per-IP limit. + +## Požadavky + +- Běžící server (`server.py`). +- Existující lokální klíče pro účty v `~/.encrypted_chat//` (stejné jako pro běžného CLI klienta). +- 3 různé účty: + - `member` (A), + - `peer` (B), + - `outsider` (C). + +## Spuštění + +```bash +python3 tests/pentest_client.py \ + --server-host localhost \ + --member-email alice@example.com \ + --peer-email bob@example.com \ + --outsider-email mallory@example.com +``` + +Skript si vyžádá hesla interaktivně. Lze je předat i argumenty: + +```bash +python3 tests/pentest_client.py \ + --server-host localhost \ + --member-email alice@example.com --member-password '***' \ + --peer-email bob@example.com --peer-password '***' \ + --outsider-email mallory@example.com --outsider-password '***' +``` + +Volby: + +- `--conversation-id `: použije konkrétní konverzaci místo auto member<->peer DM. +- `--skip-login-rate-limit`: přeskočí test `login_start` limiteru. +- `--server-host `: přepíše `SERVER_HOST` pro tento běh. +- `--server-port `: přepíše `SERVER_PORT` pro tento běh. + +Poznámka k TLS: + +- Pokud máš v `.env` `SERVER_HOST=0.0.0.0`, je to správně pro server bind, ale klient na to nesmí přistupovat přes TLS. +- Pro klienta použij `--server-host` s hodnotou, která je v certifikátu (SAN/CN), typicky `localhost` nebo konkrétní IP. + +## Výstup + +Skript tiskne souhrn: + +- `[PASS]` test prošel, +- `[FAIL]` test selhal (potenciální regrese), +- `[SKIP]` test nelze vyhodnotit kvůli dataset/setup podmínkám. + +Návratový kód: + +- `0` = bez failu, +- `1` = alespoň jeden fail, +- `2` = chyba vstupních parametrů. diff --git a/tests/pentest_client.py b/tests/pentest_client.py new file mode 100644 index 0000000..502153a --- /dev/null +++ b/tests/pentest_client.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 +"""Security regression harness for encrypted_chat server. + +Runs focused pentest/integration checks against a live server using real accounts. +""" + +from __future__ import annotations + +import argparse +import asyncio +import getpass +import os +import ssl +import sys +import time +import uuid +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING, Any + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +if TYPE_CHECKING: + from chat_core import ChatClient + + +@dataclass +class TestResult: + name: str + outcome: str # PASS | FAIL | SKIP + details: str + + +def _msg(resp: dict) -> str: + data = resp.get("data") or {} + return str(data.get("message", "")) + + +async def _connect_client() -> "ChatClient": + from chat_core import ChatClient # Imported lazily so --help works without full deps + client = ChatClient() + await client.connect() + client._listener_task = asyncio.create_task(client._background_listener()) + return client + + +async def _login_client(email: str, password: str) -> tuple["ChatClient", str]: + client = await _connect_client() + ok, message = await client.login(email, password) + if not ok: + await client.close() + raise RuntimeError(f"Login failed for {email}: {message}") + return client, message + + +async def _close_client(client: "ChatClient | None"): + if not client: + return + try: + await client.close() + except Exception: + pass + + +def _too_many_attempts(resp: dict) -> bool: + return resp.get("status") == "error" and "Too many attempts" in _msg(resp) + + +async def test_conversation_isolation(outsider: "ChatClient", conv_id: str) -> TestResult: + """Outsider must not access a conversation they are not a member of.""" + fake_mid = str(uuid.uuid4()) + checks: list[tuple[str, dict]] = [ + ( + "get_messages", + await outsider.send_and_recv("get_messages", conversation_id=conv_id, limit=5, offset=0), + ), + ( + "mark_read", + await outsider.send_and_recv("mark_read", conversation_id=conv_id, message_ids=[fake_mid]), + ), + ( + "send_message", + await outsider.send_and_recv("send_message", conversation_id=conv_id), + ), + ] + failures: list[str] = [] + for endpoint, resp in checks: + if resp.get("status") != "error" or "Not a member" not in _msg(resp): + failures.append(f"{endpoint} -> status={resp.get('status')} message={_msg(resp)!r}") + if failures: + return TestResult("Conversation Isolation (AuthZ)", "FAIL", "; ".join(failures)) + return TestResult( + "Conversation Isolation (AuthZ)", + "PASS", + "Outsider got 'Not a member' for get_messages, mark_read, send_message.", + ) + + +async def test_session_reset_no_shared(outsider: "ChatClient", peer_user_id: str) -> TestResult: + """session_reset must be rejected without shared conversation.""" + resp = await outsider.send_and_recv("session_reset", peer_user_id=peer_user_id) + if resp.get("status") == "error" and "No shared conversation" in _msg(resp): + return TestResult("Session Reset Authorization", "PASS", "Rejected with 'No shared conversation'.") + if resp.get("status") == "ok": + return TestResult( + "Session Reset Authorization", + "SKIP", + "Outsider appears to share a conversation with peer in current dataset.", + ) + return TestResult( + "Session Reset Authorization", + "FAIL", + f"Unexpected response: status={resp.get('status')} message={_msg(resp)!r}", + ) + + +async def test_malformed_header_rejected(member: "ChatClient", conv_id: str) -> TestResult: + """Oversized ratchet header should be rejected by server-side validation.""" + huge_header = {"dh_pub": "A" * 5000, "n": 1, "pn": 0} + resp = await member.send_and_recv( + "send_message", + conversation_id=conv_id, + ratchet_header=huge_header, + recipients=[{}], + ) + if resp.get("status") == "error" and "Invalid ratchet_header format" in _msg(resp): + return TestResult("Malformed Header Rejection", "PASS", "Oversized ratchet_header rejected.") + return TestResult( + "Malformed Header Rejection", + "FAIL", + f"Unexpected response: status={resp.get('status')} message={_msg(resp)!r}", + ) + + +async def test_login_rate_limits() -> TestResult: + """Validate login_start per-email(case-insensitive) and per-IP limits.""" + probe = await _connect_client() + try: + stamp = int(time.time()) + base_local = f"pentest-login-{stamp}" + base_domain = "example.invalid" + base_email = f"{base_local}@{base_domain}" + case_variants = [ + base_email, + f"{base_local.upper()}@{base_domain}", + f"{base_local.capitalize()}@{base_domain}", + f"{base_local}@{base_domain.upper()}", + f"{base_local.swapcase()}@{base_domain}", + base_email, + f"{base_local.upper()}@{base_domain}", + f"{base_local.capitalize()}@{base_domain}", + f"{base_local}@{base_domain.upper()}", + f"{base_local.swapcase()}@{base_domain}", + base_email, # should exceed per-email bucket (10/min) + ] + + email_bucket_triggered = False + phase1_last = "" + for e in case_variants: + resp = await probe.send_and_recv("login_start", email=e) + phase1_last = _msg(resp) + if _too_many_attempts(resp): + email_bucket_triggered = True + await asyncio.sleep(0.12) # stay under per-connection 20 req/s limiter + + ip_bucket_triggered = False + phase2_last = "" + for i in range(1, 16): + unique_email = f"{base_local}-{i}@{base_domain}" + resp = await probe.send_and_recv("login_start", email=unique_email) + phase2_last = _msg(resp) + if _too_many_attempts(resp): + ip_bucket_triggered = True + break + await asyncio.sleep(0.12) + + if email_bucket_triggered and ip_bucket_triggered: + return TestResult( + "Login Rate Limits (case + per-IP)", + "PASS", + "Per-email(case-insensitive) and per-IP login_start limits both triggered.", + ) + return TestResult( + "Login Rate Limits (case + per-IP)", + "FAIL", + ( + f"email_bucket_triggered={email_bucket_triggered}, " + f"ip_bucket_triggered={ip_bucket_triggered}, " + f"phase1_last={phase1_last!r}, phase2_last={phase2_last!r}" + ), + ) + finally: + await _close_client(probe) + + +def _pick_password(flag_value: str | None, prompt: str) -> str: + if flag_value is not None: + return flag_value + return getpass.getpass(prompt) + + +async def run(args: argparse.Namespace) -> int: + if len({args.member_email.lower(), args.peer_email.lower(), args.outsider_email.lower()}) != 3: + print("ERROR: member/peer/outsider emails must be three different accounts.", file=sys.stderr) + return 2 + + if args.server_host: + os.environ["SERVER_HOST"] = args.server_host + if args.server_port is not None: + os.environ["SERVER_PORT"] = str(args.server_port) + + effective_host = os.getenv("SERVER_HOST", "127.0.0.1").strip() + if effective_host == "0.0.0.0": + print( + "ERROR: SERVER_HOST=0.0.0.0 je bind adresa serveru, ne klientský TLS hostname.\n" + "Pouzij --server-host (napr. localhost nebo 192.168.1.112).", + file=sys.stderr, + ) + return 2 + + member_password = _pick_password(args.member_password, f"Password for {args.member_email}: ") + peer_password = _pick_password(args.peer_password, f"Password for {args.peer_email}: ") + outsider_password = _pick_password(args.outsider_password, f"Password for {args.outsider_email}: ") + + member: "ChatClient | None" = None + peer: "ChatClient | None" = None + outsider: "ChatClient | None" = None + results: list[TestResult] = [] + + try: + print("[setup] Logging in member account...") + member, _ = await _login_client(args.member_email, member_password) + print("[setup] Logging in peer account...") + peer, _ = await _login_client(args.peer_email, peer_password) + print("[setup] Logging in outsider account...") + outsider, _ = await _login_client(args.outsider_email, outsider_password) + + if args.conversation_id: + conv_id = args.conversation_id + else: + print("[setup] Finding/creating member<->peer direct conversation...") + conv_id, err = await member.find_or_create_conversation(args.peer_email) + if not conv_id: + raise RuntimeError(f"Could not find/create conversation: {err}") + + outsider_convs = {c["conversation_id"] for c in await outsider.list_conversations()} + if conv_id in outsider_convs: + results.append( + TestResult( + "Conversation Isolation (AuthZ)", + "SKIP", + "Outsider is already a member of target conversation; choose different outsider/account set.", + ) + ) + else: + results.append(await test_conversation_isolation(outsider, conv_id)) + results.append(await test_malformed_header_rejected(member, conv_id)) + + results.append(await test_session_reset_no_shared(outsider, peer.session["user_id"])) + + if not args.skip_login_rate_limit: + results.append(await test_login_rate_limits()) + + except ssl.SSLCertVerificationError as e: + results.append( + TestResult( + "Harness Setup", + "FAIL", + ( + f"{e}. Zkus --server-host s hodnotou ze SAN/CN certifikatu " + "(napr. localhost nebo 192.168.1.112)." + ), + ) + ) + except Exception as e: + emsg = str(e) + if "CERTIFICATE_VERIFY_FAILED" in emsg or "IP address mismatch" in emsg: + emsg += ( + " | Hint: pouzij --server-host s hostname/IP, ktery je v certifikatu " + "(SERVER_HOST nesmi byt 0.0.0.0)." + ) + results.append(TestResult("Harness Setup", "FAIL", emsg)) + finally: + await _close_client(member) + await _close_client(peer) + await _close_client(outsider) + + print("\n=== Pentest Results ===") + for r in results: + print(f"[{r.outcome}] {r.name}: {r.details}") + + has_fail = any(r.outcome == "FAIL" for r in results) + return 1 if has_fail else 0 + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Run focused pentest/integration checks against encrypted_chat server." + ) + parser.add_argument("--member-email", required=True, help="Email of regular account A (conversation member).") + parser.add_argument("--peer-email", required=True, help="Email of regular account B (other conversation member).") + parser.add_argument("--outsider-email", required=True, help="Email of regular account C (must not be in target conversation).") + parser.add_argument( + "--server-host", + default=None, + help="Override SERVER_HOST for this run (must match TLS cert SAN/CN).", + ) + parser.add_argument( + "--server-port", + type=int, + default=None, + help="Override SERVER_PORT for this run.", + ) + parser.add_argument("--member-password", default=None, help="Password for --member-email (optional; prompt if omitted).") + parser.add_argument("--peer-password", default=None, help="Password for --peer-email (optional; prompt if omitted).") + parser.add_argument("--outsider-password", default=None, help="Password for --outsider-email (optional; prompt if omitted).") + parser.add_argument( + "--conversation-id", + default=None, + help="Optional target conversation UUID. If omitted, member<->peer DM is found/created automatically.", + ) + parser.add_argument( + "--skip-login-rate-limit", + action="store_true", + help="Skip anonymous login_start rate-limit regression check.", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + return asyncio.run(run(args)) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/theme.py b/theme.py new file mode 100644 index 0000000..15ec5c1 --- /dev/null +++ b/theme.py @@ -0,0 +1,539 @@ +"""Theme system for Encrypted Chat GUI — light + dark mode with live switching.""" + +from __future__ import annotations + +import json +import logging +import os +from dataclasses import dataclass, fields +from pathlib import Path +from typing import Callable + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True) +class ThemeColors: + """All colour tokens for one theme.""" + + # Surface hierarchy + bg_primary: str # Main background (messages area, right panel) + bg_secondary: str # Cards, inputs, elevated surfaces + bg_tertiary: str # Sidebar, deeper surfaces + bg_hover: str # Hover state on list items + bg_selected: str # Selected list item + + # Text + text_primary: str # Main text + text_secondary: str # Secondary / muted text + text_muted: str # Timestamps, counters, hints + + # Accent (brand blue) + accent: str + accent_hover: str + accent_text: str # Text on accent background + + # Message bubbles + bubble_sent_bg: str + bubble_sent_text: str + bubble_recv_bg: str + bubble_recv_text: str + bubble_sent_meta: str # Timestamp/read inside sent bubble + bubble_recv_meta: str # Timestamp inside received bubble + + # Semantic colours + success: str + warning: str + error: str + info: str + + # Chrome / borders + border: str + border_focus: str + scrollbar: str + separator: str + overlay: str # Privacy overlay background (rgba) + + # Links + link_https: str + link_http: str # Insecure link (orange) + + # Mentions & search + mention: str + search_highlight: str + search_current: str + + # Reactions + reaction_bg: str + reaction_bg_own: str + reaction_border: str + reaction_border_own: str + + # Misc + online_dot: str + online_dot_border: str + pin_color: str + sender_name_other: str # Non-self sender name colour in groups + receipt_read: str # Read receipt checkmarks (must contrast with sent bubble bg) + + +# --------------------------------------------------------------------------- +# Dark theme — Catppuccin Mocha palette +# --------------------------------------------------------------------------- + +DARK_THEME = ThemeColors( + bg_primary="#1e1e2e", + bg_secondary="#313244", + bg_tertiary="#181825", + bg_hover="#252536", + bg_selected="#313244", + + text_primary="#cdd6f4", + text_secondary="#bac2de", + text_muted="#6c7086", + + accent="#89b4fa", + accent_hover="#74c7ec", + accent_text="#1e1e2e", + + bubble_sent_bg="#2a4a7f", + bubble_sent_text="#cdd6f4", + bubble_recv_bg="#2c2c3e", + bubble_recv_text="#cdd6f4", + bubble_sent_meta="#8899bb", + bubble_recv_meta="#6c7086", + + success="#a6e3a1", + warning="#f9e2af", + error="#f38ba8", + info="#74c7ec", + + border="#45475a", + border_focus="#89b4fa", + scrollbar="#45475a", + separator="#45475a", + overlay="rgba(30, 30, 46, 245)", + + link_https="#89b4fa", + link_http="#fab387", + + mention="#89b4fa", + search_highlight="#f9e2af", + search_current="#fab387", + + reaction_bg="#313244", + reaction_bg_own="#45475a", + reaction_border="#45475a", + reaction_border_own="#585b70", + + online_dot="#a6e3a1", + online_dot_border="#181825", + pin_color="#f9e2af", + sender_name_other="#f9e2af", + receipt_read="#74c7ec", +) + +# --------------------------------------------------------------------------- +# Light theme — Signal-inspired palette +# --------------------------------------------------------------------------- + +LIGHT_THEME = ThemeColors( + bg_primary="#ffffff", + bg_secondary="#f2f2f7", + bg_tertiary="#e5e5ea", + bg_hover="#dcdce4", + bg_selected="#c7c7d1", + + text_primary="#1c1c1e", + text_secondary="#3a3a3c", + text_muted="#8a8a8e", + + accent="#3478f6", + accent_hover="#2563eb", + accent_text="#ffffff", + + bubble_sent_bg="#3478f6", + bubble_sent_text="#ffffff", + bubble_recv_bg="#e5e5ea", + bubble_recv_text="#1c1c1e", + bubble_sent_meta="#a3c4ff", + bubble_recv_meta="#8a8a8e", + + success="#34c759", + warning="#ff9500", + error="#ff3b30", + info="#5ac8fa", + + border="#c6c6c8", + border_focus="#3478f6", + scrollbar="#aeaeb2", + separator="#c6c6c8", + overlay="rgba(0, 0, 0, 200)", + + link_https="#2563eb", + link_http="#ea580c", + + mention="#2563eb", + search_highlight="#fde68a", + search_current="#fb923c", + + reaction_bg="#e5e5ea", + reaction_bg_own="#c7c7d1", + reaction_border="#c6c6c8", + reaction_border_own="#a0a0a8", + + online_dot="#34c759", + online_dot_border="#e5e5ea", + pin_color="#ff9500", + sender_name_other="#7c3aed", + receipt_read="#d0e8ff", +) + + +# --------------------------------------------------------------------------- +# ThemeManager singleton +# --------------------------------------------------------------------------- + +class ThemeManager: + """Manages the active theme, persistence and change notification.""" + + _instance: ThemeManager | None = None + + @classmethod + def instance(cls) -> ThemeManager: + if cls._instance is None: + cls._instance = cls() + return cls._instance + + def __init__(self): + self._is_dark: bool = True + self._listeners: list[Callable[[], None]] = [] + self._email: str | None = None + self._load_global() + + # -- Public API -- + + @property + def is_dark(self) -> bool: + return self._is_dark + + @property + def colors(self) -> ThemeColors: + return DARK_THEME if self._is_dark else LIGHT_THEME + + def toggle(self): + self._is_dark = not self._is_dark + self._save() + self._notify() + + def set_dark(self, dark: bool): + if dark == self._is_dark: + return + self._is_dark = dark + self._save() + self._notify() + + def set_email(self, email: str): + """After login, bind to user-specific preference file.""" + self._email = email + self._load_user() + + def on_change(self, callback: Callable[[], None]): + self._listeners.append(callback) + + def remove_listener(self, callback: Callable[[], None]): + try: + self._listeners.remove(callback) + except ValueError: + pass + + def generate_qss(self) -> str: + return _build_qss(self.colors) + + # -- Persistence -- + + def _global_path(self) -> Path: + p = Path.home() / ".encrypted_chat" + p.mkdir(parents=True, exist_ok=True) + return p / "global_settings.json" + + def _user_path(self) -> Path | None: + if not self._email: + return None + p = Path.home() / ".encrypted_chat" / self._email + if not p.exists(): + return None + return p / "theme.json" + + def _load_global(self): + try: + p = self._global_path() + if p.exists(): + data = json.loads(p.read_text()) + self._is_dark = data.get("dark", True) + except Exception: + pass + + def _load_user(self): + try: + p = self._user_path() + if p and p.exists(): + data = json.loads(p.read_text()) + self._is_dark = data.get("dark", self._is_dark) + except Exception: + pass + + def _save(self): + data = {"dark": self._is_dark} + try: + self._global_path().write_text(json.dumps(data)) + except Exception: + pass + try: + p = self._user_path() + if p: + p.write_text(json.dumps(data)) + except Exception: + pass + + def _notify(self): + for cb in list(self._listeners): + try: + cb() + except Exception: + logger.debug("Theme listener error", exc_info=True) + + +# --------------------------------------------------------------------------- +# Convenience accessors +# --------------------------------------------------------------------------- + +def c() -> ThemeColors: + """Shorthand for ThemeManager.instance().colors.""" + return ThemeManager.instance().colors + + +def qss() -> str: + """Shorthand for ThemeManager.instance().generate_qss().""" + return ThemeManager.instance().generate_qss() + + +def tm() -> ThemeManager: + """Shorthand for ThemeManager.instance().""" + return ThemeManager.instance() + + +# --------------------------------------------------------------------------- +# QSS generator +# --------------------------------------------------------------------------- + +_FONT_STACK = ( + '"Segoe UI Variable", "Segoe UI", "Helvetica Neue", ' + '"SF Pro Text", "Calibri", sans-serif' +) + + +def _build_qss(t: ThemeColors) -> str: + return f""" +/* ── Global ──────────────────────────────────────────────── */ +QWidget {{ + background-color: {t.bg_primary}; + color: {t.text_primary}; + font-family: {_FONT_STACK}; + font-size: 11pt; +}} + +/* ── Input fields ────────────────────────────────────────── */ +QLineEdit {{ + background-color: {t.bg_secondary}; + border: 1px solid {t.border}; + border-radius: 6px; + padding: 8px; + color: {t.text_primary}; +}} +QLineEdit:focus {{ + border: 1px solid {t.border_focus}; +}} + +/* ── Buttons ─────────────────────────────────────────────── */ +QPushButton {{ + background-color: {t.accent}; + color: {t.accent_text}; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-weight: bold; +}} +QPushButton:hover {{ + background-color: {t.accent_hover}; +}} +QPushButton:pressed {{ + background-color: {t.accent_hover}; +}} +QPushButton#secondaryBtn {{ + background-color: {t.bg_secondary}; + color: {t.text_primary}; + font-weight: normal; +}} +QPushButton#secondaryBtn:hover {{ + background-color: {t.bg_hover}; +}} +QPushButton#toolBtn {{ + background-color: transparent; + border: none; + border-radius: 4px; + padding: 4px; +}} +QPushButton#toolBtn:hover {{ + background-color: {t.bg_hover}; +}} + +/* ── Lists ───────────────────────────────────────────────── */ +QListWidget {{ + background-color: {t.bg_tertiary}; + border: none; + border-radius: 6px; + padding: 4px; +}} +QListWidget::item {{ + padding: 10px; + border-radius: 4px; +}} +QListWidget::item:selected {{ + background-color: {t.bg_selected}; + border-left: 3px solid {t.accent}; +}} +QListWidget::item:hover {{ + background-color: {t.bg_hover}; + color: {t.text_primary}; +}} + +/* ── Text areas ──────────────────────────────────────────── */ +QTextEdit, QTextBrowser {{ + background-color: {t.bg_primary}; + border: none; + border-radius: 6px; + padding: 8px; + color: {t.text_primary}; +}} + +/* ── Scrollbar ───────────────────────────────────────────── */ +QScrollBar:vertical {{ + background: transparent; + width: 8px; + margin: 0; +}} +QScrollBar::handle:vertical {{ + background: {t.scrollbar}; + border-radius: 4px; + min-height: 30px; +}} +QScrollBar::handle:vertical:hover {{ + background: {t.text_muted}; +}} +QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{ + height: 0; +}} +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {{ + background: transparent; +}} +QScrollBar:horizontal {{ + background: transparent; + height: 8px; + margin: 0; +}} +QScrollBar::handle:horizontal {{ + background: {t.scrollbar}; + border-radius: 4px; + min-width: 30px; +}} +QScrollBar::handle:horizontal:hover {{ + background: {t.text_muted}; +}} +QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{ + width: 0; +}} +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {{ + background: transparent; +}} + +/* ── Title label ─────────────────────────────────────────── */ +QLabel#title {{ + font-size: 15pt; + font-weight: bold; + color: {t.accent}; +}} + +/* ── Sidebar panel ───────────────────────────────────────── */ +#sidebarPanel {{ + background-color: {t.bg_tertiary}; +}} + +/* ── Splitter ────────────────────────────────────────────── */ +QSplitter::handle {{ + background-color: {t.separator}; + width: 1px; +}} + +/* ── Checkbox ────────────────────────────────────────────── */ +QCheckBox {{ + color: {t.text_primary}; +}} + +/* ── Menus ───────────────────────────────────────────────── */ +QMenu {{ + background-color: {t.bg_secondary}; + border: 1px solid {t.border}; + border-radius: 6px; + padding: 4px; +}} +QMenu::item {{ + padding: 6px 20px; + color: {t.text_primary}; + border-radius: 4px; +}} +QMenu::item:selected {{ + background-color: {t.bg_hover}; +}} +QMenu::separator {{ + height: 1px; + background: {t.separator}; + margin: 4px 8px; +}} + +/* ── Dialogs ─────────────────────────────────────────────── */ +QDialog {{ + background-color: {t.bg_primary}; + color: {t.text_primary}; +}} + +/* ── MessageBox ──────────────────────────────────────────── */ +QMessageBox {{ + background-color: {t.bg_primary}; + color: {t.text_primary}; +}} +QMessageBox QLabel {{ + color: {t.text_primary}; +}} + +/* ── InputDialog ─────────────────────────────────────────── */ +QInputDialog {{ + background-color: {t.bg_primary}; + color: {t.text_primary}; +}} + +/* ── ScrollArea ──────────────────────────────────────────── */ +QScrollArea {{ + background-color: {t.bg_primary}; + border: none; +}} + +/* ── ToolTip ─────────────────────────────────────────────── */ +QToolTip {{ + background-color: {t.bg_secondary}; + color: {t.text_primary}; + border: 1px solid {t.border}; + padding: 4px 8px; + font-size: 9pt; +}} +""" diff --git a/uploads/041d292d-c94a-4c46-b8f0-b4ac02536d50.enc b/uploads/041d292d-c94a-4c46-b8f0-b4ac02536d50.enc new file mode 100644 index 0000000000000000000000000000000000000000..d9f1f355dd494c604d5ecf43f06c1d7cb493c598 GIT binary patch literal 86523 zcmV(nK=Qx!T9D|;4rwB{l1ONsmvM?c2*C=ZyQjaYx8~3@9Js39owAMTKgz8DYEEph zg)jF3140-pqGy45Hxg|KG}KavnhBX+-nFND8~tqzjudo+B>BZuAEH0h7qtZxDTqR^ z39=B147b<-gZaM7ziTt*FF}Y{y+moP1rxhYcNUE56(_@uN6Yb8!aDeDc0v8m9=JV~ zx4t^6tQZpW)dclv=js9YsT3X(Lxj4hu;_eL7IlY;AlZoAygA}OtX#B9c`p1Au&AK@ zI2NTrx95=U1gP%}7H$Odzu=hVcDQ}(>A%GIN!3RtY3x{4L=7*DsgH_O1bhgQe$tlj zQ-1%~74$gT7$a9{BUppa{tN`^^?GIL2=ziuxb5c=^N)0xE$p`d@NK4gv}DE;%-Mop z?!t}B+rmZ^@W#9gbwpd@*d^kfX~~#ODciqt2kSu)tmMCiL4{u?)T3Cpy3Aafx{~@U z`j&m7gv`Y$ah&pmjpLb=yXVhL0q_dX1VZuL(zIg%O6HZh#&EQb$^%y&rjS{ERD+e) zw8!{4Sng#sgTP&FNa|Rg^Az!g`MPliSoNq@(c5rJ(S1%dh9%PwU|39HV#envmpKG= z(sp}mZJ$elv8fkAeTsUr;ss+4Kg(`CxgAKPO4B}WVWh(h4Ol5_mS;Zh;(7s=d0e~- zW&sqc>+BpMM|0{EN*PUEMG_nOs>;Oa))49 zGv)xL5nf)loy61-tRSw_8BDN$V{8#Im70uPoLkK>0jsmZ#l@4*`$GtGy&N%{b-buP z5c_9iwR{Uy#^fnLx`-$FrZ6+~q>9W0=wOb$F9L%B3iB=@d3}j|GnP!8Vnc2B=kdMw z>I3)|2YHrnI24W7Vt-_m({IRfo5vQ>4>O z-soI{fMxtbti}Tdj^MfJo;5hHXl4xLp7W;f$HRhPP_7={xjXpubS$;VspB1Gm_-Z@ZZu5qTWZqfJI`Ydk^!OPYW*#K<-8?hl zsKhouLeHL;Rt2vR##oJ{W<(>Jh;yJd0cJo<5}H>T6h}FDU-tf9#(|4Nqk#muO4Oj6 zg0p?}P;r731<-rON5>=F;oHd)pcs9FVS}(+FaOj|ZD~FKMXxfF_#>&*ZCCgKspuBv zhKcK~C%;H9Ydpv#9nt`J5w12hJb8a*FR@R!Xi_vu{=vIpYoe4v;K_T%m7L-Soz*Q9 zK?FI?U+l3~L#|K$Lr0)e6|eM${&>$h3b!jURM8@TiNKI+IA#kA;N6J$|GR{>TC|4O z4rdr}!!`ER!=Gx*aq9fsGpI&s&DZrlk;gR z21h3_DR;!Jyx0@vrmp@S?oyyBx>3!z(b&#`>dtEoSgW!4KXZk5p*J`)_ES6HP`Rdj z%(|BGbB$GUgE^)ZsbX7i`UhU!0ny1=CCwbULl&rA6%(!vfe4M%F-<_UZ}plw_lV3f zq3`w`#8q%LzTtPKg>NMhzL%%ANEAJYPCYg;1ntju-RM+W!BZ)Rl`$_*-OE0pgdf8DdW5B`H`Hr}7$s%R zDq(*^2YOD6#J{O^s!nKSoSPGDj$uj--}x8Woiqn&_2k;W$Uw%nI~oXksP3_5CK{vj zSb~)wtEDz(GAiMSQ>$~9wTqKwIUb8qpAqTbFSx-2KdPo_b=A6Gk@?c1M_?N{-8gRw z-&dddUE{#{eN7c)=1D*J_S?KQMT8Km4>M22P7;Edq$JP@wqIN5YhoDM5B(=`4z@V*_;uiXgQm0@+!uy7ovmQPQZq9Cx$Uj8+5J^wq{DOH_6%X~G+gIJ^g<+s+1vsnz%$;n=I+kr5( z>+OvT21)urK%Jv%nblB?sjEuX)y4i}QQA2@(0Mjr%g9JLBIg&lkft>*iy@@`7b4fDS zo2LusiYfaSH3s_dx#VyAs-4dTIy3_yA_%CY8IeDqhVpId2K*Qjq;ix)2W+{Nv;5Go zp0i^is$XOO|^WzXTImlAvn{O)pK#he@1xk<{+v zHbrE7h=&2hZQq!!J1FJkiN9cX0gh?vLt())(xfk(=%FL>=iXSM&_ZBJWKs}I)mb1&9nn?-BCXK zp!Yp|5KN#QvBs1BF|y4e4OyuzG4u{!cc#aVzSC%@Mo{ra%p4U1RuT(Ux2)}rxy7~}3ZO(rrJ8ju0zfUiQu zlbh)ZbPuA11b2gflXly`w~~WGrjUh7N zICO8`?t3B0DUmpObqR)j2f+l&8ju}l7$OSWs$-`gCo@SJT@0Ipg07sO9PRCrU4m0$ z*mglz?ZhsQ>A=+VcG6cE_wdbO3VxzF^?)Mu9U_M9{tI9 zR_n5~G^bt1n5U}?{wa9-VU$2^# z-}2EqUbG(l(A<)ij_#?KVAu+BQX_9cI&UGY4c^Ep-B=FSPG1@(9Kpe?jzhspr zn-!7NRl~eLDHM`Z6@N4Z>bA<7IowhgandE}dQ+EL#_0{NDRtX=KSlIW71&;QN`z$6 zC=lZZnsyz|!y23T@w9Os@3yP0FXz&2V$9Gu8zDE2h4@EWBY!gHOBdZeA_X=ZnJ}C@ z_S6~H84FOcLx?5f)6=E#wo3f`f|vqtt+K)54>IXB(*EcdzC(PyZbvM{C#k3)E$5Y> zISpMPf(6gBrv1pW`Ah&kB*CR?kZ{`(C%AU%_2jy^DkzZBGH8x6v82RExc|!DTDmS^ zFy_O23L4O39zk0^-`z&*2CdCq=Xg0pN}gO?vn-Yn>%~^l=RHH|XFXSX+(o<-HvLDj zbRlu7n1wt2$1rvYgXO@g(>^)o+b-k<*eh2*CvFmUw!~TCSddue-?}&#T)RuJ@-ULt;~zaz-4O9rT@=XxRu9XlbImmNXMY0Bcn z&HuVw_&an>g#CDc&98ob7#&(2=~sJBq5ZrKy8p*Lm{PFC*}7C#I2gP?YKYt8!Plq* zxjkl?(F{7`vWjZ(9B@9}$K2+5eSY7TfY`0>kZm{yo1n$yrv=A@#T%nvY9=aUaVpIl z1!q?jK)>T?0IH4Q;J>eki&*K^*qqs^-<<0)$jn-ZgLe|W&4I5Zxj{DwA0udz(=$MB zqvdni;AwKj_(W|V}c)n*sA>Akh)ALiqzF69=q>a z?UVnc==mT5649_^fc90*x%5p+YS^Uf=nmJGZFaPbmLo>LNx#+CRBEp2utd@O{cTdE z7i}6mBr(o!eTp&H;4!(J7Fc)9!^dlL50hx>2DfEF1a_90Z1DL@Kj0 zEX@#C@`v7lbp|EtZKKnc>I(EW3^e>1TOu!31(6vdqy>F2;HUb-Tz&;c%Foj14PaS0 zD8fR%@QMGXuG8(8jPL+qv)6OXm5xz9N&>~UlurLcnnln0&d^*;4&dOe)#Jh|5Q<_> zt;z=F*a?Bw(*jWkyeC}x@QRwFhY-7}Ihp~*IZrOv<{0n?C2n+Q(la?lXP~JOlSczE z8<&ytL2knPH(bQm16iuS+=|a?)Ju(tp|#tScAT2}&-$MrzX%SC>XN|W07^q_4>J}w zU{fmco6fXKo#ow${hsDA)=N=TY|>{8FUqjz)mLAq3nKO5jnYifGDjc)IT8tCqH# z0-c_wb9%a@}ge>xGJ)*cQ-FTd!>ZK|&wpfJ9cJkLDEAn7OwV zqkV`>nxa0$epXL;GCsvF?rLidN4>g1Ocryp8QUetB$Nkfd|gh%V!r%&nUu5ZVJ)rJ zeaXy=AEcV4{vXc+cXnBMyUFMgNJNC8rSCb{h-pr3h_jXmqa%{pEZU`r1>~5&_JFL8 zM!+nDB&c+cC*5`sh!6MQuyJK{)-6BeWq#+u_aA+p2}T2ije}4e{xnrJvWX?#FQ}F7 z1~GN^i;zEi)9dc=h~q zhi*;ygOUH=?Z&^?+_x=mARfg2=0Opr$drtGXX?|#UpDh{XI6Z4xyrzmXxJE{*VE{^ z{r&ikD_@cQ2t&~T8<<4O%x7{r` z1uYSe1t>%`YwvnUq!L+wUf-KIGL|~C1}xnnB$I+0u(CC1j$;?FNDHW)iUD0GygHEL zETgw}`(ui?YQa9BErmCe;X*=O1`E5?Su<0wmLz$K%lc!tLmcL4? zn@(5uD`6yPRU@>p2J0)F_Ame!snS)*sRsPpco!sBVp@%;0}EtJ-ytT17>uNBf3{zi zSQv8-QAfz75p%-MrLC~@83k&>9cz=0c*vzei8#=(TaBduL4fTodPC0IJb{2M2m~f` z>Ap)=V9SQXSQ|bjll5PlQ}UT#8Ki>Zu#55Ug8}e1TU1T*AEB4u3m{?2BaWu`8hmDD z@yfXQo13TJ*M+bbg%MNV}y?i*y$!LjK{6{nvAoiIpB<=DxcjKAc%PzyBivwk4_ zwcm-PWQ>X(N)LT$QqdbX-LMnMJS>az}*9-4>?$BPi>Nh@WqFc|*L5j(}y8bndH)wgAxvesOCTA{{>&DNpg{tCy>scBK zTP(pSd2y?`WN>oB3O?0#cQ$CRwH>-xG6!IZ>|)WBx36HC5eNEU_S*>Y8LBw*5%LV#jkcYSS!$zj4Ync650_{9iLDONAqP((<78H z3EJcI1pENbRXL97v;v*EkrR@-=hnkX2FhDvrTR%+Alw;>^X~|cLTk!e%*&RBXC@Hy zksGI%WMkOBv9PYYw_c~uv=EX?#b?Vq*m5tT~yffnh%d%xO|8ErMw?M3~a(K z8)hDbPh#OQGQXcC@njUO_zWFR0ztSXddUJ%9WhN!E}TAQZP>e(DKk--**V3P81_eK z>HN8*9R02NYFBR3e<0c>xLr3`*9+W98*9%Y0vr}!Pc=7Jb*f7SnG+_|rS=P? znMAi9RNA@yGJ&mN5jhmi|FC7K{taaSa(grnPsY|kJGP|i4(E?zk8>FNoHTO_Jq6Wr z7_v(rtQBGS+7ARBu5~Df|Fjtg0dQhDSrZs^w>{~as42snJ;7TXtvwye%-(0!27!*6hF|WH778vEAvr(pax_o_^%Mt1-{D~ zTg8w&Zl~ykM0aW7^$UBuPo?uMg&?Csupg2|WO0gN3=zT)h8B0+%I{N+b_waGx%Aoi zoNzxdP^`^TGq0R5&t}8thV6|K8uOf6sZS|BcqG_@4;UY<9n2`1zTvc_8mA3}i0`9_ zwDMwh6f#!AAts_}JTbFkdPY^~X)s@^z^w)zjtdzNa_4~;^RwZ??E)*e_{>!>6s0^y zM3?o^oVnC+jf;sr3mtXUjm&PXXUi&_@;}QlN@+ZN&8K7sUKY)&eFeoRfIC&_LM%L~ zmB1K>{vt1vBKJY89e^4q_o>0Pi{>~>*W6kFq{>eEqW{SdKSHCB5Z=~Vn|^meoX70c`EyX?y&ps6LXnT zgm`dUY_hov8jp?)Pm*=VMh;{vWu;iF1As~B7CUr`$=hUdL2(okph9$OD^6I#g=|=bhMFl z?4|3dA^H%(gpAoke2MCBwZ=d*ph1m^%z{T7uilR!DTLDnS=oE%IBQA=s6S>>&kd5? z`&tPx6yKi<5iITFLmj@cr_VxLkzWQoyn2OfwPnkvY63H~UKcYKl+?#AkYB~r-SEJs z%DD@KN>aEX#F%I|D8Do*^L2$c^UPcF`{wGb38yMBtX{=YG`|b3mIkkt0lNeJ5?uB( zQee02?NVf0@j#f8SD}O3N4%TlYV%JMEa?(yX?0CQ9g*_^b2x-x>izk-MlE8X{4i9u z45MUxvwUF{t8;uky@jm`K-4Ntd1x?XsUD(-c-0O{YJXq8Y!=zKOCxOty7vw?a>}XA zBb2^=7DDWc;pwhu-Q#5UPbL0F-6W9BZiV! zb%(Iw+3@)B@|KhTQpx2OuKVM}x7Xdi0Z!X8D7au9My8Rlg=O_Wnn7eN=ysL}%b{dEF2DfvSAfvdyYOWdtqTUJ+$@kh(vgZo-;*BHWuW>8dlW9#1WG;H zq>S>J*|?9qjRFqP5+~v6$GX{w;Bq)6OuYG7sXgkC>*MxX#`8C?sQr)KC1p?gz3>iV zPkUXou~R_=yI)%(!PI$uzK8&lT!6l^J*tkiB8uVc#aWY zjt~9@0R+65xWu199=30+prOloS(@ei4M3`3$h!zCU7pZ(zLxLsY2H#^cyW(BTI+$x zK6P++fRn6(^QqjX%1%t1-hfN9A00@UC6S661D%`u9UbHR+>FCYSQ>ZzHK%i_llTe4 zIBjT7-IHH=iI;MX=|wIEk$XC%**L=hkVWgCp<=wUK-ecLeu?zGx6=t zWdB7j(AM@#^ec1wRA@N}tI5hDXN+oI~`ggXaH_$lO@HQwk%@%fQS0uKT;bZ5kRaO@(H zUU+O<0{!lXQB8uLDEYOh(VR%fyC*f3SH*v`SZ}jHSgFaw?cv-j&Y^XBL^uxiupY0` z)^qQsifLQ>x*fQyfuw0C--%Xw3hhO5KBga;`3A=kJwC{AQcTnn3dcNJQgItW3~#eH2ZtR=qJu~^+VL=#ia5r&bq&GBr%oaqPR{Qr)}m^R$+ zAtBGmc+`uxJel@-z0N1_kLxe*8_gMGv+rD?2yOYeJv!_~z+5t5))2*1Lc+OtGCm=n zE%^#*(-HAxdYguv)-V_;m?;%rkD(|zFW$uss-?6`3~|17cu1B0&U4e=B^B^LJa5kFDzV=;`Eidk1H5JGJj#D^NSDm1rL!Pj4W5dw z%^*q0j>wd#Vsz2?gpz$)3U2MrsCcu{c~Ik`W>~rZ9g=kh5LlKJIpMy}8XBnl^oXqq z1ZRvnEB|WBsg)uQQ@@*W9w8VLcWaV#J00`}$5x!HblGhxXT239VQl6Kyt$7QmHV6I zliNgx#7huGk@q30Bw2vDDRHT9QVIDiFl$GtSfAd&ZYHPBSHMNzPNE6av8?V^VkFR8 zVssN}8v@#r3x9LKMgda8Gh%kXAqW%$but5AQ%WrpzZ>R8 z+S(g&2^X{Lvl@S&mTgt1{=>H-HZl}jw=%r-`m~LzwaS`19L7^}z$YmW7rGuq~Wn!5~X07QYj@NEc!NMdO-jC|x zJ;~$}0wPGitQ82+QTMg+eCsw#v)e#}Uqg7CNkH`*Ln9Vw_xNC9aR4zX#!fj8uv$%v(dORo zzg~xM%_hj>^>{GnAlAq=4qFBQCFGrgDcrhF9qfVWgl4UpEPgIp6N3`%9v-VqNqc|$ znKR}nCr}7hopr1615qcjg>W(=9%6dbODq|f>l^q(m84aV2xhjIRag4L-+&Sb95=BP zn5vD%_l3NcpXM5UNPbWt1=v;i1o}u;06A8^Vr|DK z;#QOu_-=)bIu9bs;RI4M=g6APQ?1rF(+9$dY%*koS=ntPo|6yl6IvfQ5=35$wjVLQ zfX3%lySB8nxoN=Xn*1bjikw57g}xw@gkJ{s{oqqf)M267^m35* zQ^X)9FI|5ck)NbPxbT>8D_7}k)#?|(fvIFsSUc;FsKS$|vdT0vr zLRLs|NU7h7pARS-=WUb5QJ8j_meM3z_k_E>^7EH@krq7#z~;7B)8#CzBFB zuLRnCYt-LArr8rpa^MYh2}^o{p=h38u;_SJS5U#33fPrpsbH@_3c@d1w5qv&Z_)jr zP8$cr$jg+|t@Up%f`GX)kL{Z+$oR6x%iEM+pbW~(6)GIdm07lq3+4Zg9Ze|Xat=`5 zFhc$89shY)Qr4jsQ;A$*a8DpSf2M1!Ct#6Xq7eu{T?GyJk3y!VO;9pfkq@k3@5wtV zWZF}5Gd?*3Gg<>>s;flu%Ly6AeLKfdY9~aJN zyOAw6;ktqa}~)sXbJ2M}5mp zV<|*Mpg>=CZ~Kq~qhW%8}+rTPnn5@t}ddt67L7G?}|J=OG2S zm;-b`#YaF)!}ATLKiSVn(|x;*ftgIq zfe`ykb2H{146~fD6t^2nFuq&DEkh5E`0Sr*`K)b)h z9$A0Jg?=$@+I$QHf|YKc2Nvn}x%*wtrdk*9V6+w$^xfRV`Ou*oCj@}`9Kot>di#&h-w62|pKnS;+OsyXIA~BYx#9BOedbH_ zwj87Sm@VQ5+82~3yS_`($SRZ>&zI#Rq?4bcUolN)&5I$Va7W05bmzGfOHv%%X4#mb zm4#<&r-Qd#DQIic8880yA9V-2COiCim`sc@PM5avD_M-#O|A*om!uvSlLA+0zPiYa zJ9=$JRiZoF+|!kA1*&a3X1Voru2FnlW79Ws0N5gN(GG;EQw}FZAK4^i?6D99Sh6k< zpdp}uUYOwie`q`j3O3&ldf2{BY~V{zDnFt6#j4=($T=)OA&w_4r*wl1+ zs1I}-QOX7dY?NZ?O)c%v2HpIN+s7I7m*p#9&1d?u zRHObU(0u7R^)gR;_W<8Ef6W%j*Qv`z(9L$BffQPNuA7aNMTL(h3$d)T9&blHFQo zM7+b)vHmEa->D-t77fqPTC)_#(ekT3K{Oj1Q=9w_E}7R!2&@nv8FW2KJ#KYeYBY*p zG@&Qz;Y}yu9KuAHXJ>WjwFeiwKWZZxdyyWjkd%e#e019ip~T*?L#q*^Pv8`s>~s9e zzRPDnwHXe0$xrb{y`gdH@EC9NpnAbPIbq3=iwMvB^N4Oxdkh)`SrzB*^yqRBzfWw@ z{T_8xKySO}-cnQTi8Xc3DM3z8t6xftxrm9CARF0=YA65?B=_jD^o!~?_~vi`#yd&c zn!~oZ?Ky^V6Ew;LLe`MoxCRy3o+360%>JL5(u=w(3|Q>wlYMmn84XTb*Q{dfkaSLq z(;}e*?FLQ{I|HZN`CIli~KWU(3DIWrFom;{e7= zyMMs5<@`-T&V^or@fB}yTiLzOM}@s5WX{sWG=0Q4+~6GGvDY;M0dAj9O!c2nZSWdw zwo^vN_#(MnqCaFTCTIuD`NGLXf4KMQ9I`-MwZ&8JyP!T?&-V^J?!tEv5q09u43)j6 ze%otL-&jwb)c4)SqC@ac0k7BL^H(c|=9pE=#)Tf|_Y!_Iq?^JfS#{S&V&*+H8OjCCTR*g%*&K?e7l%TEy5ob>k#|HNeJ-7@wM$)^Hy$xkvRBwIm7n88_rzki`6`=p!J`Q^;Z12T?(uK`lz8ANtH;N%t> zFLkhLjGDsAoIaOVNK|t*#~O^`9sqTskAWh|lrvve6R^Eu+f$i+a~&!y59e!PDx=l1Zv*eBPm*d_K(;MvlaVk|ha@aoPFC*s@ z=ORVJc~0&@3l8l7harqVjfBH6kh4~brYW!UcGR3)?)rdgfgf z=CKHUeGoppFY}B&@s9e7|Lg=LlG*q3AvtJ#w=moioWM7y4bV!{@G3~KG*EO}6Ager z#!}Dnc^eIfIO`E+@8qaE>({1Ft2np$vfbnMpta0!Li;Q&Y%@@Aty)K8`XSSBC9;eA zPJ(zlfcv=KRaqZA(B=U!tz@<^hs^%V-$vz9=irF$+^!)>~}KOL4grT*i%AQ##?!0D6B(lV|F^j zb3YoO#OEla6gPA4-4!K}HFUx0$XXy=Y*N`5vda&ahi;_>=<~D@V)_hEK|zQRD!q+v zC`kP0gTpg5eO-i_v6}q|CUSzuxqocNEnJ4Zfl2#X;M-+guRCh(72PqNlGZ;PER9(N zh&i*FP|*(gs=~AzH^paml-S`*yH1B5gZ~{uxTRpR@O^0S{Nq%Q)xqqxrj&2BhzOU^Ryqm5riA#+&Ip8RYkzAa zV&rJt1nOvuy8w;N+l-N61k|54vg)p{>OR_nyu4E2pc=#it4+aX`vsrfAkvG(&re`p zk9WYZu|((ZGhRuSli3XpBe!fGGulgV!P*wi_`6~s-3+eWl;_*K+`G&k1Z8orLbyu# z{Nb*am5QBeMw#S^!REf;l~l1gR{&u`nd3D>K_95RtDwBN10pW|4k@#=6Sm^ExWs*>YBB z{kYQPOQ$`pOCq4?Zm+v@lkyZkN7m|^2K&l{67FeW>18BugwLxp-O9Lm!)f0R7KNU^ z!0S-9G-u28>I!-I4bz$cDcG8Qfe>F?{eAkB%~`N}BW#sReK}fjGg__P1a=vTk8R*_ zFFqNhR@$R4GL#I`b<1vdV~SPH;~YUy{)7uUrQITirt*=;d1b*i2D=abyBMjKI$Q9O z7s)l`S#Q+axkGp*KAD~#2<7i~>SAF888S*Tf0&IV6JM*R&RW}k4ZiYfk-aroR7C+@ zB$!#p308CKYLJ%)bhGRtkd7H*NE%U7=sB%b_4rvBTipOuFNj8}ck6C;TRpopB$qt{ z0Fp-elv`A7JKh~eoyEi*=rlwEEnH4yq-4bk?Z{=fj}JffnUyV?(W(~`eh$|Lbr9zcSCGRZq$rA-ocL+3r`J%-YqK2>OzUJ=)v9lNC85d z4CW0RZhX>UtBh20jPef^{7r;4=Zw+O0T&PIIIt`BK#13NE$yDMN zWK`(6%JQHLF_5cmlQ6scQ0bO+)F^}onuu@e9p{;mRt0bPQk<8zzDp4me*Y*RA{ps3 z4feQmNu(qc{uA_}Suh1YmWherers^&+LCJEOn3dD@SjnbC$+!VDmvnyiD7*=#aWpv zCK&J}iJwDO*(|UuJ%4B#D$)W@8hji!pi;8 zd%{E=7`uh7Q|jexp3MHUMlri@!9;*=9Gt&K4cia}$*s$gwCqe4q+T^g$%Jg#Yz#CZ zR&dtd(5|VI1yK<(`Y8!p^a7VTO|_67MR#^5&Dn&{ zp?gu^HS50`Z))?)v!0nLSuGaX=Pqg)e5Jr8TwzXr{g|EN;Wk|6ADFeP*|RrbO>c9% z5=sZ7!Ki3DI1h}eAJB)}pzp24nlI3ujfdv{Qp-v_CI6GsWL!Kly%DsArw%z3-jCB{ ze_5NT1B4>~Ub?SGTGz8^uijQE@U$U&GMs(NlbVoRdlxg;baIi}%xPyZKEf-VJcjgm zZg3|Q{bh9CCH&638&FB^-_~nX$AB1Z#}W%Eq*`WRr#07-U;!!Wz>GI)!zSsCj!?wE zHxR1va#J~y%ArwV*OIBz)Rt|lAyS@Jx<^BY$z=f@*;xdFJLH>h)9^@TKEznJsN12M z%b! zN-&{wW6q3%sNkt@)0=#%Jy<=Q>7$OKUq@UJyab*Oa1DTn06z8<*GH#JnCgLC)ZAxI zy)i8SuK{B%_lKCXdS5-^qDC^%@#Ztt?>3UlJ)S=W2+;QMB{&GLg0?4P|7cd6cWQyw zM-Z@~f=#EzeBIF%-9m(>{Q?@zMj0p*+0jKNPS8RS^EdqVXU0m)9Q08DT7fP8($x)WQg@qu~zBRvsL1T3+)W0Y;knXer3U=WZ;Qd;SBw$H{aL zT!FX%(99l}l#K4!0s-VT|70R>h(0ov$X0{)=%%s*m&x;;Evc$UMo?(RFJCUgwYJEo5M6xG4D5QrLgLr z8l7$_Uww2N`D6;ABtBHyH&^n`J*V&(1#Q<`6t$$@ImvSwP2{YaMCrdZ;b}xrEvWkG;md)!vLe-ID69;R_m(n{p68XnAET zrN-zeFca1yKGk&S>y@RL9t=zSrjjjXnU)=1khIo3d?}Wtvi2^(j zx8b!PZ~=6P0p_(~h|^r1fM3CZiWEGF@1jL_c!7>B93-2V#)sfk2EB|xSdD$YqB_+F z(Pf1)JQ-;VI5Mj2a2VjiMtSgiOb)+tJz2sf*?*`Re-6U3It*+kfi0ulHd_sC zIKru+Hyi_3{^-qF&k;Dg^c3b^w%#PQDzSrIA|t=Vm<6*noVe$8nO9r3%5IXR0)FZD|&a+HS!EZm4X80H_;A9 zInzX^8%JOQC5&0EZ|((|Ny<}(>yM?EMp|9n2prZJwI&`1cZ)@Lk~klq*zuJ+aBMOt z;9rZFkI=-ykMqgnh{=R`nB%S6T>p!~Dqxh+>5%&bC`0{ZGNDBUv$b8tOmMfCGYW{f zEZ`IHrf$C-ewf8@!`0E852rdz;JgSpe)+}&zB^fN#6F_ND57F=10!$lOtOgo5t21U zp&X7SWSiMG(Z^_3^Ih)sqPlUQX7<*%9R10kxps`=5{;k_z=nVuxPT0_a$Ej%GVyRA z$;Eu_@*p%0wHxJl&o!u+4|R0-w@ACooxZ;M zDReu3sB?m{+Q-bT>vq73@#apB{4)pTO}1vPL1h~U;KMZ*(F$#)H)QUi56ogfS5*=I zB=hHwpWVBhDk2qIDvc{aQHSvoZ)b$1(ZYL-)iDBdHHBdP*d7ZM{a#xNmIaD2R zj<>reI#p5!icBm@0(ZU(_0cShJ!S=@{-Y+Q$9Cx$!ZC8cJ7{z_OWfo!tKvdu4_iJp z4SO+xlkPneqc*0rf_2MKE3yt7|F!zKB#2(SmGimvHL)2VJC7vdP*J)6PDeq2-;ajs z{~rlKxxTS}KBnkm7J*M1ky_gJg%fT%vAQ^iJ!M~C%vjv*lr2Q+@Ni__pnjw5o_@ufD`De{wP{mk-kEe<|1zhd8p zc!YA#mrz+tb=XZ!oD#Hp@umgEXa-TmUs>_lHUA7r-E3s@*IjFy6jq~8|DQw?Hd-z{esz; z?wyGkq)k=4WX!XS>QB%o-ggmCnZac#qj!a6H6`Vt=O!yRJN0^7n!MXRxsTvdM_)RD zjE)d1cC0i%gY3Sq zyOjo+`W&7s%i|#TNM!tfsH>kpLJgaLo__oMju6NPAz;ZTPJ>t>6{>HUoS#5ej8zYCFBpPVFwK-$6KL~br-xC`c)c9 z2mkzsO@8(hVJSu5zdiVBVLSinLLb-`*{q>qRV1^3ew>w%x0@vdJkh8>$<`c9&$Wuq zdt6fQO{;^9?ZLqak}QixFLdq@Rs<>_qyp0>*_Fy+AXE-M?%CjDl+aye0P_Q78L|(N zj)lXB{UqdM8}rDe4deXlc(=t-sK|?l1M+pTl7HlZDK+X56LD74deu*0D;Gd(yN@6=){S$=&w^3&}}v$aZ3l2OXLi<1bF9+z37a8`E`~O9z!UN z-1-h|{Ov-w{x2c|icL?vuUzy(9;A#9Iv%E!NPB+?#YH|0Rhf6JZ>Nhho|Q6iTKQ$? z4Z1<@>O2x&jODzX%xc99M+*t}zXM-V9WaILXe}PY|G|?6K~1%Pxg`O793<8!a0a?} z{F*bOC^o8cH79(82iAS=bA5T^o=w^sw1hI5@QG1MP zRX5?Sqj%yMFcB+Y%cKYNr)!Hv3l*eEIuc9u&bfT{zpn~8Vyt>J?{;W=DqO}SoVMPH z8K4=ZWm?A?_84Qbdk|m!{I08o=t_Ak_#QwH;TY0`Nk-64qyverfd)Z%A?J{bxSq5; z$eboYDW&bSKY+#j7t?@%b19Jdfn!nw04qS$zf3MZ0X#2BE?nRjBwHBH$l92>^Im0_ zhHZ7S4uG_xf!GJO7|AVkyh;+Rq12HSOtSFgv!v7%lFCQgsml?FutR5R7rBY*6ORAU z9q+==2DeXDM#0^xpT&sSe_&P2FNUS(AT$l)N81mVqW9HL=5P_mf^W`F;ctL``+QbY zIIcq6SPMt}|MO)>*&G>AP2DKKgE4qOYbBi0lYZ80DPAc%w(IINfqFPteM|qZ0O102gf`CDwM^oE#xl8*QJ@D& zy12iCj5fFmBE#;)ay^dl>ba+11D)MEB_xe$jN#_7ku)mE>gLv%hl;!l(5v^sP|kX6 z$DeXAZydHEq689(vN$&6Js(2LhA!9;JHZylvRQ=#EF&8geNZQB(ts}1g`ehrI) zU9O;*&(tUm?s(SWm)~MCHzCR@2CVNGypjX-K?fLd6m3nQYG(Bg@02Wtu;tBjPVELC zw3<9M7@F7i#{TQNYYB;2WG&g#hvF(WUvRj{39f1o??V!?op1$H(dsH7q;U5!`;d>v zp`3%;VUsJ38?qOUvMIPRbh8|_uI0`jPgy8E&JP6PpvFFPp$pDeWi&|Or9&nXxQ$64 zZYWR+&NJ-F1*@lPFGwx^RX>nbtgL^$y68peX#d9vFF;auq zrV3N5V1Unn5h43W$DC74P#VdvwV;D=z1$nADE(PJ=nM+XEZMZzM2jdihAJb2w8ca1 zlx-2GOhu*<)XM;R@jV3$|F)jj^Ocpfv|p^i*&aNSVSyM)H*-xIg{l&c`0Ga z9(ih#3yLhZ1ok9W;(3#uGRNA(fR1uKXEtLI;vd#^J(7s5j<+0Y-j?@t@p0Xx11R34rR(uoPO5R+pT}sw6wX8s zg9ws|plVR;fYX?cSjy-1(xKbxfmgVnqWRxy&WADx{XFEq-0&z=8%w~$A#|>h#8s&RULr_;Aj3PvAM>kxuOgOI7B{UP9MO?G|+eF!C3JDZ|zGLy+`~nd` zlXf&v)5q(6B#2^G+pzuX*}EsfbNonStG0>}ld!oXZ~s0-y68u>G`q4c;`aP9lILaP zpS=yZQ$u?!QuBno_glmZsaz%lo&u5z^g@*ZOal2&&H2IsBB65nA_H|IORSp3dmkhg zQivcfjaGJYw$L`>i#!a6>>8!Cypp)MYsAAwawZh)kmjL#vseqlb~9`pJ`lrzWyNO;o0M$7s8_OqEkztuJpv%X0T^mT2}8Pp(2pw5OM z8CE$SJ!zn=VBi}rnu0#;`3-h~{9R)bw-O|KI6{1Y${gu2nC8GP`K5JeTyL$r;3^$xo^Efnw!$NG)`d517M)AO=9N^zg6Ot#$(M!5eC@bh~ z6cY6=>8$WPYobrzBlZ%VH@s5wCqIJNG%T~u_GK{q)1;q570hCf8s-%F_aS1#LTjvy z{jTY=H&R$3jk#fv0Sc1_H`C!{{3^B{k4c}SG!+(He)&DMkN1opxA(`O~~A-d;4#nsJfawczO*0FnQA>5I_9(EP60w7%7- zXeig9c)zhx9V}XMi0E?c?V=3rsG!L|Zn)8kj^92LO+YWEjFUQ{6SCPD8E}w4svydjimgIZdZ}ZU2!<Z}@>)i?rJvuvA=*FI zM|4z8374f7p_^Bi8Vhgz2s|`AOBupq0J8DS&BeuMjtxYHY=_LAp3#+q_Q&_A3RCWa z!kevja{G{2zX}`?vG8gnZKthr1QsfZZcbBYsP<0=pG}a(??R{1-7ki)Au23F^*$sc z-twH~0buHtez>}%u1=Hxi%C1VT?~}nHjNhb9{8bAiDp-JY~z!?cc?w|X1G>0nBw_X zCGQbTh%S449MV<)Qa6n5rE&5ltfCt*cXh}%qS}%~u5hjQB>*JmvnYgivr2;>Rj^Ox zUc$vHU{PUqFMCOhVCF39tfa-Q#Jf#XBn0KOryyOr_CMZzRMLVd=HFkakCYTNU5dDD|2w^8j=(519E8<}^vp_4p=?awhP1Ygu)ZzV9QBD}B- z^z|x}qp^#brfwa;5 z!3+YQ)*G+5pd{>kgRX89NcgbVCSInJ1MQEF!S7vyK?2f`vz;|be+d)@Yuf3Oo*uF( zNdnBB*a9%FL)>#)@EUZpOyGBV>4d$na*+jh!KSe+zGy$B>AFnhHPK?I`sNtk*s42+VNyDq1IHfZPJs{SZj9p338asZssF0_H z_lbi6X|=Q}B#k+K)(^@0mmmgKiyt@d8HP6tYuWlx*BeDI70DPh7FNL^XRVOm#chxv zqP8uYxm<*k{Q;k`e$4s)@E-`oty;Q_?H{lL?T}mw;4!7vr%{j*oLzd?{`V-?-ty3mlo#RnE2W^BY+=3$rON>vz_H0kQ#Dx?x4KA)4@F3N$~m6G)iup-?e z@LwadC@@<6vT-9Pb#es4PS?W(BiU7Kg_K+)IBgp?d?!*56zoN|r0nVcj;2*QBI;eL z^v?3Ce;!GBU;SFEi(r3L3dFHgbe5|(R)HEi+_=oDTY--74PSnkJ954Gt{*EP9IYw@ znqgWCJ2}jUnx6)+2TjXI%mDyNsuMigcq(lhbTY3wQo^~$Xm3~7U-RAZKy9o$N2JHT z^mR=GQT7gc6M(WF7S80 z8Y>~+y!ICgVbI^c4mKZ${&tCz8WOnIwYuV07?j%R2cTMa?AhFy(+>?9a^PAJ1IXZ%jd>0{H;-_3>rm@FuBAEVi z5a$BosHEon?x?_3%9NzEhrV9wPQ z{?p2RLV#d3(KGwvn7o%dB-H}u8m&g3IARJUTnrANQD%Y?|Dk2mLU)P>- zC%Ve1fgg^x-Ni9S8Cjk$g3VYjP8KaH9$zfo0r0cU+qpXgQf&B}l^d$ho;f7jW0x-w z@%zC@1R6U;DQMBhRp?_^8hXemm*JkkC{KqY{yac`)PrA<$d(>0z1uw&$VTd=RNctV z*H&KiSJbGl1@(9&g0KM!Tr-SbeS@~_t-*-{KR1UQg(6P9`~)u#x>T6b`-ia*)-!!Gj(8lo4BEu{4Iw6zGqx9A}0i>jY4h?O+ieh?wlSo zb#p8z1P>z9?+Y{3OhWFkdt^(gFksln;h@rN`4k&})IeXYWDjrBYbmllF~+cfUOOeN zuHh=#?_=YOs2q(<%G}3;0Qj?R#_-XRs*U=&cSf*}!#{PMD3D}Pt2H&1qW1Mwv(aF1uzr^q-t1n{>$XKe-jDx=S|sMR9-rMiNe9gi8+3}M5!Dm-PC_vYps z3*#ntAy~On%0#7d$|ZCPK`tuaifaO&H0rqCz-StA-CC@~GY1}gnEfrY>#xz!t1%iV z@IDhH;Al*|vwjbyj_wVb7dLK|wI*kZAJIWCp-Qk(d|!90A;-Wf*FuZdD<2hKLF{kD z5Oev#3h~z|%*BT_Toz^6_cfXjZ-W-)A>$<5&2pQa$C4RVP>@v&Kr8Y`%~6#|#9| ztu=t|cLan3(P!2i2C`_)}G&rgjak=(Wb8Ipq@}}23Jw+k{ zVYvdk1&t)Z$$>?+LKt6*e#WA$u!i&T9#B{`t-|GLHRUbzrEl%eP2 zzr|j@599kZV*^D^U-)HaRMB?wLI+CpoF0${CSuX7RP-fhnl&Jy>`YQnUaIIShZR8S z_h_wl44t8p88!YAqxQN5^+C%lv&Yt`#oZJToYh@tlS#Z09D2dQ59(zXIc5#O<#0vi zj{utVZtzE^13Lq0Ab5imj>DN+5vzc2<$lx-j8&~G#NGD`Ig#mpc-a>~g&4CmK#PV8 zF*-&+Q7f&ih?OF2Y2YWsflS*4qh-ejt=&4<{{r;zFgIwk?hwsCX(Qc}Cx*2q*^GL}uWq%pxY4TI;g4J@L!)7QXAwCLu+@?NPCz*~d6 z&SMiUH4N4YMtJm*CYQ)Y(%b|F3iPkB)i{~KdW5;Zdg{l^IQHWuMB8$qstS<(Qcj)3W;eQvzcOR*aODNwjuDi2Es*Fe zDlLgc&)SIXSIrdp+Ikc3(A+D}zxq1zRro(2M9FIg!}$&#cG$h2o6hunjoC%x$Ilaj zX84q=37Y#OX|CBj0GX*wh(wB!`)B2OQ z9>nStXW)t=YCVPnxLl@N=1tUm_v1E@>Ubm%2-?Ddqiq;j1P7z#xvE?-#CnMrr{xg2 z9qrUqBaTxNuE+5SWm`_heFu3MKT4d=878m$aOSoyTH)1ns$<7bK!IDJ?U50;m$Lx@ zg>Z%k?k@RoNGl~}&yjo_wsZ4LC(}nWG#VU%jGC(C>iGNe=v-C7O%{haWBR|obv@R= z{kcFqNGk+6ebE1wCyzZ>L6NAUrwKtxfsAS_3(%tNfsU$-7%K=Soqvps|tEkh|wQ zt@JSG#a8ON_ypwGwFz1Ox8n>NL}+&|LE5HOoqJ@FDNqAqkqt|F31ZJ~=qIXJ)-ff% zXSB1B#<5?KWUOh%&8iI^HbZzE$-DXouj}bjZqX45I(%-?pyj+Ah~qJhaRUpO*tiSEKCWA+sw4* z^LdrtxY?s{nr&+wAhB@4cRE5y6aCjuO*x2x{#NeU+hDZqI0JHFIbjUjnUA~N#R)hH zf0~-lj?#w|(-Z3$_@Oh-v(UP903cIw3;ex#ITI8;(E2|-{asyplBSRf+T6HAQ)YPf znu~nhfz3Kvo1jkdfg(JZdPX#nRaOp6lL>W`7>6q@S`(SuThzaZO~_-3a%oA26upVs z=jMc8>rW`{As2=BkkC`n9o(pK?A?#x`iW>bB6U|){>&aHhPZtYT^yw2Uy;?65 z=?T~ZeT+HRu}M0i<(vE{ zA2;mQx&I~(U7C}Ah%?nk<*B{ohLncuM{=dgkf-|ass1O6L(i-jwPIda8T3K(5Ulid z^Yv&7wzl`NaWtIBA0FoW&*eA;Vb4WkrbiIBA@d4?v#}a{`q<00~0RQ2SzdClD1HKCfc^gxQuqYh6V|Mj8oXpWUhDu2tgKx)1 zP&;0dP#lUrzP~WfGrG|lk4G63C@B?E^d8CJ?nVwo0``SwC5-8e{(lJp9+gAiDR|r! z;9*>53kd1jn4k;>jGe?(D}Gu~fK?jurrw#3UeZEfSd z2$#Nt!{jKy%_k6u4&-T!ETy3_yZEg5AQBVtF>~pdpk)*`Rdqp2mMtW%+zeE$u_d0K zTSW2EE-49fgkA6W^F*5nREJf+YcQJ~v!UtgWRrhjb&1tZjYDML5rGFy`^C&!Q0slH zS;jePAsJh=Qrp0z0WO0Nt;RRm8l0c@bvX?H%jxMj+U%`sjAz&3?#xmZv>Gz<^Nbc# z`E-;cLn*q=pLU|Adn({B?mIS3@}qc4sp{%-t%PfsSV1cXbr6%=&ShufX8J)~~hk^O}=(NwSCfH!vxYcd#aL%Q@=IOqmw4@Z8OU3VpD zNo@S71{HT6?!A_?^Dk;{s~C+sj7tQeU)E0A#BXfJ;6neVfHUHr*|HL`%XrI%A)wKF z%rr)>Yv zP0uyd23{=Be~o8N=zFXu771q>>H@|}!;WI)O^@v$9Htby=X5MrJ}YMSO%JAq&=dHI z3~``DXv!4sIfKVwZO$W+wYTg9A%x|cjB z&4nK%NCKgak2a#AJ@F5@kaA%BHE23=&y$RR%r@Gj=@0E-)1r3A{c;3 z?J{46fZdrrXYuha>s<9{WyJJGnxC7a_WFIPeE!btrSG?uF%Vc2V)|7aL;_vh6O$z? zd9Q$U2~a%5OaAo1I%0~0BF_%SR-$(67DLxmgX@MgksS=Di`^nCK`MX6|@uSk6qN}>H+l` zYwIeK+g8X?0)PChGUs0^kUbLE*5H6_s*HYFwx{M~Lk&uQZ2+h5hsa%;BTS0JVRR?( z`MUstFzfdCg$yjpvB~VPlWMb;3;Dzi0-~UME*1!`zWSHGt3uT~2~C~nT-(uEi%oC6 zoWoNm>)-W-;$&{{HJ4|}$!p-k&PU2_}Ys^p9dfFwt&mbQ8n{lw&Xe(Z0TC3#4 zET)Sn=hnr`1|?K$9T2@;noN7rhZp7z3Cdmhu67g1Gzt=-ub4sbKj|!*DAO4~caft) z2+t!F?GXS)k9HGA{9PS|1M>dRFgEBS=~^i}1rV^i z-);JUpGa8?ZB$(rlAt^dS>ak6#_gIPAvLt7NG))-+ypZ=Xsmc8g`kljL~&t#yCDeDG( z=>DeuUG20?`f43w7izqO(>@5R@;z2m1%S`R4RRo9U3n!@0W|5g-g%atI4O$RIiD2gqc>uYO6 zlmp|xijDQQyz%Qz4we#`jP~rgm@C@dDh-_IZr##$xUs&kvO{7xk)PCI4n?Rj# z>Fz)Hz%N(3x8XJVc7TCPnk(E+O*0ij$vz%R85~M3A<3ijf|Vyds}9d|I?EnFoMpTi zJiqi%#t*g2B*tg}a4ms)QkF#(TB9()Dfk{Fbj zq#}ze7upd?H*L0VUqw`;lLHJJyXJfM^7nT-B*Xh4aD!%MKT*3I^U&RAImXG;zdkO9 zP1N)B%#pgWz9f7SeUvR)`F1M?0c9*Mm{Ax2l(@iD>`p_d-VU!9Tw#fPTSm~o(>F|r z;kY&Z4DxZJ4d>f(dFvCALO$TZhc!eER9TCe0U5i4BvJA*-K^2R+>&%2>?Mvw>J3j+>kqo^-3ivf|tgLDe!@PmQoSyb}VP1K?IG8$uW<5dhtVhL~5PDE| zzZiiC?dg~-xb#eJEh~DXMW0I*%^=ON=gdiLo)KP)?|wm3CLmGrKoZdX;)KG8A`*h4 zcUQ$&Br@?DXQb`sc)Z*8RaF&O$hP)JF}mz|+lVR>fwQ{yoti&nvIqY#!@ZIE2xU$z z9K435Gec#TJO*C3*^FEO@#dakG~$-fNPx8agutYLpUF2H7kOkJzDjN|dULu^Nh%a@ zb#j$8Km+p>o>*bvy1i2GMQKl8nW!t1Hvo+(+@Z%9|LxxMi+#8Avq|4r&uAG)1|-Z< z(gk)ZU@)?PxfIeOdM^QwE8rtW3(&YqeK}~z68x+u2r$<~eIvVJP;+@Jx4@k#7wn)vH;s;p%|K$}~qYsB&U=reIYH z8m}w?zc^6DCm>2r&da8}C;{`HOdupikG_F~SAY^lmu|ue(yZFgJY+wo7KZCl( z6o4Zu;zTTn`U2=2N;rElVl%i8Z5QxF=9{PfsFX;xl?kXAM^#GXD4`e!PD>|ow+BTcN_u4Y)fv|;JV#8Ew3P^WS3xQPK^MAH*>1$9Nf`- zt4zoUY5h|fRtxdqK{+n;V0PZSP@8g7CrlF5+~(i_!fL_1Q9j`d5>o=X(t2oA%5%2c zEu?LfnBc5b9BWCNf6I!5X_-~ja*_{mB4-imBP*jP&5<&xaspD{{T|!^@6Y$}D~SYU z32#LUI0WQr5);%LO4SQR&9HHqWHT|8GpFst+QUKkWW>wzngu0~-)1pmQn};}VY)$Y zpS!ZI)o#W*vM2L_*OuW?`MI`nZ>XuwjVJCoyjVPBJw=h-F|mM6GY-C4^^eutZJd99 zD6h3-UPhGWg=)UR73gdl;~Fl2_Cai+LgOTb|IN^%B`jDj=6>lPXws$8A#o$fwZ4QVldp5njQcZ0Q4Xc_f#Q4)Vh(?BKUQ~z4p5f8>O=x zMEV&`C}+5naF9awDOZja)ONC@vZw-`*4Mh4v3SNMNP;!f;2mAOPRc#>p~CxSM67&fDfK`C;*Ncy3^OJ8mdv?_2EG)BNSBB<7iC9JMdayC?YfQd}g7l@IlW z$G zuycAO`u#4ngd(zbqP>%Yc6C*0z>O)EacFZK%nT@~wAMBR@kWeu&$ADe9oDx<;wr+5 zO|R)q^%D3MMn=`VAT^txY!*6foi+{D>aBDUv$zdMtCsv~cAq}?=|H_1{Z0?x>bcLs zZ%}AGobdRtZn3()mLc<|$?WXYa;t-{20+zZRx2grn`pdJ?=8T(FwZn|2qdGw`9!t8 z#@V$z3{TFqOh-RFJ+;)F!hSy@q=ptqqqXcNAI90WuCQw>eIc1)n{QFVHLuqWw~ht- zbND0u`M2l--0XyZ57<*wmCga*o)_6S@2#u=eDTg5KpqKxppIo6$bvn0AQBL}8G4OR zp|Z_AX#^2RG0bm@aV%Xa68*>z5RQ+npN}(FxsJT%y4Vyrn1A=r_Q8}8`|Lb9vS#bV4yv8#KA@H^__~hw+h3*b zVpDM9T_RK-rFqf@4_uAavDI!+MoHBixjb^tO<3D4PwHSL@$cZ2H@Me^JO#w@d{|WU z(1%Ccc1|84ZK_eI$dN`)cb*+oSKOdZUkC{@OXEEG+TzE>U++`ZeLwTT9EQETX1BEVRGwk*JI_& zf&Gx<4oV}IOlIl-7)LJ0()^E5fxX0nY+t5eqAwx!v5ux5yYP}{wcd3FM+K*W>7lJ5 zXDuK>t03ZI*?pjm$_*w^QyD2&GpX)0VFJ9_7UPQLv}EuI4c1fSh6dA>yM8iQCfhTJ8p2Mxol_!?5HUD^>MPaenj2~uTsfbf0LwiOZf z6$8`1shzHMqnH=*kmSN7U*_cl=hNE4rwb)?F_QP}f|MjYngxxR{E}{$yN37;^3=kh z2Cr`a)g(uVC<*ZBotY?oDheyu^p1CV&?q@AY1F-Pu znlGzDM5)NX{&U!zqAc-%tp|#``ToX zJE;2vs0JbIB52!GiIIdW@q>Hk(+uIodl?-5@ zYT~Oq{y#LAOoq)1FOG!jMJ%}`gUZ)CD1EXGp~9Q24sDkY{b>@>8bGaa?Izr}$k&EH z+k26ZVIEuj0J3?U) z@xmiY3j8q( zV$3Z)W5?6%)q*lG9MC?_z74wnme6GRmi8`z9(0kuheG0D^#zF;nHN!zS*=ecwF&in z?dYmubde|XT7%_UXH>uuXEzP5$=_cD*A08$rRn)mzqw+lx!uZEj)=`o76nTQpC<06 zMPcZK=}7u7E>J}<3AGn1Ps#UC?FYX3lPmuD?|q{ZCkIDIpfgffdRzD^rcRn%j{O)X zOXP<=wk1`{kK zf=%zDdC&)U-s^gE1U>3z=@wFo$*5*z_aBeIo;o!Dy_-13qF4sM(oC z?xlULs4Mc27IA2^r0sE*bS^#NYUyur>M4qw`OEv~nHO9CsRRtmR(P&B+ft{>R9?3b zq;);zFOGVpIw%3mGCyz#Hm*Ei#iw>IDSx(g7{C4uk%?lLdvUk7OV}94nJPAPF`hOF>v296EERf3}))52|xc|;nB)wi(s#&q(b(<(# zXi5^7*L@uD$*$L1Ny10;SwnLKJO*I61zs2uXQqs2qY(V>xo8XrP%paFYgTWM66*;5 zoUl1}N7{k{5Z`Y9jarj3G?UN1`TuT>p*2U?)MqoxQIT>&MONRBKm`AhW54pBLcs`< zulzkhmLaovGAGozQH&NFfmB%t&pMi?X*#+BNK;E{a)o%lO90k6j5+4rwc#zKsk`P) z;u14nlUBrU+vntekGts>_jm~rBYTHl>orNOCqp?K-&$kWyRu$Ljk7d~lsihHhi+eC z|5#~ll|l7SqVmO@J;d#1?{)43>)=9iR>8#TotZD4bNil!1~dWJ&&0>cw8>` zf+by9lVPf4SEJHqauV)>1Ixr^8OI?tYDxCNw6H*^lWgLi z2caQK!aWswkR(?7Z=B<;A_I^F9Y$+SDdvw;M=a{YzW7w!#j5;5a+0L+nWnrsI8n3- zuVh+!s~a6A?t4+1V`Dk&znM`wf(h;Oj?&*K3Uolr3dX{dzbv8$@SDPXJWQb-gB35O z_osEOoy;I=UA3u9y|72Bcfz`59=bL)q9hjSl|P* zgw7$TXK76}VXDI)Xj88@scPunLDdB8;0U)$An8K_@s_4E+k=V=Zl!>pFXY0sxmS!^ zC_&+V+;9jvJR`+%YAtw<*1#KQeEe_?f%qYcLqv^p?DMaXF(*mnFogc!6RMX;koL~&)eNJYhL&)sk zE??qqy{vnf;okePO9op5IcdeYU%2+cWGX8oFBA^*ia|OI;iwDSpAwpRJ1%L4HymmU z#($mq+ho#!2sRJ;eCTF_;&{CB22dV(eB&N3cbEvK|pqvAhNx-zqOJ})n~BIsyCy$A~7o4pTDi95Mb z!-;ZO z(4zy#+$LEa>DfTw;X#1CtXC(L>KtGkj_fvuJB$!yIOjb17Jp!lVPpm zwV61*JOW&xbvsIZJ&u(zmi6x`gWC*Rs$v>8Rn^TIupzAj z()m>kGwpzSa+V*F+I)bsRT7K}dPJl0Dt&cyn&RK`=B; zbTLR$*n<`o9`Vnal571GZuj%&cfj*f52`x^0b~kXrkb5FnWSGI{zYa4WXEP#$h>Kf z0%_yO7F`SLcGL=bV~&Iiay9kUl)2e^Fn13_$ZUV*zYT-BJu&z_jUk&;nz|ct1X3kv zX*bvD@B1|2n;PBfG6OzI&MTw`<*!N`j}=^eYJXS^8Dc zZ*O8{sh>6Zi_&EQXm8`y-jr5D_@xx zBRoXuJYbBIe5xELI&w}HeY0+JbMao93uD2GceMfV;}Gn&+Wm?*TqKc+&r$GHdOmPu zX`@w6sy6abs-7U&LB`5s%ETiEbFC9sN!p+9%GOlrG+~P$vzj^PtFopz?u5+Qq0{c> zj2yakR^KNL!_)gxXh3X0$7n_H34AY732&f=MUj6Ynlx<~e%M|jW>5My4s?Tmgy-y~ zo;lZ4Y0?O&?^<9moJ0wt`q#YS$fS>aRU7<*&#Z zkx`yaRf``S)N`CV0VEDAxIppl(z62*;sA1f@b=bw zh56b5?lhaY)npvICqPSe23>IY<0QDRjGI_YOuazK$if(HXNEs|sBKcuEO+7lG0gV? zcP%ODza4Ht$wZx{ZgE@_DuNL0PZIik!$R&urbvqE&vTjW?HafiWi;tb8s+8@RnVHV z=VE8TFBnWhG*liEj+^p3Eg2q8Tr`z5-qGXxOy#Wjp#^~h$DEDa#H|TLHm)? z@0G>QS-F`LZpr37#AQX)@1CICz9a~ByJA9h{nGYFJTO98r zHb~de9u->L`!s3= z>ff70c9v8p4=2JN-MeeFn=fe>-~j#puw_{qLXbapcoqS6&(NG~l03d>49#scnU_zV zJ8E^4kx0C2v_pP<#}xzx*gs~0i-JaVJ`YpW1*;exziOvX=ix0*!{M@Y0W!@x7mgp% z`nxb9Xam+x3}z`{ae@VPgR7QSBHXPM?O>0&j@anm@-EC+NEfhtoW25BW3)0Tl`F6p zqQoDqFYxSnb3~UXJ{*I-TTGnW5PEkw#uUeLj1+k`x|4tBpFNKNZe)XU!S~Db0NyqZ zC^G%^XFjLjx*WkN6SyjqL54}_JABvoEoYgyw zb6LYjrM>otHp&utzKeY$F{A9@v5a~c86D4Hlw(y0`El*bl#ZwZ*mI8Q#!_2yofE`< zlz^QOsTl1huLEvxR-GnTsD;T^4W287g{{#@tBiG~GfPxR+wR)!&Ar6qu?j_152wW) zjL957BNz@fgoF-+5ND7)Mp!kt$wMTsXFrdMuY+0IiqHCbo1r^<6nu0$c-X(+eg<8J z!(6LBI1Dg-rB7a_k177>mrD$DIDqlmQ!JeqOKGHimV=&46+@Gd|NZgTX_0gT3giROVHvreMX`tPZ1=2FrQ)hj<6$goH-!-)q!Mo*Dn7u`nB(rQy{5E_8+Q3@Kjtc$hrsc!eCzh zoG)2oAnf9Y9IDO8Ae#%`oqZxoinw*U3p83hY##h!QjfuXfq_Q7dlNL`0J#GZfF`T? zBpq5zD}TP{c8kVkUO;wp83zy6+sE@-dy5DGrxJwT@fqexCW@<;_d`;BFf+^(7*;l| zh!n@N`Q4>`pEwtGoO^1%xjc)}6L{n){Quy|S*h9RSx=B7yW7-hf1IOo5}OnS8{40 zpbVdcF8YjjpJ%GMaz~GcViR;)`linXkh}G=)93Y=Q1smY9_#~y0*@I>RMV?ROPHDY zC{M_3wBZ?l(WZ;d8c}q7y4J&OjC2yx&61ernGw=;ezEy&Brs}b3r`e^k^bqs;c5QnSn+-@lvb~#oNXd@t4`uxjCx*TIfnX_VyAyc!>@Z|O zMja-pouHru}ouk;+Ih2pC36jNAOH&dV)-GxdO+97mt?yVx<O z?cWXbZwApnDe$_keDwE`~QtW03yRQbJ6C05*$J`t|1v`kW>h1*#1;kyq0 z7=#WKp+x$|1le=S%o;$pu1Kn*lSUUJ{Ho#h7DdnHehn$=tE1jgDIhi}CXCxk@}(X& z;;7&)h-nPN_EG_c$Ttvj?W~|vArak406jp$zrMZN4=>}XmI16}$Ql@Qc_TzTjrG&M zuTqzU@o32~ckNAbBbX}7)_)tiePHv+6K1fYoU`Y}J=giEaOx?Zg^2xA9x(M~S5ZG9=FeD0UJsopR@?a0(F6y>boMn=*}Zcs5dKRkbN5(<7xL* zKR5~Z^Xf-ArMx1u(a9&L1({`qtnxuhX1U7Iwly>CMGfO)e+1Q4;x4ejyC){<{-|k3 z2_GYG&YP^_zSJa=;y>WS`?Rn1xZq$6I2Pv-0g4A(e#XW~fm3=N=iHjaZilHrVTM8E z$hVqdQk`dlFISnT@Lwu1&Xvs)DIII#3U!Z^k^;qafReI-z3|0|@$J0wZ*tYuS{)E2fBvXnYGpd?=c%qB9FbmY;_UB-!*wF4asZoJkZseZG|i1)OFJthrz zPbUPVYW%*4Lgpi~2tH`ynpF0cYi1{n0^7`}>E(P&ulHNdlk+ z8t!m%?53=LPtNlwoI6go(w;ObvNH_UQ|qD8^vBIY=HhT#bjmcY^*xJ>f~Mv!tRfGJG-O7+Wae+VP1{r5p%iI&0P(b+kX3|Vzhex z>Q0OSNFMtiOJ6T-aPe_!=<*CA^enrTq@+D|+VVY^gL;3QuE)+6{^SrL>c$N6Q)+#C zU=SaR+33S$;^cDm0keC&VtS6c)QgYVdDHRgff=4-Dl@Jrd5mjG+?3`xtmi%C8IPdB zYiM<2*PubC8Dbq#%vR#F@GwnQwBVtF9`~D!k95iIdkTzJCq!^wZqL68aI#UwJS_pX z8&DvjSgnn8{?F2{5BpU&Lvqyq&N9j zCTdBd07l(vH^><`+bZiJgn?(Ass7WjQcde(eIYMjKk2;V2u?9w3zo+31U4isi84A{ z8_sr&kS8Vyaz*S(f5wz8%8=N`fck%u~ZbJ$M{8j-*8f$b3+TwjJmmss|S z{OD(4GOqHpNj-0t-!&j2DWSbdM$Y0f}kE^IJT}k2`BHYO;<+&{)?_sm4pn!obnB-Z>s|vdCNQ1dt*_d9tvXO|NeZ zDCUTi;iPyDkzF}-iS8ybIyXCF*63lUsc!&3^CByvnRw7f9ku#Zw%+5#U%*xspHKXM zjAo=#82p2SDaITgtH|9}T5xPkeXBsNN+3PEaJ@ghAAZx0*7^!YSKPY5r7oD>?CPjZO0RJ`95PjtU|$=@9My@3L64O9TzNack~{%^%D7nI9L zxVqwhP~~gLG$*o_Ga7uth_cwJ)KABGn(j=VtDh<0D?OR6@mx)h0DIlf&AdE$uIW>f zwbV#h)mQ(LEow2vk~(8o1)+fDWY@uP$vvgA$Om_jd;jrxD{3+;=_jpXbM=D0h5>&! zLMyA|NJ(m}H(U(F=m-+?t6x)F5Q($@xK|oQQ%?6J#w*AMA=y9|D{^T`|RG&nuL2|=20iNt*bAK(0 z^bC6zP|3fRD^<_-1xx@u{ghEsTmVly0Rdk~9(8<~E+SL+M6dGWfWey7s! z;>?Z6kdO-x-wrdhAl{h~d!MN?ar4~rL|VWQ==loVf*aqFslB{wte*3|z+sK?(3yTp zbs|+sNA4zDB&@5+NcH1RRa1Ql`sem}oF~Ym5F;b-4Bn#AQSD70ysg)}6P_cf$ar=9 z9(?ED!y-+hS^FI+)xPF7$@stoLWagr^&GybtKaHzqGjB|-~#*cK@XL@9CFxA<>(A> z$vMAr?ZHkw9GYlQx2hiwt9tD-q9wP%v$z0Ee9XF~=0QY<+r5ZEX;b!tu%MJi1D}$Z znJP(nZz?*0iD5Br$Fb9IiME+SR#A5&`A|raSik{Rr1iWNV|6o6jWoph5Li(7BI2pGKRvdQ0#Zng@AI ziIQcfnCE5X|45&AVh7--6%RjDHrh?t5TpLo&JBJ&WEss$!ABM3!FE!V1*Zq%$0>fX z;;q(lXk@@GaqOy(@f`vUgu0B2TSdc|{?K9M$yCUA8A z{?*`V7Ftygxo?Ri3-qG9`VKm(K{+jjsVouB@&EjKuXj5VUv}s2yi&eYm$bvJ|4*{# zLBf7h#jkKBh-&(o`Y%dg*!I@nL1w<%>kvQk5SUXx8DX4%&Tan_lRuaJlE0-__c^Iq z%O}=WFWEAID6(8!9v)=aiqC#qMVx-sr_y{MH}C8IpAWR_9%@vX@$XCB@JG_8Vx)oD zqJ4%chZ5-l;ds#AHP!u!zcKkC%<7lo_MD93Vdb@YZFde$s(m2%g;^H~PU6`lO}iWh z@>4a6&j)^3VW^dGOrg=*PApC2`k0BA=@O|Ago1X!{9T%|Mj6%>E8Akm+?sd@$xQ=0 z?@DSe|C9s6Hln6o1N02PLL6i|9W+G^BM4tRYLIZr0>IC@?B5f~GI0 zI-hqojqs8eU8*kGd>QK}i9u#R4ej|n!%H29h?+pCLy#dkr*ixCinooYcuicP8WeSo znoy%4?da|oMCK(^t83$^1S;2f!oy>WRJV*9mK-Gcer~KUMZrVIaIl^gXt$EtyUg;c zQa!);8HwICPvhhqVqNN z!;bSZYN={DJ3TWU?Stfr1;ygI6qkIe4qsx&S#})Glj15pO(W+DWG)Wzu?3(25p$V^ z(iD2I|1kmLO@f%it;CF8cLrz)44t*+x>zU_f5<-~n1&NyAVu}t3v6=A;w@zImFG#N zwI_?^K3>+!Pc{UI`5uwf2`~hTp`qEo7aydGJKxuepVUB2_$&N+c3z0fJT`vEOwBms zNhd&H7Xk{aHvbjV;;{Q-!U{_b8)S=VhGX@#bSS~W)1b$iaq0pGpOONxU&yq*gE z209D?HH%#Hv%77t1rz(0n=U-4oDRqCD_!#<6ykOCrodK8v^|UL*D7Q+s7#$?i3|?v z!ol{g+U|n`FXMv-6htegGXJqy^izI>X_q;=!xB#$3h=+Q&$SoV_S;%#zI{`V&Pc-Q znE?7|U2G9+4v>USv0@iAC_$-FiYg>o&F;y^?QCX(=&-mH0sOj9^CZ6~=S4empIF%- zqHAF*{U3w-39=9^t2JU>(mh{YA%0Lu4Sc%zt7(|6pRonYOJD9+Lc3SimvJK?q;`ij zlWuoTg$zBox6lwgVeY%KR9;5I=vYx~T)yb*1v9kBbyhw}~}p`IjxZ zI4%`Lcz<%Bd;XczE5wr6e#@*Ju<~B_2~kWiSFzN~&p4NnMg@08O?yk?YWHLRUw4|lmodPPNwkIj5r)y3DOXr;9B2&|Lz zT|!`pwqx1Fd2|?X3g-zod`F$p9A%7rJq`{ATOfL$`yi|@Api&{QD_>Z{l;W{c2=(A z)bGYHNCN(39pRV2uAD21i%9NmZ=L}-?i1Te=LPSs>D@_ z!uQ>W*6Ehzt#?Z}$eqAmo`7~4nS;Mm+dF4pz0AvM9^Yn76_W}H==M-X7thq*A1~W* zU(TqvG6oxV{m~x?Vunxk!QqC0%FE&5l7&3y#tpn;DR;*o^R|nMAa7ugXcWup6JSOq zb0G1M9mEiQ^uY1FOkG#tP%LroBI7I-R}+O8tppDg?g_bU@=+W!b2Yo9hdf)TP9m;7 znD{?_vf;^NU}*VMy`8tF;mn@4Hm+PkT3%bicugl~N=^;rY8(8cN}w4=dY)e?TGUhA zUeTAgOd!6g5qB35Fcb`Eb~h|370?2+{%ar%m-^6J9oVWdmaoDF=1&qTjK9K9Xjp$2 z;wk={n0LiyA!sxSjD~NAFpofjQ$0o1b2RUanfYX@Qm^ma^hv_!|Ky2Qz=!rhOS=`dvSM3 zk;?w9eorPdDcS=6C~)MA>b>?CHyylH@V>5p1I8r{oj|{UJ6x6=Srm%ffH{jWTu=u;M?tDJttF}LjZEp3jDLOxPCMZo&P-ElHO?y7}i`qQ$u=EyFN^CjoFN?Gl?D(P4D%cr8LrV4Uo)fXE z%qh?)yDM>TWe9JVsPyDZRLxB9M0TF`CVK@}=g)$IQLgC1G)H^C8@&j(#avp%xzfoQ zqBBxJoAQxroxQ-v=YzCpy-3Nu>>WBXS*!$~{4^WJFvj<+IF8GjEDz3-~<-@}attWT1)9m*P+-C>Y+uz^31R@XT9ePbrrCTh&31%_I#+HK_csG5I`p7nE` z!-v3#P3t6851T~0l;zhp_&FtsU5IAR&Lw@SwEsh*NT$+xY`@j)EJsXuqSA$Q!3g236)GI)e? zCBuVnD`y3&tF4glF8>EXcxq$!!@c$y53tD&1=R_RjXx>D=86 zhLe?#6GZQIPawo%Fm&;|q%8aJX4kTbaAEXJ?;s1?oWcCS%jU>Ev+XZl+An})LY_-6 z_*!S5r3@E}H0wr1qB0m4zT$T-6*vUX-cMNn&}U`ty7{3x$LcfOe9Bmwlb#t`J|gkWw zaTTtoiRpnyw5)t=sx{bWyAoyfp*eN&;_gdSg@#w~B1(Em5U=rgUqu27XuTcA-MZKS zU_PcR^bUo5~{%=8ovGYcgH2J|OPx1<%^=$cfks>xT8UZMzjhkJKnGktUw~Qs9$lM3iD(bFend?)fh1iwx@uE165>Y{2r zIM3@~D>*)@QKM>jIac;Wjo!BiFUSbGCd(3^s6xc9#L%;QazUygd~*E`;7UB%9~Thx zFJ-fazoq**&8{-Goe>khb;jvq^`Bt}FEdC^*07WB0)_s}g~nu21vnx|YMNOByWhvi zRjJo~-hjc99d*puph%w?KLV|d^X!3ID3h2z$^LdF$-Db{XiBtkzwHelqOfTIB|D%x1%c^KI7 z*vz|eTfzbXQ#*rDf+gwH`?10dZ}64c7bga#Rox+WXSSxSCtD|b4br^87pRLjkAb&g zP^6H~u}>)wLRd%o)OVx!`~doR1AqX_L3D?9sT50k=I$1re@5>WH&67qU3Oug5&zry?xa&1>)Z)r{Q45?}fbp06i(4p7s`QV{H@!WeAw*mIYM8{E; zFSNL!@7G!voL>5~(7RlMW#QYW^<2#6P=Qeg!M<50Qt*lG#i%~MyP~|>Xd)p(7spf? zU6=?+ESUNE^W1Z5FZL(ZzD_s$vTW6}o^)d_v~v4A*$9@>EIUoBG5;vwH}JdEFCS~V zouIFVZD!fYE*i) ziV`$W+<2m1(Gh^r1EqC>39?xE8T-X`yB)ZS8ZgAqAMSHj?6~~%SGbt7{vjIUOzIdT z)SXiEOa+ZD&I;wM%GWkr5z1jY3)?xP@}(R=k`?44s<9J z%l}_0hAy5tLw#YTh4Z$E2fjT%CFl;98xu~zh#q|#Jz3Osou|3g5Cu2!n1VDy=tY_2 z(31pb`JU4YD>T<75Xi1~_~jhG>%q0nrrZOvrRsIoxpb%m9qz%NG4~i1%{ZFAv8#Ot z@6DD3WR=cLunX|{XqCK?tqKX{wSU!u!&St)oQ~QyG#l2a8Z~gzgzpiU);;86NTMu> z{@+Bm?HF~O?W9Q~d<;B}L^~m?)k{la`~&oY^*{FYtJ;K;WDyV=)KSN^OV#WI>PV?b zcP<`1CMYm{N6^D=1cpm7CRnd~xUOV#(^PjrVwy6lIzFm*Ame+B$u~0c28E$J*#nK6 z_V(F8fL9N%2LDEwmh9I&;L6iA)^_vQ ze_YB~a|Cwtpl@ zrEQ2%K)SyRH-vG#`i9n5w^{0 z$8ckR6s<+~jR?!y58T?v@;Va&pU_AJ4;sS+rAieHObYsU&tIh+=s64+zzWgSJh(MA zPcEJ^JxT8NJl-_}a)o!9$*|b3cX}+w`f?31g#X%JQrtE<=)P&<*QM-qEO=9DtMt@g zc@=EcY8sAsFVk-Rwy zppvv_wE7=3*x8wPpv2A4y;i9`y&EMvu~o1hQ0^eqOe0W0jpVQ#QYWmuW=~ke>za_3 zN~NxqDvby>uE*QG!d%?cz-`7o7UbLlHhkQhWdIb!(~3ODl9Z;-ZmIDs!)vs$fO0zM zC-4Vwopz!iX=`NY3Y2pg;`fv#k${uiVBNHO&C_+U@S1LFDnv~f)33HCD6^Z_38>(P|Tv}&L zqKTq+Rv;E?{lP1lEPMk9yhrU{W;JREBtXZ>!cU|o^<4|Cq3@D;fqaa6Gu~Fuf$zin zg;&6v0TfH5M^x7n?AS5?;BW}Gpn2b7s`P`hx++G(^-Kj~L$NM0k^2ngL>zTdM6d?A zVuS8jV+VObInF7(N!DUYvVfG;q6s^8E9y0?H#_UKl7eex>2RO7tBtVL(cBT9W~(R! zGLnAaE9I?dY$KRXgx5%L*-^G{xKRjXL{r2V^8(5VzP}kJI1?Xdes;!lEBZkrhU;A5 zRy|E7DD~z3m;>CloARi~(~5tbh9xW+HR~F5$gDf;+5|vK0Jw?Jo`{SPp5%I#xQi8l ze3dl~={{tuu4+##t4?VLjoQq{RE(`?U+vBO%r5k#YrkA_f=rO`kQ_yJyA5WFA8n?N ze1t{7zU%X6E#CMZUT2ll zoIGkkskTflWCi7J&{*}Subm;?_^{!kweer4n&2W}2io&;8RaXHsP8@*m&h|strtxt zc()bn$U&Uu6UbdR!xWiWZmK~{x4H{?s7=UIm3-C$-${2ZEebHal&0&n-5#eKYdd=V zHBOkh`cm0f-*?tMNVnM{4Qyz20~K1;yPTkMz4eLfLgbBr;rBinW!Mvh(fBvkuf?|n51tDVf(90p@cx>Du)G6C$s+}Dj^ z3RcsyOga5Mh+Zx`#3!FZ7^#Jv&KPHDaA~4>E%6Ze{uL0K-4vf;(~c2>#4gCjdFFHB~=~r zhFye6Jnh|>9~wGD{Y0!I+WDgyVk>XPtT-mIxQzExeNU&Jz8Ysv^N?*-xe+!-7JfU& zIR9Dl^FLvvbWT>CLu~^_k-WY1t1A_oXn_Yed2E$STr(@x{UfH9HsQnG zwaIUWgGbY=CqVvEu=uxeRQTs-gk;IL#OQ7pPf4llFeEQUB|n@fiDO5Mj9HHAn+t2? ziRv4|l&wDS&tV3lk38XblDl&Xo!A^Mgz~hZ&lv&IAmf?!b8P}8^U2Aua42=NI*?8v zngFO7Wt5s?T>c~-MZo{wW+|QyRF^I5dOD0(X=bx_s-x)E*%yCqg>ZWPk%&-@jj2`u z0!n95YK&`~V&MxY7law-Bxlm{rx) zN<*cd(mVsiUlGB66ay9GG3cEn`; zn25YED|@j^`eWPub#r(Z^Z0$ZD+Xk_W8q0Bp6(@Hi%cH!^U05|8Axu}O6BfB%lK=+ zcbnUi{#^WN^eR>$^X{IF@qXO=EVR+ScnY~D-mf!YMs{#ftXB)e3mM&_sRl41HzK#y zlv@+pI28!$#T>0#OsEYLZP3M^t)F*qBrGP56_AX5&q)W)M`b*Z7i!FMSLPHDk;`<; zI-K9yVh8)o4jUz_{d*iUrm`TWu%RwBd=1}tw--mHc03?+wV|t#$pW>Rzxm0557brp z*-fk`#6mB~NnB?5y%2@^$7oQ!{-PmOcrPCJ#DW<>O<26;h95sg2yB}9EKFJL(qzwt zd*b0b8OvHqdC(S0lLL{>ndr4-(c4v8e zr)WD0`VmqGvZqP6u<~-io+7Q<(P=Z)rk3|X1^z`+P{nj1sc{Gb!ijo9 zJwvf~I*)icNi**cdD)$70$|OqzO)0V{W*SksgSZQ?Q~2$lDd(i8aw7uB(hDIHEf#@ zQu}q>Cg}?}qM)9LxI$Mv+r2$Bmk`eT>txxH77K*YXVnu1w@!`J`*#dlNU5#A?kXwz z@DxAv9e3@~TM7Bp=a4pBl%tEDFQ^1g^ECEN{-LPPrLU`xnh5G6%0*A;21WX$GqpXo znk+!O?9WyJiq8!Z73$6I!R-5Uvwe(;7)wMxdhJsf`2OHTq^Z);v}Kshe@hU8}c*lDlYz@-W9<*LFBhucl%F-l|s}6;+9&ey>H(S@D0 zFScD*dN^Zc{}^;W1ws3#z!+;=_8$GAe5*e3r8y1LF}eGsTtg^Bk(K+Rj$h4yC4^5g zhQ;`EtILuVZaIdj=Jt~a76W@Lr+)uz8ORJX_?}C%XS`0kq1dX}3VAsv1xO@=7G-tR ztWHTpw~D~Vx)YFk9I3qPY0Pnlw$p$@tH=3vc%5@8xB4NcIvDj>o}1QhjG|9Zld(aG zM-ZvnUa@#_M%X0nozerbnq21eqgP|h8(NZwFnr*9`)*@>Hd`;AMCv~RxP@X)c^xHY zKS|Zyw9Sw+%B!#fWM2p=bPvK*o3S$Uhi-m9j4&3UiD!2Ul!%RTtTVub`QG@$(>&;} zx7KXK`?&-~Agl(s0@YWt#3J8m;@UC6WXhyWRuMPA=O!Uyk#hrRltz#=@+;u^+iWM5L4X)5z2e?SUCHzzaD$ z&s2oW9XCe@+Fkn7nv!bn|Ifu#b_q1h4i2M>(wyxj!_oe~@qu6D+EV6f;fpc+asXLR zGzSe;QlEUCmK5$K!0!?T#rDc2t|f;m<@0vA_D&%H$sVB$%m=sD9;fD$0K9Pq?2QvRqWfZP_-T{Zc>^bTAQK#*#~5`1u&+@Y7J5+z;Zq)`}V5 z_l)&C*MUZdr(t~h(Q`|F-7HUTMqhN63Cs)LK6+eeH;b-zNI*+4;qz)>Kcj#bva|Ys z_zM^OKc!04@0&{s%|#GTy0;-4clKvZ>iw+X5jNzP#MSHzx3B8=Hz|Ifh@=e&3@a@Qwmjb66mv{@pv zwuT^{z@8|ttC-VIN-xCm%cSIDF={0?mM?bB8OX_g+ViJ@k*Eg7$_mvD%el}l@RQjY z=lIw=M`D3^VM0u^;%&xw>X1799ckf_1zsm=kqQ6FRoMKnGQjSg2tDmP*986vAx(*m zQb4#KJpYZQa5@4|g+e7&!$=ok-!Mtqjh07Y{wSJRxb+1?* z;|IvgEQyzVV$MuN-=O{yvwiVc9neAm~ z(NtT5Mc+t+5=C*y_Vp@)< zBadN9k{KhL-Pzv*FTLg=cS?#*)M$CD(h)6_k41`Elc}NaP<))rA;M(FBzsw5Krn4P zP`O^zaXEAn_2bx3nwO5gK8BE1o1Bb3Ax@!boqg~2832HiH0AuIc))ULpo@rkJY<91 zfXloZBcP$(%avAIsnj2xyd805V?MYAS6}jKW|mRiy!$9bC->@wa%eAN2>yv8yWc?!jUdM)6nau1h=u239-B;9|n=EAFqaI#7) z+O>-uG}{xcS(1h9V$y2gP^r3|kgG7)>#AHjtfo-YLfZ@E#>ZM{!#I6n?Pez7>ED3l zSM$J2*geJBkszgS`aoa!5R!pFVlh2hnrN0a@LlFG#OI2$cyunThwC5l5b`%4e6;v9 zd=0-hjC;CHk=5rAxhZdnVEU2&${y!ZJ~Y=hHpaox7iuYn3HVw(WG+QcPol4U@?PJ; zKQdM>*Wc%z#uKh*3zFeeH`U@eb!l2?!AUPXESd=rw_f;Om{ZmUW#u3Vl>gq>M(!E| zx){?sj;oEFn=huJXJG1I`h8QH-J7VB2{#7PrqB~Y^=%;$ueQ*BN<#Z(@hWuz#Ol}V zaU!M%+@*^&d(2iY_$}z-(WoGAcWU`;T_oF{yxi8!;#j+Y_s`Y0E_UjlLnzGe!fhF_ zc(qIbgbSPNKFaC{PQ5u*c@O`|1vbe92G}8E= zx8#$}!4g0H_V(Pp(8OOBWD-n+`-Wb21lR1>!|%3u|La3Sypy{Qr`4wnG>AI(hXoUn z69&!cb2U}p^oP)3k-GNOh1q6GDuUM-$@NTn)fG>2S7cAb(N zwM9oFP8&sOY;kB;+VexZ^cM1&jt7;TNz4(z)J&KwGVcFzuUmPPk!_pFd-9Y3k#GlK z4(KGO+_<_%5DCiZt5z0^DZq!O<-jToibXSSHIffSCUk_L^#KGDs(I$YcAL{?k2Um% zY#Gq&oPKBiEG#tthy5L3>WZNIe^a0iJRD!vDKe?AvaW>~*XPQN0Udff2}<)vN=OW6 zc<3Dzy)m>4!+7D%uU70h_Z5d*JD4#_;KG-{*fgpoSyaLHmr2)hgmyvdpUHLjck{?!TC>q`rx;6;(23N0y?K zS04E3fNatH(9Pz0d5Xz1I^i3Qxk2)-UIrd&4cEt3^-;{a}FJs7o&cRwGR{fT%cL~lIYpDhn84poOn9_X<7<}R0o z@dvX=2#D|RDin9HMz5Jy_2MV=06;iJ6Cbpf%s>Z#F*OQyBD-$lWs42Y&>iGt1D=z4 zNlEkpx^Jf?iy2h$neMvL#>XTP!bxn|8)GGPoBv8t5g6D{p<4RRC+Q$fYED|cx>^$Vv=HVIsYV1X}z}kn~JO8sItJAWfwsuse2iG^UF2m zv?M5PV5*kYVd?DE1=%+PT~dxsW3HhRyIUI$Q|VMn39oVS%AOg1!0)%*B>~9fT9EPb z+zMa!F&J%G-T+{(&J%!X^riZ{t}66Bv&&E!-r3&Km&>CrQ3)A6-B1ZC2dq9s@)5Z8 z9A1EIY0$;y_B?xoVY7#gVVa5y(s$$pa?M=%{)vwj?ZK;6THcZ{3{p&o-Mj~98m_%l zd3|+oie|qT{OW8;j4UlcT*zHct6gIBcJyo>z<>I0IVUonjRy%9anj|d=e9hPvK6WJ zg|04CG6kW9Lsi1GSM5~>{m@68h!Pc>7}<{hLrr_vR-cMMRA?WX>dyP|;6o&wNZ%z} z5$wRHzBmEzxWK55@1F5% zHjhz%PV9B_H;drefzT_p%y6F{-qfBA7=s6H!o>E)1$g~?Y>d5neIqA${$P3IQzq3Q zMsOMD^!TqnOUQCMJlCU;Pdr1Od9)EJ>OsWiDMpY11XZGkD~gJI-rfjJmu<+uui@9K>l|1zrV0fMq`QB%YJ$Tay&%i&s$L8(Duw zeuYEC2ZWpMqweUMMPN)>&D&N03t4)#bLu*84Ckqp9dRIRYbYj9gtv(!0Z`?93!s-5 zqjJlkvx2ZsbQ|GGok>ohcG=#yOQ1{PT4!=xl`Fvu|J^%q@di@xy zkp$(Lm!BY5to=3>wk7P21ZJbmtpmOijD334@!s)Ic0s!-JQ&nn(O1~Zc1(h zz~kDw^tn7lWzPfs0^S6_GcY0EG1tfw=!TR0sRnHU7v-Pgz+6IiHM9Y%ZdpMtaCt^I z*j|T~hTk#@{{zQX6KKbdh>roKE~?Z6%Eo&31b_11=wZJ z1+*q3LY0zWc^<;wNP8$8J$g{^yyA8aw&^|&N6X5T61U5WHA*Ad@!vVItPY!ICoFvS2!$+;CojJ{dt|6C3q2&%U8!tG89U!woa_5sNL~3j)h<@EiiAzGtXk z7iM1_vdqr|Knepc&rE{ZLPe7BgNw9$G1Tbua%jFQp$xl)EEpF&3y#g6G@AIInTmAd z1tT*~V{eL)ivpYqY#noVcqbMFSx^~Msb9EfPj1_fa$qID1k{R?z=?xfA3Tp>M6LBj zresWApLS=#o&{O~(f~_Dzi_aI>j$4(=#)(=!`O#mt1Uo8#;dL03S%(pX%Zw(P=|1} z7t!Dic2P%h!3m^Km0L%~V8xG3K0>D@B@y0SLO7N^n2gabaO1MK7lQ{>g-nq|k;D1m&MTP;{QCF7 z!&T{}C{g4|IIE-kHV&_D1xzK8{c&aPL}8fo%vZrWubVDV2C*WnT^r%$K>!xu&iGC^ zVyigy!@-mvpn;XFY#g5!kN>%S(*n)a)m2h`CMVADYD3X(3XI523J3F$fl@rX!sz!Y>=G5Wiam#IoVS}LtSJtI1Wv(!Yx ze1O>rv3aa{CT5-OS&q?3@+4;+*b>?|oH`MKXiQe=NWB6C-uioaRMpAAWeuqjdOFim z$(8ByWuM5rb*7T%=62@@uNjQbJJ8&I@;huvYt-SP{EDJ-lD7#tNSO8t0L%%vnnRf%xx)1|K(0(|;#WhgT0(E!`x{OrWU-+b^$xw&z82&<~jRHGv-7 z3sn8EFyT)B%rQE6QkbkFDe`Q?+uGBhWX2+eHDg{Sn?DJzfwYATQdJyc>qvuppD$(G z|7?hHWUA>+0mVx{@4k^|E=*b^V-o=L!P)Z7tFj!tG(|*kChmsKv<;fIyT5V_v%jbl zy+4y2;^y(!U{ zVv)AFaT;4E;@6ovtnr4`JnZJrsLPN44Rnm{eX%aQ2mO4fA%r`TEQ6%RZQvz*LwWNo zm`c=A*3xF}&fbqmiKd(68Z)C~7ehtjalF5dR%dHUE|14p$k*@F&wH+y`QCqBO(2r-Ww&jd457t3HdjxJ zkTKqc)F$^FPa0erMsYYa={L7mqb1bHaR0%VV~wvn^9oJSSt7Z}9N{P1mLa@xg{Y{^ zrLW0bgU+c|lLC03%69iSAx+&BQB7r6f~jJjYyffa%Ei~MFS_nfQTb6m|FuTs+_5cY zB4{?L!|N>tFkwvmGw&$vlcrtiDCMlPb-(m{fB>~M5~f(YY(s<5O) z+wm^5{|uQ$hX5>+5)#hVK}UJ|uj=5A0z)VR0OQ8UKO=*0a04&i%~}ER;B#_`ut|JK zWdd(G2zd`YvX$Kf$tgirOg$w=gFhQH0Ul@K)c)GzeGZymf*Y{2y^U|y5E$0t8oku# zro~u)?ckY5vww$NKX874F@gQ?qjGi$GM@0=9sUETGSI$$*ch*HcR+g7;7S8HHlW7< zLcNs(G+TjuiAs>=S0) zP?D=ikW;LH%u59Z;JO>=vBMO)vP?eHL_qJHaQj(d|M=0FPd+aZZq=$Irn-$22KEbM zIDEO>A3DvJw)Efz9r6QtTxci~(g}&hR_bU|fAzpJ=2}Mx z5@oNVY{9) zyNr+e+9y3sFdHR^V%7CKXc8oYnpy-E(?f0^UTPJkCxe#xvzp&tc81ivanXDmx=Fw) zt#V!4u)G%K=FnG;uhHxx2j%O_?CrD^;ZU=9=O3!r-GLp%{Y`%I>q>okGawYLP&b=* zT{mGx7aUn(Xd$l#fmO6NV0Qh|E<(3d!bhcNir(7?utTCvLFX zt)QPo9+#oIR7nu*mNhav#oxw?GjS}K_nP&(#L4h0wbiVE?>ZJaeA}XA{6WN7G zc_i|^58sJuh9AmSzlZ%5d{%-T(gA>B9R|$2buK~}C=O48{uUn3Yp)(BgB~_&#!nGp zTh=Fd^={=Ip&K(DPqeqxtqsn-t|9Uk|?Aa2WH2u_Vc+zbt8+2qj_z@6^ z-lr!r)zrVkOQ5eOT!1V)F2vUuu7Fo3A!Rou6yUws2_>c?rA)e*_cNc~H!fA9_fkUt zVZ52hiO+Y7)lq@K$&(Ce7>NnIqiX(a@VNgQj3CjAmMIEzL{Qgf^%NzaPtg*kqP@T| z3rq~GVUm~@gUh4#AIZfd!WUHg@f24B-xowoaAFCkHk9B;CF1y8_Pu8hFMz&{A=Yvgc)`1wZUz0GgQrWRF|56Q2FX&LMUnD@?@7GY1x# z!DI@qLT|1p42``0+31NIE$;3Z*JVmu=Bkq;(WJ5;Vpfk&%vPdzZQQP~7(ZWla3|xr zl414^+xl`}6OYr=x3>)UPqarI1&UkGFsenMoqSfaTiM^6*hw!vA(rp=E8{SH)n2LY zm10G=A8OF|DT2VMp{`U61v1sRcsa!}yxs=2KK++c8g^Emd-6$kGgF9^ncz1#d7(sl z^o@62qlUU<(QjK%eZ-|N^bj7sHZ7K=ax#??yZ;IcOu;M#fz%b4)UQQxW#;G}U`d@9ZsFdvyxbm3{|Pu2Y#g3CeNKR*U}a?pWf z4f7Gw2;{w;LxY^#F&V4MtJ5_mI?$-1^Ssy@|BJVFhf8W#SB!E=nEz*kSXCX1v}+)q zr)+w!FE{X-8{|e#YQs2}8`kWsV>gz)`|GE_A|~@6V)%!-+uZ)s7K_tPlZ*UbKD#0(Pzl(!EpY5utgeF%K~7lRyKF6x zX>1I>rIHnZmThwuw?@oQgrBLJPCxUjDY$?+(Hcq%4O8mfCXr`84R!r@bq%gI+u9vC zs;f6HdNS2Ft9QeAO>~||(HLhXain z1`6?;oq}9g=CzWxdbRv#SZd}{Yo%?TbSgF)&M=(_AAqcnSw^lboGsprm_WBGSd2^k z+J4$KVRgiliavQAQoA@y9mkvQSSPsStWBQk&2M;}+XG-})eDNGFyg2qbLkVq7JFBp zVT7vT1K|2cZmlmQbf%HN*N4pg5VfdX%aQiHySuzPT~^W)nkv7|bgC$y340><$zR~I z6gilvK?oA@F??AuI!D|kNyf>-!GJ2+1gaSC%z;ejPKjKrOc?rNg7%)J_F*sts8DQuAun)WMXR5Hu9l287} zWo`_ku>ojj5$sKe3R1P~e^OO91K}k1BPeqxzo>^yjImP4*;KX8fCdwT*P?g~euaoW zkM|>i^)i>U0%fDv?KCK=3Ft_`Up^i#T@N$UpCR*F0{7$210K$j^6eBy3}qXqhD_e5 z_X}?jOOA&I2{jv00fVb+XJz=uz%*8X)}|bTWM!V+JD;GT{K?{AQlIl~Z4EVl6J-D- zJwYo?2^tt^(Ghk-@zeK9lh~L5&g5#?Yn%&rd6F7uC&3uLG z_1+M3cpM^rbiNEd&j7i9i{6aXS{@E?-v2q_OXNG)yl2dQx05#`97z>a_-G{%NY;1` z3_1Uh>el-a`neN>kN{4tz?P{Nm+KINa_GaqxUxO;8xFvt;sH`Ybe0&xhR9pc3fUv6DIML*eo}O_Um4a@Guj!8&^^bc zMJ;K%VRyoDv3Rgd#Gf>8>)tEQ8#mELvbUk0umc*h)07SdJz~*%3!H_a0(Sqz(Q?F@ z-#%V}0sR#~e4Lgq6C;DfNx|XKIVfM9G@WW;hEK#MmI34;8X;|r?#!h zCodj~JZ^0^w?d1ahWkZN3syOjbMw$AY)_|go^39MwF>0s$B3SJ=ZGA|$!g6pt|_4e zAj>}+(3uk>He=E39Q$-|+xUaF#M1aZKVcLcfeluHes9wu4hr7BXY@|!rj5<3tKKmZ zXsA6E;uDGMA#D8#Dt1Tu?z;l?O116D5;aL({h?;KnoTb-FeHO?Mk4Ju@q8ToNsmsY z>rgX{TX;q=km)yq=o&)~^qTi+$i%GSz(|^|+0z*|_ICGKe?Pl<*UnO#jBYlyKV@*O zm;Lui4Ud#0alH{b=l5yN9KJKgYxVj7B8LF^$kAk{kI z@^pGLQV&A4Q0U~=He>G<-7eAaB63Z(wtyPq;!NW?8-6?Mh18`N;V2IXUBGHv3hUK$ zBd&o7osIH57kRM`)6I^nKu%-G12dW_2zpWyylSFhs`Ua$_-14T1hL)Wtl`!V?8U8a zZ!lzJ*n)AR%a(8gVJ#+Wk5z`KM<0j(=c6Y85uuCyDW`X50de*7M0fh>c!0i~@0iGe z;wGX&r~iJLq2py9-u4&p6!_wU0b5fEiv~x?6KKfD z=a3zQf>duXlWct$MOOa}ITd{Fk+s|2FW*BWOy|Z1J7EEXu%#9Yh&1vL=#$B%kp1K{ zq-M*o*j1k3^hxRI^uU#|r%$>O;KfFYW0cH>CH+PI6zJC$Q(^-w(X-s=O7LDVTJ3>027Z7)Q zVG64byE@2Y!3^|&K?+GfCJR5e5FEFU$kTn}>LP`?K4Fh<7?a$NNgkZR{I6F(G_!y> zU3Amh5e^LaAtR-cWp&XZ4bzBJ&oa#3MctfdRX-wRpF4U&WTSg7YlLI5G1Bm=vb7X0YJ|lz|@;@!773$gpY+W&XP!|J$8G+C&yt?854f+S=RF=V!~U1hFaeO|NP zE6e=>G^xEW{aIIbdtSuUhFRMa0_^nbCIh?}Odj5BAYmaur$%x4IZYxAKiZ~Ig*@Zo zGmg%1{FQ5Eg^IwvKLV-fM(hiX33CJ@P=B3PGr$xX9f3i>)Jxm1RM#zsj8q+WP7aDeu=N7UQJZbc@Ddj85^Z9k~0bR!dIIC!g11g;piG5-MUKOKZ)8~W6 zw%p$`@UZ_SqH^#X$L9&*asgf5N_$F&0dBX6vqwnj=ZUawe>R2&@=Ud8+C4JySsh#X z1k1L7n;z^9mYyYe)J6%Lf}ALomTFWG<9=W3bD~EdWE@>r^~y0JP{Lclxr)L1eAK@N zYm44u2kVY4tcg|2vJIcv>lR7&UjyhP`|+j5`&$$&2(Y#@vtMO3rNF}q>x!)%MDr~^ z@_7~=Ett-4PVsf=vnF!2Xx}v2r))3?nzxY`#ai^Dfw}ANxY1gbX|mjOnAEfKY@vsS z2kjdHaxc(I(RWUbtJp5K!l1S+cisTU>p|ErwMI9UJ*li^Zq*Y15*BOc+!U6!lJUE; zS4Ge5OB5vVa}!a)qMH-=O>AaSsYN^t?E(WAnXJrvsan29!wzv9Q>pOiwA~qs=Jx22 zkJfMtuNzH6f9IIf7q4&td}-9XpRu=-@Y!d!u)J9G|MF<(_z>zJnuTTZJ~WkRAE?-Y zr5z0hRcNHyJFyR(kM;uie}uAJE*XN43EWiD1|Bgaz1KX9rX6{52_Gd|qgGv4G&r~O z&_23(AiKO@dut~r&O2}g^hNZL*FTM;v$>Fmd>AZY=|ypcu$y2L&&Igc#}7r>;X6d~ z)_b3C^Ybc2C}R9MYv(3bK0jxa1m?2(E^5GN5Uv4tSL71{^ z@OLC|Bf{JcPB%891OAre?gZ+a;VvNLBeD=|-|rXD%+sUCKXXrOnP z4 z|9qtXSkh4m1~;Y6lG+4m{v7*_f1%0f0G19z_sTph)x2wVpH7v)!dZO0PcxIBadcGe z%_}k6vrqyH%m=Q48L4MDR^MB7q66t{*>ZoeXzqg3HQxGFai2p9=(u6?EveZ7Fpjx{ z_9Xuvx*f#GY16GHeus=utu8yf#F5NO8L4ey=XN4^GHu-#jAwk!(S1NtT1<1i&PH3f zi_sbAY^r%5JcxhJE`SN2@8Tmvb)h!{RiOSmOqsQ?DYizR*utXZ*@PKZyPqlMe|B>p zC!cGr`WP!SOuOIsm2A0T({td(YyOz*0w+Xoa88}cdOqgaf zzmU%wp(`KcfEr*~%8!|XNamqybhz9w3K$vw>(!$hZ=+*9Q@u8vs0!i%w2HWf&Gji9 zR&UiBEld>s!Cb_cX>Ax1Y^yW~MPyE-hpB>dX3UTtr>9(V-mm{fw^Z2>i!+xE!2InZ ztRLnTd|6%8gDscC9sED8wd-TJl)X|b`z^WAO)be0pzo5UvSVAI&#T;Y|3a%pdy zlHl(M`lWL$F@8gTTjU?_qUitfCaYTCkdm2uFEilhA9A#JN;ZEcqv#y3_^v%9&dkoU zQowR1&8}{dUD+Vj&#S;x31G!X+@OuEU)Pt?5q@6SjuhsX1tMY20kD2{8$Us|CWH3XJoJ_;XIgaV&`>bxpk=rn8cIb1LlrMzc4hwFUM!qw zkY{5{!5aF#e*J{zdt%s?%-M0K8u}5TYscvWu`yEHfy-5Rq87kXAh!NU-(aVRB+2j` z-(wsNt3)b~_*f!;hcu?afP6J;j1m#xHe|0W4PTM)?xSW#(%!sklPjhViqVKuHEp|2 zzjF1`n>~8_V+zmVLydtDpty(qd*5R0TtX5AZ98)+WbOts$LKNlp@2J5l}8AbU`{c2 z5$~SQ(KQL|nBv&o0FGh zvFa!mt0OdK4TIFp-m59?3x|=;EP|lJwl93ODW#tJxeAqPVLwJP%7YrrIQ#Q8QLgzK zLyD?n&lC?kw|y|4mCk^Er`S}=AU#P@OAGo4y3Jn3!5&Mip!XNL78E%pf}A3&U~qzc z&9tI|!SzSw4b{n-hPx3M;)2zF=+4n@btOp$LJJ%)oXK3$(uIoC*w(H25Rvr>a$%ihF1rm6GeZQG- z$nKsC(yuQfhqs{-8q%fN>?rHs%i0e2vTpo|dI5nDyN|e(EKE1k`18UEEHm3HO&ph^ zpRAy)0!uRHst#!?+-Xv_h^%$)L}cS;J2vfRk>i;isa~?nXe|p4*I}Sq3hE6?Si=Lh z0^wK8_LG(4cATP{#J(rk@Y&U3*xP*%6A==Au6RY*J^56c%O);(JhGL&?Mge)GM(h8 z@5_5}ENbI>uOS19y?Rb(B^O%Xyl%2&zEGq!L~LV9y&$$%-*c14TOFUp_SgNab>})p zY%Q{ci~ZOAo)B~?RvQu3Y&a>;+XjUDtEL)MuTo~N?XOu`d0{@2f#ZxaXbT(n>)wlm2&c) zP6NLiI#*!{#yvr1%&SCWVk!%X7q~X{=oF?&ug)N^wqx&X;klhIV^3kX*c@;B`Cgd= zI8iyeqidGU{Bf90Swv>;0P+KHXB#M8obTS@9<+>z)0WJ@-KbokT*_=Qi3;ogv2Vjg zCzlgsau@)FPCbdI#Ca4qrbzsZ(&k6S41Egp8vU}xbODX~2%vC%_V(9}TBW=Pg;mi?H5LI3^XLY83RhBfOXCE`Y2FgkEK#ajW_~qIKn!!USCZJMzULg+`7i z(4>hmK-!ZBf@<1Bmy3C;aq(%Z*8?SdZA8|mnKj=5MOl!8+EpnznSl8^9)Haj!o+0B z3&z$l*RnuDr6(yWVF32#IV0kIskIF1_qE23u@vta2q8c0j>PuOS;;+ulp-g)3ePj&)yFsh9s&5+_gu;RL8MYul6%UQ%C@l@y3cKL9%_4?KO^nd^gu2 z;teo7hvZvca?1XkkiJOP49dK**8^N*i|dHng;UnbJ)|nxU5C#OFr7d2cgFamS=y`S&bSrX;yOscq@cDHaI!ehY0&HkgCCN8lJHh9v1NR~0 z`E;;{d#@Z8?t&KZ%$5h}s&^x0u+d?MgGoMZ!`7N2;iA-ioF{6?Ki0(70@r^>L(uT~ zUneJ#bw*ebPL0Rj45%WBXv0@s%&&KS>2@_JLeQJhsx3ILz|xyDQ9dfD2?r;5kG#RW zlG-2#9wRT^5o-xfgTqMi%YN?pbv6Q#4F)+zxLlFG!Vo2%s`yuj-cxQYh}}qY@&l|k zc2Xdtpjw*RTFdJXrYZE;yT>wP5sRBr$nxFCM)Yf- zTuYB{gsNm~xI9GYGMU1(Yp7Nbf{ep#bVorRD=zhE8^~IOWf3}HJDki40cGurzVwG6 z-Dq=+qbiXMrC~5I3lUi?Y0f2fcA#uSuLcEMX-ONNZTZe`iPXPTCT!`EQr5w`$vn3` zM1xB1;R4*3&I5F^1i~#6Zn+xh&#Rmu8-iAC7Jv`>8=m;b#^hT8K+8N6OxZtc`q+6W zcURb)zvf&~o-LUXfWtnEX1LUAAUzRfgzjdBhJn@)G3{qjoBI($F84NqnVT$Qw5=q= ze}M`~(DThm_>8+PjQS|nE*JknzC6rmFpS(DaVZ25`9}dB~eg$xp zkgt=@Sd_QdJqwB6ohF36g@WZ*oV#V{Fto!N;R_Dlj z;)%-DQ9-aA_)Yh^%po}2FdcBVHy~t)mFG@ZsT7k`l^_y&ajZqT{#@9Bqa<>7;_Vqw zT2wsNiB9c{+39vga;9ZZAn+S%Ji^h?a6>LQR<@$xi z5uNsS9#=dhzQZ;AlrP<MvpAf0^FaG+a?Ta{S-47jKoQPCq zAJOujvwwdaaw=F)d`w%K<{nv5k*#{HAxBbxEV^MM=-OiUYDhyaWwXUy@wcMvO(Z(+ z*xk&B4jyk675Thd2R2=qi>N(;@>T1LR{k*~=wy-R5*_8B99~wFdUh69)QBWu*-gpTuk72>}yM;fKw00S{=6C;GA7NQFisZN+Ap`y}K0>U> z6T5rVNPl`knYG5)-<0 z1(uSBI!BwDTQxJL7KmC(6u}bd8DaR!i8?14gKX<_-J#E$#$=5-Pn#_zcdldUOtOi1 zx=dw+XeuiMBho1w?IU$Tk-r>%sPb7n4`9NlZsw|#DwC_(Ek5Pj#Qk0$?~Ou*7S{3Q zivRLL!4`Rc>q>wDh)YR*DAD93P`tc;of7(Mzv{B$ zZBj2jBH`>Z-qStW6x7TD&PkaZ?-Lg9pJR}&=+Uz?4sDGxT-%mqALs>=_~zgB2Dv3J zX$oVaNg+UnyDaVC5o?YO4c(j@V94WpFuwBAFY(C!;sIz6^HSoFG-Dse@zdO%Rd2H# zL^pJzcXS;WuEcv_Td*I4x)nrSBb6)erIUj&DAa&{{MG^;$seR%Z^TI}FM8qq zettq$3~W(+?P?H^JOW*##O`d@u@!Q#FU2J52DC2@)r8BFpecp$Vq2^hvrj@UzCuyA z_i`5$5}HA>VDWJVKfpaH*Pwoh+WPTKiyu(&Ql<)U&|#T{Ymk~k8zNt#aZM*DRcJLk zPB?&4&9+8v0M^6(m2Po-@FZOY5FLj)3;Pv^pXZ-0Ih_IQK56>Lhc=CwP9*aV zHzRy-l`>#ns4B2N27|wtKhkaU>YZ&tb(k1*Am?q=z$MLhpCPYRdM?|uN#peCvcgNU zN(Z0a`Y_WgaQlW}2y&VuRCM|P4gUF{wbOe^N}bTzSSAp@ul|GQl{CV0)qtM$Ft%-h z%34V}8<*)MIEb{Q8-y?fsLSZVk|SA z%Nf!%p!ArP%<)~Zf79n9`J`a^fp0_^hvwpP)009nFO%z+kG?Ke2l8a$m9!CZow@EgM@`T!Lk>k z(D;PVDrK>9(Pp^;5q`zb-R#B=xrLciDF&12Uh$I2m3CCPHQNaux}9>Yp0Kk#IqbU zN%Tt8w{RCZ=18585iqA4+4GRRwj&ok%7qCfXT zzFr$d%|Cmf!h|#~@g=+~t{CN;KFN>Kqu;ZzdKsfH(|A|UCA!4g94k%r0Gm)+r2lbg z7tGWMq-xiVVFOaFcWCWj4n=CF}1u z_*^jXFYziisl9H`AX{XzqMCoOI-X+tN;B6XBrwTVcQ< zWRCwD{&3o+_5lvNue8i$++F6*6VV;W4pnH$rjWy5f=az8SQ z8iDEGtg2FcP_1YFu&SG0B53f%owM_y2U(!_>} zulm0vo|C}=G)jn#v8$mc5*PuJxp)!1!B}>xf&-y5v8heL=DiCA9DHwF-m75g0d{&G zz`35rOBkU!T3M-hMF&PquY*5*g3v+fxS7!#qT?w(^a%>>`_X?^d-Un43?)|=oJ&X0 zJhrK$ntPl({~{-_KebAMS3;b$Mz(L6UkG0NLLeR`)M)2JdEvdaqhDkWAXFEFwZsOUs@swXW0uIpbw|C;Hmia7fSNBMP!wcb|Q!&Za0JOq74MLP|UqJnH>kvz90S`^8 z6x9v*lcTL>+FD!5ta^F@INqHplPnHPVlKTK-8#lVX2vpcplZoF)eC3(KfIL*(K)?x z-CBcb&gYWl*v^J`URdQZA49o9_jD?6C9#cl0iEY7ia^rU zr@dN1FmqlZf>EDt8&9)Eikb6OsWfDXF&QM#Mr~uO+J&BB6po=#L(Ou@p*6zcwV66i zU;mQmgfn!ouBJ0+_OrLUf%C`c3)S~U~x}n&uky;&Km=(s4 zt?FIE6xsw;tg@2BV%KlBX#8dw)iAiQWATLQuSP5Jb-nsB3mln|%47l_)|Az%Rri!? zy=JW_4mTeQ9cSn~tTDx!SF}iROmUXw$=oNMhyQUssX1-erR=pq%3sseQvJ_y zeTPbzk0t3WF$snOd7acxOVp)|NqKCH?=8bxEuMhPJcZTIzGxUJp8FB@Pi|TC{YUPpA9snd1`;k zKs}{zn?A(SQd!dq_UO_Wu7w_i4*u!gaF*)723MKXFMV2bNQ4Ux1!ff;txnoOeBr_y z<1+yaz6}{J9U!|*O|rWH$b`jF4quv#h6f(%TcwVPH>D?adO|)m7IpB12&~*EC!Q3m znfBucA0_makq7l$fNwQmz)XHMICyxADpM&|G&s6ma7VHqd<_eBgM92)d#rTwd=5Lc=z$CdQ z?D&E*zDH`U{PT0gW!bF}e=$$Km*#;W?eQom$((&P4h3OJ5r2k;{P0Moj5VyPAUgOV zSm~xW%B>@zp*Pn&6hitJPbCAw(L|=A0i|R7m6-iqx2GK)d!98Lth>a#C*}6$EVl($ zD^%Z4>iE3oJ40cF=aA5)vwKlgw|5L-U(J?B)b|Z0rLcXj-fvzzy8&?de9T0wjjX6# zd<a++l36a1njDug9&NI$ibHo{WW4_karEb7t@PNo89b%8912{ofOToh^;-4m`z5F& zq;GvBu{bYoC5n&+8)mLnHwufzHL8Eq51)wI#i(N$_J2BG(GEiM+@(~o_3IY!5W1|1 z_?SHQE@B}QWZ^zXL-i)x5=|)XpW8CUFc@?=u(UvuGMu(>B&yoNcZBv~Hkg}j(qEHsQeVK1@Bsr&cusZt& zR=UY%D)Y-5PHSWk6t%b!^|`k)03{mVbw4Ann}!He?;#L>f}^GYfCB@I!*xC_%~-Sz zG_V~M*oXLF|8&8CIQQMQf8O=ij!^6h?nn78+St3r>O6_rJs#fNMN!d|^r4hl_iU8z zhep~$rHld&w^xQPXE;DkdhH`?szY+X0gF8=e2kK$ao$G=H_ez6nPM-~n7w~iH7Y*| zL6Hg&g^Nqh{0imO?}zTd`!n7#{%1#3>%SS4qU$$lcO+y}8A7*C>2;?Hf4DM5T;kn>H#!yj`FXCprQ?|pM8TJi3znPZ7&8u=rl*+YnKIuO$fq<`SWmm^P59YHODgu0>7nm1Zido#kS8H| za-Ef4km#IBx|HoqjPYWZlX~$Id4BBTf|2M}Sj8(iTjPb5$KJK0{ zny+pQKRJkDTPF*9P`b#I?B=U=`xz0j%B5CEu@7+tV{w&cU(?KAWd?o~ z@{fXWeY_Lpl?T@bKYqqTVO@9+R~RTmPcJ;i{%gMKZMZd z95Oz1o`pr1yR41fO+3gkR0ek#N1rD?6aVxs<1RouNcSb3a{Xg`wOk5}i0(iF&31Bq z;wu>e$3Aa#$6z}Tm}jdnjAJ8v%v@_H{7Fruw$P{n{oWM?{0%6Hz~NS>nU|M)s^?Bo z6N6(PBEFU?r_+8ZhGWSs>lAbOBF;e1MJ8S`wt&JDy0Bq0+SCc{hYMfv{kJ!C&p>!; zg6PrK*0OeeJycsCoH}1ifoDXha7CIXpW>SK_yCKx+p(!H1fMiAYqU>!NZ}`6gG^p&EkG&t^<%u2G?YVO_ytT?uX2 zocSbqRp==*Hi6@f1HXZWMcN)BRFN`D0W52^hEm{?#=@1lyj4)DRnUCYeRC>G5fexdGoTZiA*4YQmZRzmHUOhk-~sujh9M@eQejd~;t+jLD% zz<%S!LW0S~JIEO~=Na4oMfW>>BREK;2;7aZPK(p~`Xa9iABD2G$Occo@S+}1_NF^Hr{EtQy5V3qKBY2b<&yx1-a53Wyk{b3K0AKInz}^$3?>B?q7(8ObALT4p+fEZ zsFel->{ehJIxJair{BcSR7vBteZq75%DBSgRKCvv+c%{lv#6D09XDPNmT?J=M3S-t zI?jNgr_@NH&=G9E-Fp?EUnC;Hdkz#VXv`wvQ*tr>i_W25onhuC`1NKZG_q`?c=V($ zJD>I0NZ9IseZRn`m?Z>9|fe-63@h+KZ9walAy8p#m(A z_0O&zaI@J4;yrmXsJ2)a&sH{*FdE^WJBYX?y4+3W8^;6%?iLAnFOdtg0ZE%tI4lJt zF7^Y?QltjYY#roxu5k`orpprIVmAk+x4}U>ML^}ZDS$-XGDQr}x!1Xo6m8Y0Lnj?GpJ-Y*yt0x_5ICebc@UurhMXPY{BQ5!pv*IMRl%op3i0Qr!#+ucQ)I@zHaU!-td3gb0ZBcadhxWGBBA60H`qK$880i&lnwx z%p8L&;LcAj_fVEzr7RSUKB*QiB?34eXVUt?g7N>ZE8TM60G)EKG zuL*o0h?>AEpL!m5*KfzmlTSPzr9B_p?%S;2)(28+@s1AQjgDXnqPQIn3JtiluNV(# zW-dYLrJV>rJh;F=FU)1b1MgQBf!?e#zZRryV zzofX`;|mQc9~p)c`4VBhk1QXg%e!F`CdJVdYY)z=gwisk!30UAQbX2dxDNZ*4f3{L zUyO7=9ZBss*FOfxa>)4d2L(tI7eLbN*~OVN06%%L8VJi>`A#k( zm|sHX`E`xwGPj*P{>w77gaS)*WRS?m&slCyTcF%j|Iwa!VS0jT{oVYBZZ1m9XCDn& zZK(Ey@#D*IVDHlje5Gx^M>7oUHH7fvbUt@1@0(1cL5^s5IpqM5C_-R~0WPQ9%Y{HG zhd8)_W9+C5zl{8Z(UYVzSqDYLcP3shJ>!;4y9be|NWQx)1Apl*ZZZ6*K`YbsOz|tX zfBZ*(Ps1iklO$wjh>TPB5`Y;vekJqav|zRLgL*XzJ1Iz9+g>|68_FPvg_PXOkz91t zfgu8Qc0-&1a9iD3AxETw2WRUMaS>Qtr&yi3U=Q(OE5WU^EY@*7#p2a_EW}tVr>w{k(XZ!R>I!z}n$+#V#CH8-9u?z<6dXEr!$m7A-%9 z6G&T)wH*gMHmPpR!1*KZFd?!?DSDZ8Y;#6+mU-SVu+<{Ku_i3O9I$1ff~ff%@h|Tl zu{NH#+9#IFA!B6r&oa$7I*JW0o-YYlq>k|vgJW;~RWKFgKGAXoRSmJMG;L1LjHnmZ zUbRYodfdN*xQY$oo8zwI57MAG(KQtNYw@|B0-RwIpo2DL2JPHF3{=L2DFObR0}lJw zmR?xWA4hocr&|fOpB2eZZQ4%kKUgd%0hs+L$vrTtNE~v0eD;JFCvDHxXOOP|$|@={ zLd)6vV|87o=_BCdqTrJgDefu7*qU4tbTaG}&LR^aA?gl0o5rBC9D!-M5a}_F($vU7 z^FENoG9Zy?U+`Y(`*I&T{oNyVc*fJ61J7D~jmWReoUV9dU*2qNwe~K2*(2|AQ{~au zbnD;E!V<76qg~U2L=!3OYk9gdM@P{hjeR}%!r*aI+X+|&TxrZPwDm7cq9R|8U?1mQ zz8$QEfp*47(XWXWAf&|t%4kHB>pC3kt?zfE0dOiX^PUA5xkHcDVurD2_3}!8RBqC- z8}SbrJ|Q2}UoI}IWDa;qO9jjc0^u7enmI+KFz^)^&cF5u$n`WcV03~Sdn$-li;EuA zd4yWqC_4vcXy=Ha-UxR~H*1A^bE>5N8NC3CuBZ8PfgJF6^Sl>>qAPKzRr+eM(m(68 zZtANoqoZAX-(KTzF#b&7XzK^fbDz~@t-&}`D-3-@MQkmLh`E&--$K=xIP761!-UWW z8%T8-6lsm*{jmX*a!=+g+TJlRE02X|nimROsKeXS?7z15t#1|x@#d0k`&BW&b&vf7 zX2pac=VkLstd<5yFrmlwR_vk?){u6XA$LXKty_JxKxo*ppFv8eJu#Sc=jiJoPK=Y% zA%#Uz(70J}Gkrr)!0eEw{hc9Qo!d+?zN7_KJfS{805WjvLqR10VhQ0q&{v!qiC|NA!@Q8&AU0|7nbs zeV}=s3wQ0iTyt@h!Xbs_4Oo4~W~0SE=TroOx&@T+nMw8a9pV=3s_=Z1N{XE6mJ8py zv%AFsJbo>cyI{|wW?@ydijtL>_vmgxv|XY2v3P7djS(V$Y_ql&g?V3m%v}jU`!U)4 zhh4y3iwD6PBTI|=EIFT=>-pkKMl`tz4#OQDPQcTkMG@3FSVBpS(ye2!1#Z^=r)Glg z2A6k@Y-Rq2eO+|*G;vvyywXihhh~cUm4FLW-|o0_(T}!aBb~<7*yeQ&x*n+#K3J#) zR+2^L+dy;{ZOGj&Sj1WcjlmJ_ErSv-COr6m4!DhpklHI{h94UB58AYk8(wJDo?cGJ z!m1##8e=V@9Xz6~gFb+_pbF00N3E3qH`+8#R3E};cWFdQJsOcuswOj>$OUQosMOjg zy&3u+SRkt$_jQn)?vP>ON4zF~IA^O>SAvLolvgaMz{_L+b`+zqA?}}s1{ri^k=;=) zEc)OmcQejiJ7e6k6T@Xa*d93S-7^VzgHU0oW1W*}Fhn!>o0+$boQWGh#oi*bT?E$k zrEI!qi~89MIFxJDsN&_TfI<4Kln+eSsP13P1&VSKX1mx$&wOr8>tB;S4jfn~i^q8p}te*)o z#xamyL7vUT>ih)|7wiF%P3!8Svi%eYe+5O=wIfmt6@b*$jeVeS5E3}3RPpkwFy^DX z^i$d$mO^wzWD-!Rq__!MVhg9H8hY6{vkK#h-#Ocm;uQwux`nh`-S z#Wp<_sG@`tSbmrvefqfJ zqUeiwfxMj}ml{d;w=w?AWxgPXk7ok+PkLWcv@@w1s60{5Wkl^mDbXqjI};!u0r%I+ zO!7`wAcwFfqS{XC)C4RKm@+X5W#BESgWz+`&yAB||3hWgD91Z;c!AzA_y*Yvbv+CM zQ49OG&@S@(>2WP0;dszgcx}_J;uy#?F`rD+?k+hK?lY?Z>tPLi&(&v;fUNzlP+}_|7M`{uGQ=oZ)eUf7<#y~P zoP|Ds1J7g|xt75#n>{i$;^8sN)^|_%IoECL^}8W!g9a}FwL2QoESDt|X9!~^{FzUl zpE+-}(ZN5^1ww?Ev5PHCKPg)$pMuZMstCc^L$Dx{pjVyw_(WBhGVqhvPZbsW&5A=y zI*TP6kNRX9hODT@M{>Iv-#W$9HC=xrVGrYJbf?n|tFtXQQlJl?m_XPvX^r{dHZTT( zO_HG`A7Ezs=byg-L~t*E@LU=!YO6Ir29&Kicv*)vw1Yw^Cgc{Yh$){Dw<)N*i9DWM z`yXMgg&L{JbNVAJINI+Z-i=rMo#x$y_ z61IZ~GEj+qj`7{~!j|j~8K{Mgxxxg-XHu5LmcG;k4y_UwMB|HGQk|m05~B2k=JO)F zwD5G4RFZ@vReY`Teb}jEkutT%{QpV;+=eCk`@y1wwVy^76*rJHt3o4XYp3v zFnRHt2FY3W9;Xn{(f1w7h&y%UQ3>Y@Fe$|boNjH0{cWp!FGW-KpBPN!aZ6m(PuX*O zcIOX|0^y!cQu7RsVyjum{Gk3jV`0UN1|yG695J~o;?>e3j$c0zO8R3nN$-i)8}s7h zvvuAAzQA7EC5C38-j{Qwig_k#9m&n0#|OPP17~BhW1!*q zJikQ02du_t8b))rz%Nu(iy!1WroPB`_{RrR|FXgh1rw-c?BVvt_VuvsUburoG@L)|Mu4?gp-Af%>Xhs6CV zps^jWmRMC+O*Rr5l}u2TatQ2NF@}DKhLw$l3lNJ(Ss=<7PWDn2Y*IW~rKJ5cEZ=!j z-&MJjL>;S~hoS!+v)YzFTnE;&&XX5#X~R@8%-tKiOn%4`WZ`HLjkX;%`&l^s@h$5@ zY#fn%r!l}bUk)X=lx&SK%(WBX??&gv^Sht^ifsz4wse(Q3H|nk)cV~KUqEkT7PS%& zEo;m)DqLcB<`>al0$Pa)LNn1fn~@3#Rh%ffRrnZTEF=;J*78PL6Ad9vD7REG&IC04 zHJTLwhy9Jp8vS-FXH^??01jpoPw^1ar1R8wnhaJL;sI>*6V6>D&lB?8r5qp`+8LO? zihnPoG~}9B#QG)XgONxv4WLRTcm7e%pZBUhE;KZf1w; z2L3f$nQhWs-p0*07Jz@r8W**YL87nOxQZ@xQu=#mb+6VzZOSQkFmLm@1mntCaQg@ClxnHgw7dQ(7NS?$Sc zeEF$P;jIe^^47yu?Bbv`h=Cf@M5YJl>}oi(pDCeB7d$T2pwu-YWl9AhAbirOVmk66mtW0-#BvPGU!$|4c`9ZxL`D~+~Uhm2cD;8G7(FO<33TG~Jz%`w- zzp}vdl2jJGt+!-9(JUgF@0#d;HCo%&x#L^Z@!@la+pT1B142BR^fOI%Kp~py#8xNp zZW|xt)W#eGDv|o--)Hb?8x_eseD~vQ#wKX0EnTSa9 zl|N_xeHS{;<%#aRBH0QbI|*ryy|JOC9`F!3g(vPFAOSGaW5{H*W)@uHvbGOnFNN#^LIwn;OdINGn; zTH)cC?POJI~Cmv1@}$F)dvr_Q^`$&mewTHGH7{1$IX_Di@$Rd|a=XnNy zBE4*0F#LKpH6jYxPbtc6ge35gOu9?$yMj^Kcm|ooy~6ebKXfZ;x za}4FIW|!*D1s%D!9=BKb7_9UINE6Q+JvtE|ALqu>i#h?rj?` zi0`Xhz5)+4vu=m~h+|EBcT)hIwZN;gr$VjrUVOfJOJb&S?Cc{g2{RJyOKY+`I*uN@yc{=T?<|!Q5 zy)pKdl>XNoUgYIIV=H`R_kxHhHem~IIblqBCNwiAlAB!|9;c~6Cm8%*j%80t0Pe;d zND=HF{feB1-+H@!)#RlTaEEF{=QAPwJO0hwi_aVXXHI``HWu+Y%Mb!6Y~;DsQCP9u z!wm}(%L!m>2tZlU!kl|x>pRgU{@#f8`|Fp_k1;Ww1|P7t(yjyT-VJtC4*C{;j(VTlNGdHlZn=znqd zp`X1U$ODF|u(C0KSR^h@zpKYi;i#E89#KlYno;OyUZ?l8Zs=yjGw9WIAQ*?VxB^RR zCBxgi_wH0gHF<4hbbV)x-KW^10&HKON6Dt)VSe5{5wxscaLd8qwPk^wYP(I%EnHs4 z=DU0x$`Z!^!Vw`Z$@{6MPGz9qm4V0QkNOo(^6l3@F&b5I5pwsmI-)3)R#K-p zO&Sh3I*UwIce?h9V4)y;R=LLnj?aH-<-b*COFI)!wQF~4XtPH#wa|dyjmVJFOKnhK z_iwQIbzQiKo+hL%OH<<WL+~B#1*M0j94usl|4w&5G{o^UKl@6>Al^++tf%DzxgpAX3m|1FSCxmAU4Rm_y5DmOhM*tSy#K#nW)k?Mzj;_ki(XXbzjaGpmM(gax|8kDNB zs<5+V4LlwaEP2Vle-e2)xYcRN9CeW1B?3VzS(FAxI!60J^8P}C+-OD|zkiP6q1AoZ zqhjyhNjAafPJ90JE_~xt%*V~mUwUCa=JfP^kN_57xvF>#R-|6M)yIbm2Okl|USQDk z(^dftFWsgQf0?@h!7imO5eTyke)47G7tKpi#$dz!m@~2hy(NngsjxYmm-+P*em~a~ zXW;7dOm-~S3ifvBwHmbUo9}6ELW3FhdVIm+M6gJG)gGldQ1~Wt~D-CtMW^_6v z;4k*~y{eGz6gj(0zIx-lfe_lwe;IiW=cRt)wP9*E><_t{G+3(C64%2SCp%0-R7;|T zfDX1U_G~#CvHESUV6@`wCqc*GVCee0Xke3cYJpoShpP_u`Evr$HeV=Ml9zM0Qb&XZ zy|w7lPfk7@qM~4>ESM4KZ#92K`KrpTsjm60vGvrHU`Aq2}%^(G<`_3>JElX3= zEs73n99&@WMyt@FE3#nUYb--`cFnImtlxA%-OO=B~s?xVc# zTHrV*J%uNotG73Bh4BHxn#~Qt6q5bJDHC2tsDx}|Ghzo!sX!B!Wlix5c4|n#IN;7e zFATta8!EtXM_t@{_DiC8~GA_jl z(2i}wfb)ABgD#<_;{9g>W0T#E_cAx0IG3N7g{zY?L_IjKtLJP~0+@HQ_^*1yfN`Wp za5FS*YOR7ourBWFmrdlC@9oS+e`|*2xngOWni>Az3y768zNQ%aHT(u>Up&MmuSM^U zOP_cq3VEy~g%t5`4OAObQV@NVuwgki6kjE*{=?VnLNnHj?eDd@OB@t};&68-cDxwKbzK13JC9D)zn6ozTHoujG;~ ztU_WF-^*>-eI@~htNgfIw;$lcA!9P6jgVjJ-FJrf0xj z41G3?PolJB+n34hMNd~txh5`iE5!b@1NbVc3eZdW&dzKtNH zh7NpOo}QzeGh1V`(T9L}#qkHS3AQ69ffuQw$6W%lP=CaKja)dHJL#M}8^I*{ep9A< z^M5!ZGdffolPl&t*bG6hV{N%0qQN4%0tpct-@M%c+fAKH=hHSM#NC# ze53!#YMOBHB@=JoQ89-E@#3C4pO~urSU#MD3D8>KW$Z@;G##fN#CXR$I2iy_vFv~f z3N&R!K*!pR*lbP6y=PsFijkhVwK%43`=I~xe~9j;g#|Qc1is0)y{n#$aPdX5wUO#tqUJ@f{&*MNUrd(G&bpd9=|cu zh%EK%Y`@1QQF(bm`aXplX!fHUvI6_y$bjMeymXVjmeaT0V;q_F!EVv+BHjt% zdw7<^O%_8gxIp%DNb`CHEqONL#B3w}|4q7U=?eHo?M8<@zfC9u&mI$Q4md2Nn=#W* zGdh$+pOZUASyKn@qsqY=Oc)fA-@T=>XDvGi$KG~4x*xeC3`7m>=ZUhWo>{@lNy%8@X|`P~`uY7lj?8^U~~R#g5|V)uO@ zY9h-kToTCrxEtHo1f}KEqI_gw6!Z=1D21>14)Z8vY55MjOdo3tN6^8@H+THvHuUQu z3)Ok4^(YM}D{-_hOcpB)`VulPCRBh+A?6y3f`U;B+zNuHFoM}9+PM{)+~;_&aN;^8 zHavNOoEKs(r&2nB9+A8td$9c^x|Lf5cjJk;~;f85G@=n(bYEy>axZDI#W^jsS9wpGX%S_NfWH|?}S;vIpGhQw&st(!@AOVte{ zd64C?Etuf+Aa=#MOw|Svg}I9d!x=^`s~qmGT{}_C4X~A%N}Ga4=s5W~+%5v>q$$tni5APLlh(or zZcXUL46Kagw{8ex=}@6tf5f&g#&Yfb_2TrSXw?I`1x8P31`I=+10i*#+GGRP%Up4< ze=*2%*1#`(32UgGkjppEhQ$7Y>}i1CSX5@;@^qW_hJoPL{{XnC4_T4S8zgp~%-j@| zl#P5`rbuS#W%fP5gcI1v07>4SbRp0kkC*QzT@@Q-VO?Y+m7AhekhuP3`fgd@{el)% z?r_!MwYf^&)};U88$i3we;&UWAr})z zee5)`__E=pN^L&HlA>mRPz|~@kbEf#e2K4#QFhg&-nhm@&uHP(n1h51yp`5q;qiTL z@j76YnfsjF0Q)dzmq!9${~6WH$9#agi{3e&piG;yNONC7m<*T3Mz>@NBRd?Z*b2go zawk^EZ?nJ0_yiDsx2f_iOVU_zB(m?V+O*-UkCvm8DKbt(mz>AtYXA$-3WaO^PPG^i z_=?Lyr5~&>O&MgYlkSH(bwjp{DOSf@lo+9QjPp*mOUMOb3D}lLCea>ID5q%)1n(y? z(yJwR)zvpwNL&6Fv`~(5cmJwY(##G8XZ=zwO1Ln+@wJkg(gm)i5*i~`spyZ@Gv7%Y zXfG+Z-Sm$ddV-PowQ(4iul(ryPr)cJ2pSp!m>m+e^586l-zF|LQzHj!6Y2Rs+T~%h z<|jQw*YkHD*seh!cA0T{mAHWW1hiTYV-rlGz`PDOfnHIM3A6H2NS$9+D`_85lB>4% zhc=S;S{ddh2m+IH3qYv^!9o6RFOm4xdeqhcq@Uk$Pd9*JZ~S~+dkDTTcVy4|&0i0s zICzr;%#`T8JQHdcLM;%Gkm!OOi{90GJ1!q%+4;hMh-N>XuHtKmw(k)k8@-E)z?+-J zrb|RY<>xh12}GW7-ms;l-_mxpugGe)##{HqX5S-Symgpo&(Ztl0Z&h3RZk*B{*!IL z{8A&q8jK8)p%JM?9i#>^&@9i`brb{u{xm6H*Y~y@7BXIZY!K$<5au5ukLqxY02;Apc=2y5U1c6%hkd@AfKbVk>zSkJl%f^ z_S3#|k8`3;7l!2z?p>Vcc$?vz;pgbR@bsTU64%d5jrogltK#-G5VT06V@_N6dM?|X z*Z4szY)jpvzhH5q*pTKFS1vfj^KFbyoVrE3EQ6yur3H=P$G_XpU7e-~4o_w@H&V1J* z53O^CH8uW1DnnCEyyEY`wZdom)w-sBKv*nU^i1m7vs<( zqJ+ebb&w>=kST)qJ;O_P)>59&EC!@gb{p2bC>|ndc{vS}y$xGb;jhOYj;0%a96*j*wuLH!Jw)G?rb=w1RIVR+OJ^&k8D^M$}*D;ge>%?9;r~ zhaDQP-c+(c)te$TSPWwhiP!M88>>`bFrq^sfq6!58%bLVe^Y=ZiaCQ@utH5sf~b3R|znywVvlkthf#Zaf;F)E&Z_1nYtkr6-jEZl5prE z9qM-drllC;6mYxx&Cr#a-GRH==Uyg*5J0#dJ`VQZgYj$4dq#}@!wi*Tf&^iN2AP6% zwM_{o@^uzHd20!Rx@5Cw2-*!LTDqvw)rY}gJLB!~-0EjCLk`}%Zw2h+QhJH67l-Ro z5WW(G%qx!XIeJneFF{jEm0jKPrX9>nC#in^O%RA2!hVnE`WR`7YH3%O-C{Z#4dsZ9 z6|wLW9qi!4VC->TC4AFMmSYi_>63~=o6Ewt(^3)6)do~MI1d>{zSVKCjZICm`Lh$D z2*UhTo})R8T2?A!a~H$ac*N7QDhPhhLpI1jc*q&9J9ZwtFF$-u*mPQ5J&>sw7eBxW zcI2Zg7xT(^fPOL2M6{tZ$s$=gB!YOVV3^$CmQp5`kDZoU@&ZT+z9wv=$=W-Pd{}{2 z)5Sv8jzY{AZ+__5V6`Ke+Y>s`#&`(^sq?&}qh1oz;s+SBmMl1C&XB(QYOWzsEfteR zb&jr&ZB4uS`>!aY=^`>w5`9r+324LuV@c6dN*SLp#n;6+bG zQ|cRMFI`NG5?JI=WzDX)bf@%vCZIIVDI2JJjAzy^;8s3X375R>w$J?9?&ld%XEx76){w)~vP z-6w#($*rgkA5-z!3fl&aDbQ3^we9)0ja#C;nM!@@(<#}6azr%8IsGDSwf4;KlZc`m zy2oX$d=w0>>QX;=4oQ=t!4)SMr3+MZO!HiF0k96;3vD4=cOjOZy8NRiYI*BSA~T7M zV1f^t(l_1QRoifu6ZctFHB-MiL>J03$_HsS3jerGo{KpKOAvrCxm`BrM&TD%87p{B zNyRsz|LH}1YBN!EDrXc3UDXh23FwWrwdKidh8k2+Kw7R$4{9r>iH@-N(2l_)8hN>5 zlT=tBO=F^V1q6rCe4(BZgdLq=wZcNfgx;;_Q~D6c#L2@_$bl@6YbG}qF(O$I60#p~UbR%m3asX0wAfjrM3okjd7f8aOvXRrGGWu+fMb=1e8JR0Q|Yn z9!W0=f{74%YM6E>rXiKc`UoA9!>AQ<=nn;^5{MT-5LgY6vbpUZQv8nrhe1J}RKEsn z2pt+%6QpV%OupMo3eP`+1ma zwVhaHxibe5cB2-mB24YC6@Cmd!k|oiEE-R9* z&JP1DK+s${>F1f6}LXmjLWi{NSQ2qkNfoDPjIC=eF02yObYtizsVjz_dtuG56dXf&h-7o9rLfWIc2^pJ+i|5`a)jlgjQz~i%3Mc1d7F#m+?pS9cQ@j}; ziJa4*_t2oXE(*z_rbE^jJtc|OAViqT9;U%Nfs(-I^(fgd8%+>wn2J!@aR>E@5>JY{ zERmhJq(|En!Kw5NM>y(?sG^X&Hn#8`Per-M;x=_!AQ;D}rM5rstVn?B~HM!0um*G2nn9GQLf=&uZ4<*=yc8Hk-fbv~W$%rcKG_^e41$4EdT*VT> zdEuyJUqM#~t`Koyd3ec874;IR(Z_;eobPeHPDPAJIllf2U$CLcWUMy}skn(P@2R@% z5x#3Jh``_rwV064*Hk&^kECz;QqX%f0a`0YB{{n)I0vQYI)@lkqM2mE*mysDN{T8W-2o zSo?62DQC$HJ*(c2V@S&yYV3MTwIfwf{5SRcrZ>eaS~p(02tw0C0)m^7$X>F=|0%7D zYaZoRKme4TO)FNU8RMu9lf*>7Pg*Eq|oaKJd`gz>;Yo2r? z$l4Q*H%!7EnJ<)E7o2k1+9`Aln`Kv>2|^AT(LwI%@CcuL`aPxPs1r#63SA`}{WaV2ttyHo2*Em%6-d=A<<(GK465H~Sw z%lgtf#Fxsbj-K=r!(v>BA^DJjE~kLX&mU9!mO!CH0@Yf)nX|7{BZ=_|hpv4v{5Jn>YtaBu2XB94LguqmlqL6!Z+RrL{{LqL+kR5BWsT=m;PEm90=KGJ zqbQZ*Z!N0q++ys-ogbsO*e&b2C{Q;;7U~*F?{%6% z?k^2q%ZPgKiYD{ZZxn$$-MMI9&juuQ`0U9pGu>G=a0Z>E9A&UAmA5aL!>~+dWz=+` zX_l@zFsoSEVfp`r%DA&4vG>tdYt*tdA|1S!F>%6b+^89IhE}%h@t*tWc)B21cuK4T zxI=1DjQnETFgF5V#5W9oyv|?Lbh->lRsHA37||kp$ln_GqjtUv5!14`0pM_xzw5LH zNf{=(4~i`>%h=Ezsbx= z%<{hEzoBL5DDy%hMe~$;PmI@-KGCi`*Eehyc^a^~xVR{{eQh_TY>1}T)~W>vS!>9_ zFpkO%64AkrOmHu3z!!tG-{X{N?P%hluWL~A1QLBHJR9hAj@t*I;<)*db7>|AKZoiP z4aGV8!j#>GfVUK+8Oo{50S+V(_P_O?fmHFXkI7xmVHihbcT6%E4y}NHO-@S-B^1=A zay78y+ah?Z-?-`QFzYE6iSz6YPQQu^Ns*T+98K1 zKq0(zYnr1_^K~Sle8!`vq$ozh(|hB6cD%Ak#)ubuLt7wgnl>z21yklCrxt4<2$2{l zu>~_TsPf{r?`Nr zLlQlYf;blD|JKtEwtLWA#>AGM*h}+9_wkIgy!K{ za!*=a)8{$Tx;xj`737m{pn58hh<&_~bVa}g+T+%_nFQ%DG`YVLG*e9>#3tFmq1d@F z6&N!+;DteZr7m2FbPAK_zl_7;QiM++X<+cRQmD4 z3_FCLzcOGT*RSGm)0N^YGnBj%NaLaTvN5^qiUZj{TOmrolAwfn?-LPr8wwtDm2-By zbCJ5>D6pkWn!|o$(nGQCwM$j(c!(HunAXvVr$UL2&Z|dTMdL|pBR4=nn&T#JcLy7- zv{{*7fl!n)w5MzoW=w?>q*0C*pxToi+eqXGi;2a~JN^d1eAjluR=YT0@E-0Ci935| z0LDN}KHCrRCMlk&7p=_wH8&1~QXv!$HB+-T@ znj6WDV_)8bpT-pMjgHR0AOi$Czu(`pU`%c<)Q@KY<~n14vvl{6&3_!ndP-t;b&=Q^&oTF0m!Dt;@?3S^ zshO*rhOy%-vsYi(KbKjP#RI089BJ>Xz1{HEotGYJbkU z_iOz3Xsyp)r`ngk4H5|?up5d3Vr^r3jVJ2*#W+4Nd1Kx6&;XIk;#5#!zPt30mI1o; zNB7{D#nxM-fG?>?Z*igwr-G{0O|q(Zrs<5nbVj$4h>g0->`aVbTi{|i<77TtUaFcv zaWs?a?|b;@SSm6EbWlX^auFEHvWV%9p0Vx+P~Ib|<2N)Zm$X86xxv=6%%4MOC}4Qo z@ghU}qh~bZ6)R}Gk|q@A2@3;s>-hf+L){QB^oBWN%1`oRV8jAWF|)D`i!>G(&A^oJ z+DRf&Jq+VVYs2fg+!QgY#sB5evd##>nf722b1ndtFemm!O*CP1kvAKF61Ufml-Vkm zxnORDd4P3-T48;r9S2 z4vBr2^}1Kt{_kbx z)H!PRJI?i@n{Yq16K3-Q?BD9pQHj9c&e%*7Y5tHQwngj0v=Q8w3~k&S7P^`2EnOVW zdetxT)^2gH?siG=432deA=b8-;it!beSPU30{#!8)lDvY&(nf7rN)2;M3a#fJj`ygCs@p-$avBb5n8pSZ~F)Bp=46;bX{&c$u^l$#_H$uwjr3OBzDIeaIMfj7cCk`yu4(l9UPU$9||*`MhnJjBQ(t_DR68u-+@Rrf zUi@Y(?L+fHg8P6KW_8$ro|3YfwPU0e1ZHv!+H-FsZz^~PlDyAelS)oG&k8C?c_P>x|jjfawwYW|sb z*DYBL7oNv(ZLzg?s4?I-~p+v=CH5`er&y=Mwyh1LuchkN%wGXE{JFt(z(H+X`;YqZ}`*0`0 z=C!T96ap`!oRP!+f%~3U2u}33LwQzk{F@l?Sz3X(p5`OI+Uko8>^j_HCNxi=ME@F= zz2o_JiqJh_{uxW0$;$m) z=fwy!^?u;u7E$@eP<9dM`q$CR+Pt4q0r7wE%s84aDT^Y6vi7@Csa6nSBa^ir_xq@i z7iHpM4UGvQlu}2A^bzVhUzug|zlk0_7hw4|BWrSj(x{=p%Vjr2s~ImX+Sj(W1Ep{>_T7BRxIf(x9{;2k+I23w{+M#7P`a}a-u3FE z*M};iA1K|y8rWX&@{NrH0`;K{V4gfB#RbLj6rWpVAgmB#M~ zClV3c)&|168+8P-FdK1jiUSXFH*g@|&*~==Hm_6}_x8u$j3&zre~#}XHNk)^19LEW^k zMIRuX*^C0wrv8gMZ&I(zc4hhZ3D}^~8RwlNQ$B=^N{1WAIN1;wEaIxtDWigD$Ut9x zj$1D{C(M(fA2O}vHRc!jcz1EkyAxDE9jxta!$PGd5u)` zW;i*wqBKvCf;L@2e;u|bJBwV6s(MWiIUXsj%Lz~HhE%1ydQA3JYf^QKVa&|vPrv%v zL|TBX(WUC-9bo)LVudl#ELum18lIc7$NECxwN8jdqI_q`7-6={BJN9xsjm%jxJR73 zU$&G%S?}+YZE(EStM+;>Xl%rQD2|J%`NyY6`%A8Kuox2fS^6>_V$l z4mCd!L@~xG%bta#QWC3avhA1iVWyWIs{ed5hZJ0f%0K2AZ8qEU+Iz~lO)@Y4B9MHn zR&@6pQM!=LhMSVpd^IMa^M+F0kz^UariJ~43Ks2nw09Q#;i-upmJj%G6;6R4P2`9M z(#QO)MO>#YT;D^X)hjyleY2$2H$Hte20pPRTnaj|JBflEmmWk+;4ISdZWBHJ`BO+Z zSrr$~PeJeE^(Co6NnxrO3>vM9U`Bz#Lmrmc;HvmU@T#*pQ4e`ipKMmdLsdBGRw}dZ zU&8a&c-Ym6wk87x>MrMK?}imf-DhO|xurgPx_%NN=EKyZ!VfDi4P0`VdlRk-B789N;(WtZq_*M<)9D&a`(zs{AugvGtiRR+%Mv#; z)$p;xzO$0?UOhGX`W1fjU>_gM4og7_EH?~jGSuCi``d+*?z#^l|B6{{KPYfvVD)~qn#NqEy}6A$ zi4ny^_fmP1wXQ!SrFUMjjDzO^ek#%2^K37=GaYJmSBVeE!<^=gpK8M`n|Umuw10(a_}7_q%fWIic%bJo=J! z+k>lhxgzfMa1|_mILf83x5NQT<@)y$jnS$59kUZ}b2)n7LE2+9cs356Vyh`_v+>iZ zS{FQ;e+$C@Pb-_yms-+5awm>*KxcPl4nkGKr3EV2j5DpZ%vOThsFDFp;?9LM-E|pd zJP5VYgD55{@N!MyG;#$Q!>-mWG+>DC6Ezny1wI8zvf|Ob(?cP)DduX^o;NB+ccKXW zV#AxGz`6k$Fh$?9kIk3nG*ub-b#nZB+Au8&3y6;TOpNOU#qVV%sURVI`|oiD)u;S_GI& z9(IpG+;MsG0X}}|n)5jj$1kd$yZDFoABweslHNejw?7D(w=Ev$>`)NNx|kI83fFjB z(A@R*?*IDwJo>`>2^6r#riOF#DhnGI;k?O96H4h(hIqyA6TG`B)@lK&NOS>zwM~rZ zP{D7v72Dt}u{N{c>>p6ng05?xv3KaeWGpwPM$*~CednTH)7P|!oW<+qU_maNk?AG< zE}N9qkk&8(SKJ1A+ zAGJdcua4m!8n_cH(jEZH&c(6Su+!J#GJ6tCaE`LQDJr0lqv%Q!@fGN5&tl)Yd&Nup zAm3HZPP!&e4`@;G2jKcuQAtLCmpkEAd5!To$DPT`MvF^*YjqI^O;Iq2z+rsa2}KLj z35WxtNgIZVubPP(oo$UY7CzLxy7&PYG+m!`NexfPmhy+CYsnD6udA2T|C#i;^I1;q z^k|P`rhdyTK=S1zTybF-x`dGEDkSsdkih7@kczt^S~ZH(NTvi7-lo^YBGoU8*x%wgo^l3jk}))XR8qM2M{$ScBSTK-pR!5ti~w&T7$uw1@IkG?MiM$q zO}`0ip;K5iO}}lJ*w3&=Q1~DzTSnY@A8%?H?HS_Uwk$U^Y8qGPF#u3e1bMtj|6Jpi zFMhUCOB)a``uG=)7rC58KyOC~>>hs4BLHph@_S*eZ@1J9HiSK*j)YY2ZBU1rud1)h z&ae0-ENY4bboomL!N5H%*~?`Yju|u<|27s|&{6B?R6wR$oZg#!mC~-CIKz*aZv=%@txF&-YNx92joC0WbYu*!tVL?Jk3rR-AObd>r>EjUzkp z6kPHyv<7vH>$+=!Nvo!JNN*!Kr}qOzFrRa?sA1$ID2@j#MNXicguyJHM+m6P5^@Y6 zpD2k+&GD}3mmZ0${EJP zL9uae`FLdY!n2<-EYA31d`0>b+s8%BYHURsgZ1$1MKDbi!Kt-hoF$gVkY1N7Ybc8| zG^5e6V55mFY7*qk48j}(S-58EAjiq%mY^1Hky3;jW)OQ*zrRC)3Fa5OWqD^vGPmMjf~Ed0aah_n{<+N^NWG-{ag6*oYgKvJS<{}LXvqKT7eF;vk_ zbsWedr%c=%cUKZyiW*7z-dUCB%GJYr88_{M>mnwgypFbDm=tD8hla+6Yanq-A`Ofa zw0D@`%mf!W;3x7q)b`btUqt~F5$o>oSXQH}Kx-P}NijLMBOqdjT+Ag?E2G?niaivr zlN7pco?n4WzT^E^>$l(FjH!&^K}JiU)tgeg5N|esq^VYdmF=&XI)MdfjI_A~KBy+r z-yM%*>Em%sChyrQA>1%90euLt%R;T7IQ!-4{5VZ&TRayfU4N)_*L0vgtycy-L3=l> zpe{RVBLXb?9@L}=N8UDhz-}7&f{dZkL=fq`QSqDKO;mxzJ`LBCR5GXuMte=sY@#f( z9)gyW-vf33QC)MHz1=p4ug~c4NNA(8J~uu_iUDITUR^$D^gT&JZ#6C;llCZEwO%%U zv8_X6V~(d64F06tF}zB#?@ol5l$` zr6N<@CwRSUT$<+e_d8}!y5i+a<4x^OqbCDD zj%0B$`g^UuuhB%)H@E;S>bp?&I=cM!Nhm$5lajJ-sm+hbh#58F_y%Dk7Q9vbKvX-L zNj_561sS!x&FTn<8ci*6n%#;=>W1jo?(D~@0sc+cohU^L=2V|D9f|>}Q;#?mhhw#o zv{T^KLF1qnOGA+ZIUQ6$bg?8`-3EVKO*+A;kMd-9BZUnS@PVpp`XT25(k5f zp12TrJqO0K%2_Hj3UP(z{JkSgL3J=eI{1go~tbI)RPc zcZ+LuXt{1{E_DuQlq4GuTIJJK$0|&9hA@QtYO1@Lb^3nN$I5-RVPWF%&?fQHf_@s} z+AtS~>_0xHlVPv3WA1@Unrf-GK9}@*^GA!gW|(oaIv9)kwTsi~mc<_x z;xZM?6-I=Ng>EBnA6V5IvO|kfWmoIV)}d}u+bmcNoj!}2R|v&E3C-iD133I>>A!64 zsj!~K`dNnNgb7+RCwfhQbRZuDxBY9r;A14=dnr+S-#Hs;Rv*3-UscTRO&``Jf7Eqq8m--PnJhGpQ@~dX}g&HCITUQ@i1Jxhs?-+dq zZdjGs#^VRo68*Hir!SK=o|Htobf(>QqMu8-g}=-(M761mDK4zzdckNEKLvj6m-XZ| zoOP5G&lVj2IDO%%>>3Tb#S89-)BROo`#RICzyP@?OH)^|lMQ1iym&gQZYt6xaZ~?N zbFyx`|D|9dfyU;7g1^Ul@!2K*qDR;fpg-KYj)~`L$#Bxb{Zo66V%!`z8+h1%W)QTBP~cLs;(9*DVVLQb)vUe$~dTx%t@A?|vc=N>*(1Cb!krbGSX{v1vz(6mV?%)NvnoC;X+*#hKTZxt(DM zfgl9Ppww4tO76G`uUyv-06Y|Rj0RA|Eo@GT@NhR%w9aolXoFd1aiRh8L5PDw7byDW zZSV*Y`+Baq^@L>*at)>`Y*}JjQwhd={U&WaN8`%b;mt;+3%N+RE;^N;O=_^ThtU0^ z{dgp{mUN!=J;bCtjC}Jd0bcKd#fiqjEAfF;sv&Icq(GSGiwMPc91q0Hc=V4FjR7dH ztXHe$SfwI7!&aIG2q4Xq_4p*;2dQ{08XI_nSrs}kXE9{eq>wE&mQscp3wpVW@Lj*gP=5Ra%*1Gwi*? zpO#w_E08IM(dzjm=05zG!vwk*#ewQUcLK4~k=x>g^Nen{tn&g}+U{ZfA1}jL*br;) zLW_X}fFS1&w#T17{hKBriOBD*o*7Ev9vXd=l&4y9JLQn0;o3N&FD&Gs2i9sjRkGj25hxEoVcv)$~_@7`bY&uKhkp z?-%47-fa9|G2NLRIds?7s(=tv6UVI%jtt6q@QvMG2 z<8w3vDWfJh`*2q<5}#cP@7TzpE0T~)YPu{*5eO$Ai{V{3D=jVU2F{HgcM@e&wm5Mh z!U_$?)0+9lJRy8VIre;Cmd{^Hmi+{ROZh%7t;>qt3&%^aag!Er4vGw$h(W3q%i|no ztsB?)(b7M&(fH0E-jq9C%eJogc(IY_g8%!g)@+sZzgnG1jdl)qy&?N_feMEcA4jYwRL(mAEq!z1C+H97Knm) z9l(QyB3{*8k+xW|X)0&+E~rhHK3u0w%@4^i!wwfhv|U?g#2n8a#g}$Sa0DauP%PWr z25t{RRRGJR2hnB1PwR6;zkV9_=RqN|#c7uLjw!orHFSw03DwQ;lZTh~8!}_Wxdm1)41wWV->WlNmxyuQ0Ye7u6o?LlO5*x#<&y&*Ld8qGi()MF z3?kg!?^dH_$p@=g$n^JC`lKYx_2Ot0VZg=*|dl#?1JY`sWGvE)V3p#Ki3YK3Pniizem+gf{G7 z0fb#LBsxZUz`)407~IsKNY22EuxtkB5A&pZYHg?MS7IyZCD<^gJf95*n+SWn{{=_~ zNjO`)i`U6kM=yk&EWObkc}pUWVzLJJ50_&!eHq{zyp+fr4A?{X{bxi1MPkgqL+<$1 zMn&Yf3orbkA#ZvFz?mm0Eyts;3{4l=c4Kl2a!eIiR|BXYFNu6Y`c*{#>~Bjh5Z)fC z>5BZDok`h`<{TMRwvd-zBQ$!)>;+!mo^2`zWG+SpIIRPI=|D8ndn2pgL?JW#DGOcW zJ`5auv_u32NRv9o?ASepGmhGXie~Ey4E30ALQokl$DTWAK3(bW0PU2p57Z!w6g1As z4A<0y*0s>V1Q17F&wUd*l+4EZ=g8-@fqD$D0+U$Lt#?K=0C9NsA+sxAWMqUjPf6|t z3Z@Le%i_Fvt4CZzwg>^v?*%526-+`wMPka<`%YM}p%PC0tI>UA;kjrvQ0vbcr%8)0 ziy>iwS?aDWg|qkdaxTGgt75r;Mqn*^1&G)h*thig1n*6hK2sM|;^lLbm^73}BfOte zZa^c)1*oZmtLweY9l|E>+VelokJZ0O3Wrvd1_@R=%u^dx$e&?rZrjFtV4tpM1KXe1gJFdSMdY8=y80u3ZaYQJ#cy z1a^QXys{p|3ZcUe-UvEq*P(h<9Xvvsv2y6MmMCaDu-n$D2o$; z$4v0JIWUl<9Txlp%zXsUCAr^u6~By@|NbK@PFA-hE5x|tjCcaeIwO<6ri|}#lgFfk zRXe5w+ns;3MaIyRN;-x*A?79>ede(Txu^>q@L2w+8T3SCGgl*JN6NF(?KohbSC-~> zf+OV3OHB?3T#Bk|E!Wz1@^qEoq4&AAGA;p_=StW<|3Dv}$;OonBL6WAYem!CMS4o6(z&CK^1NdT^^{xK#px+_a$$-N7^RMOMuoaSOYvg?A5}$7xip%#gq3K5 zJ6A3X4TEl)N)wa>ltU@`8mX@4xz4n1IzG0PKBo7@bPXE2yYzvZt{|Zv#WLoiX(94> zB1TmM9txiZRg6R@+=p4ZJDi0QfvyFj`UC0$T~kNEmWg?rA5mRSv)`A2kp)GfIBVI9 zJ-xWygW%)yvgV?DtwG(1_9ckpf%%&rBY%%Qv;M`vEvS_KJD_gSz-c~M?E5IsMA;&KOWZW=h;2pVu|l(BZ7qLZi%^LZm~!54OiQZ zRY4>!rXT--u#;Ix3w&vldnz^_b$^JU!1$K`eRBI7?>-uH^*3st7Y9n2x?X2?SGl5r+OD!nZ_^#bBEVN%;35ta6;UIy~b*GkZIY$F&2E6{!v2t?=FpfSEl!!C4lX=HoT~T zCjmIh)*YK=|0po{s%ZqD*l!0}Lne+ajb-V5D~O$+erp=z>OEq?BpIlgZ57dVZcf^K zd1#L{@J*y_l7=?!CEynScM88K-2653GS>Z5!0SYWgM1#ciOlWH_Z5?bpYDX esWBsl@I(|%jND`|5Fs{?bG`QT2>hUwl8+O!%K$L| literal 0 HcmV?d00001 diff --git a/uploads/055eca42-b4b5-4211-b819-030dc601e9b4.enc b/uploads/055eca42-b4b5-4211-b819-030dc601e9b4.enc new file mode 100644 index 0000000000000000000000000000000000000000..494c7fba980c9566939d3008f884fedb4c935a03 GIT binary patch literal 49684 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 literal 0 HcmV?d00001 diff --git a/uploads/0cb3e08e-b294-437d-8228-abf97c996311.enc b/uploads/0cb3e08e-b294-437d-8228-abf97c996311.enc new file mode 100644 index 0000000000000000000000000000000000000000..8368ad2640a1e8f6e2ce09c42b62bfafb06f0a3b GIT binary patch literal 179149 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%0Dw6g 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

74EvwE2^$__EzVWOE(2NAXz1^5NAk zY0?F;@2#+w(L(f6l6^vM2u<9+*s4aiB8g9k9*y*{OM8YJ81*(#x+XJ4@9_$&(_6sh RDWB`oEo?&*nt*frOFF3qPKy8l literal 0 HcmV?d00001 diff --git a/uploads/1fb0cffc-1e95-4f21-aba9-90fc398d1bb2.enc b/uploads/1fb0cffc-1e95-4f21-aba9-90fc398d1bb2.enc new file mode 100644 index 0000000000000000000000000000000000000000..bbc5782339ee2736721578621ea30e27751672c3 GIT binary patch literal 8898 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 Q)FusNB+n9w9!1Jx&YNj#@c;k- literal 0 HcmV?d00001 diff --git a/uploads/23737dc9-334b-4818-a258-758596e75aef.enc b/uploads/23737dc9-334b-4818-a258-758596e75aef.enc new file mode 100644 index 0000000000000000000000000000000000000000..d4a9e0dc6e706b242709ffca606dbc03bcc6089a GIT binary patch literal 59095 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�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 literal 0 HcmV?d00001 diff --git a/uploads/41384719-ab8c-4b65-9823-91217d3bf3d3.enc b/uploads/41384719-ab8c-4b65-9823-91217d3bf3d3.enc new file mode 100644 index 0000000000000000000000000000000000000000..bf87a11339d56246085126b1b667a9ce4fd30aeb GIT binary patch literal 119893 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%0D}t$%W~_^I<$1NBmnk2}x6a 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μcm8$bs;^?9yXCRx^Y- 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 z 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 aEFrFvtNwq*Yr9W4fh$X2G8yFSx~uku97wJJ literal 0 HcmV?d00001 diff --git a/uploads/4f661a78-fd57-469f-8af1-fd88bf8c167e.enc b/uploads/4f661a78-fd57-469f-8af1-fd88bf8c167e.enc new file mode 100644 index 0000000000000000000000000000000000000000..b3966e73c50d9a46d9ab1fb32dbdd3d1b9907b78 GIT binary patch literal 33032 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 literal 0 HcmV?d00001 diff --git a/uploads/572a6f37-6a4f-4004-a95a-9e10a3080b7e.enc b/uploads/572a6f37-6a4f-4004-a95a-9e10a3080b7e.enc new file mode 100644 index 0000000000000000000000000000000000000000..a6bc82954c9051939177abb6f17ae375684da669 GIT binary patch literal 119773 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

Z`8UgX<6eMpFVVGwds9 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 KZh2I>+`hc=q1`h8 literal 0 HcmV?d00001 diff --git a/uploads/6aabd2ba-c64b-40ae-960a-a7a161c337db.enc b/uploads/6aabd2ba-c64b-40ae-960a-a7a161c337db.enc new file mode 100644 index 0000000000000000000000000000000000000000..0cb6da1281d37c7f0bd6dddc44f6593c5cef2d81 GIT binary patch literal 43137 zcmV(hK={9Lt>og2w7=#3$*ND1G{LE!?XQd=HbL$tuT@_awMmPfZtC|#0p z8osR28nMbYo8K%DgId+OEP=GKi-v#SsI=562$NIO|IIT=dsJAWDj@2roLBgx!mfY1{aTD|FC5tsVcBc z)%%Qxh}RIVpFA~}EChizPB#6Ly(A9H8$b)J?>3{t(XIQZlXr>sNHfb7P;EPr+)~2Q zhB_u46&>g*RP_cI|4*+(e>S16=yx2{H-kdZBWH?p+z@$|bD z$S%LGqn-1|USp_zkRyA{%j$VD?>G-)u3wnuPM6%#8b}k9{~;D;UEfZ>k{^5d#6`Qo zd0=b-INb5HN~R^2^JKI-iY!^Ss_<>}!td+KB`u42=9R3j@=|Q?M8mUydto6TJucTd zAY!5Y{E$qc@*u^-0nwllVwyK)10*Ft<}it5yA2NrRF+izXg4-zvk1&ZD-M5C4iMe* z4;^I~4zWZWX+mIhS8on}ex5Q)_q-43yOj*Qb_22buXMAYltuU#6Zt&@cG-iLYtdzj zXPQJ#Yuo#k8e;SOu!`O~Lrsa>1);oYxoiqRO^S8yMX6{3^XrD*UmJ+3;6m1aD{=7A zRnczq?-y-$4mlZ<&Ix3jEMtCaQ$0Hjn63aa9$UIS4W%By1SJve_>)af$#=GVfn~Z1 zQ~LKvrNpiRx@w%#V&Hai6G+<6xYGkuxf3 zr2d`kj%TXjfNAdZ#(9NoYlle7jy0hT6NzY$AokgPFA>B$kS_>v5<2R2BBW08Nn*4* zZU*9yBCuQ<3eSTqc>EOgjT3SR2U)rCWa=ZaCzztX0emo+G$<5}b$cAP4&d z1{ZU-oZNh}woGQpBW;gUkmiTb>IUV8+pyxkOO_T)k?>;5_Ib`ZWmLZrCGzOp9790L4fUc zE5A8%Hy|H5iebhPuDbN;@VJ{p7mQ(j0q9)N@SPVf-exbiDaZGw3Zw2>d(CP=t6QmD9%ojVs{Z-%^YY1m5}1e`bN%lp&p zisYJQbBL-!FQF@*F%M9OO+57d=i;{^ezExq;83-xSYDJ%_b0J6e*-Liz@9mE}0m8(w16gMaQ@{)= z>&T{RA8@Rq$Wt-q?W1q`y=)AQ{o6?Y;YV0N%1p<_LdxOqH5MwOB(B`aQ)mKxlXNnC z5P=U<64gS-9^yrJEZQO&lZu*{)abO*4Ho;6RU*u?i_%sO3Iyc-gNa+vOHavWh?;Rm zeLg-Rr@&4}Vu!E=GshlEA^@+lH-O}C!oL;R;dx-YPq^p32T1H<+CZPz<~YHct&v@2 zWQ}0I)2%+*z5l5`uQke%IDe`TVLWe*W)?UFcv=>8utpWrC+z^!P{*cQ6~daOvFz@5 z&v~MN_Wn;&iG|u7^i7m3tpX+{0wI^KvQ7D`-|G zyveai2E}L$_4WP08u{2Z>0lR8pSO$KK#zYU{sse!*S>PjSr&}N%2CjYO`rmQi$ZL{ z^R(^FLNDCM94gp&W#W=0&P>N#Y+=G!v*y5dZ~HuiVl;KmaktdX8ep{${9ouihHas{Trx$weYO{vwwReNy9z$CzC=!2kz zMA%2yugz04f$^#XDo={Z&fZ|szLH=zzR>B$AY^O2R@w&evj;_{SSQS8zZSUKx|Fhi zQuR zBhmx8ZJK!Y{iah{Mz=-?*)sjwz|2Q1XEnr)qkk3_c7o<9?L3un>h2q%qw zoNgE%zH3xkBF-=zkg>OF3o(d>OLVm$q6UAl?InoaTX5yUb8?8$*@pP4`;43^wgar& zmD=O!cUvuw;uIeLjUkh zka{g4T~>P)E1|+8y8o~r%0CpuEU`_8q?_%)yA2PIyzz>ztmEq8^uMlks5~P5vdFPp zd}qq%scN2rYk({^=23BaUo!BNx_Y}Gt#xru+x7j3O5PPix@dWbVz7PH7G1+${&(K1 zXM;e%xhq+&^E5j7wjAn0hs$(BCh*sLD2SN)e~;2e5$=@nryUi;nqCT9%-y#9c#Y0?~sY%k;h@C?!G z?+_YGWI{i3J}-rUhfn!Z*3`D}Wx_CC*oV9XrtX~3o=#LxUl~;x&h_^PZbA;n#i_W_ zI|9?-_EmkkZ0F^w5fIxb;H1$B6%>?&BIrY+2>vf`sdqU4+xq1le;$vYm>&a_cg${7 z$Re+YS6Y(G%@i7m5oI!`3ia^g#h&}U-ns>XGOI--3yii29D9|Q-_<7*Gqrfgfx{xVPzqK6hM$!Yv$AjZ~uqm z3r7*mq=bV(>mT) z`%7zh{1wQ%mliW|NJy{|gP6k;uGsbAln3`@LctzXN<2m!D~@&TlWSHA>S9f#*CF@x zl=!ewj|qwwDi_Wz{+0kH#T?w7iP=~vr6A5sdttL58`t-1o?rCNq52NqvWGc4<0D>3 zJlJp9y`!`Q^_8w<;4M^GV3Zin$4H+1uF025C@zTktaeooi`{*S=-S|UdYC|%a!#P& z%YRJ)SLdIB2NN$@NY$^~?3GJ4?LuT;ZHD zew+;~zOd&G;)q~5NTISPK*v+nc6a>aTBghOo_kz4=bc2G6_QfZN9jQLCk==CzG0kB z>0HC86to4STMZ-atV~==r1Rq^`(BMqk~G8%GqoiZ-}aM@hHl@7QF|IkT*_Pot;>wzl=J*>u9%1xn`gl&la@iQA;q4}p+pI~ir^!=q-|xWEQTASY8xEq zjR3i#)9}(+*y(&Pry@glfJ}{)`j742|A~Wje+Wy6&LU_pgqBRCF4^&w!|Z-IkNlcfFdvavx ziklS=EtIQ}5IkYksFGmcq#t0IU}x9ff!;@N-<1>F?^3Dm0|7fJS7c;fR?9!|(C|NA zf}r&}Mo4f#TTSs;(>)(A22-9HHVjz|91N#9WI4S!vEKe?a%UY_m6!~grs!og0sgJ2 z;{yJIe%eHd1B&S^LBf(*2#_>R^0~avEK+NqNZ?qtu3M6ZQbJmb=9Ve#Z^K!NO{R*t zFEyUoo6txPT{+XYm8_H$sGR|suh&Y;*1JOe$0HTHA1*tk2wHrqW_|f^#_po~YjnjX zPNm7A);=r1%-dZOvGl?Ik;hC*#KbkDz_p;H9H_W*{&GyYXVOT&ol@?ZdtAA89rpg* zluvk#=PVCLdelB3U`iWaQ?U$lELPMx=2*~6C2=Q_QP2iYFf3tg1u9ZI!M>p%>YbSk zE$IEQX*b-bTzA8!vaQsY6Wswwv(5f%D<}va;=XdLcjpc+WpkVteAU%=l75MT{5y)c zWiqS+S&ut_5_l1+KUxd9AassPq?n=hE zcIpLo<;UWyIk5PN#y7^qjLn|p;l+m_`&d$c% zq0RARiXuW5jz5qc>hjCaBK0o!RU#&Zwz31ib>oP{B~M+%uej%CY5FiW_Ab&dO~M8w zm*o1s)SuDem3y~sYtt;VFmmL)x(|d{cshi&UnZ-ea<4_GcW~opR=grJ>cDT79v}sR zMqBuW)i5pg*!M=@=c`UPS8k~fen}UR`g3d#6A*qd7(WGOd~`UNYz2;ym`cJ`f1#Qv zRQMK}g17vKA(4-@?&4^oM8?iee3Bu-+`GI+SpTe=d8Lh&6Alv<6yrf9o)yk2^i4pN#58Zo?m=*_m!_ z3f(7Pnt!rSM3;nksN_zg`HVGzr`(+tjA7%^o}h2n%+~+SXevrrG*94oX^TE=5B5UK zUuOqu63@=C)E!L49HwH{9nNas8mMqdTuD7nTCUeq+v^Zk zgUH$A?^>IjSt1=MluQnniD6hd;oSYUN*w@7!prSuPV!>6H-jgWNJx^DI6O!T&teci z1;Gv;1=Bgm^q43p!BSk%Uk0M6^!Aj{F7Sw}q0N?)2(?@J>ljcf3g%zT`~iY|>ev`a zA1?0m(F>V%)h1)8v4u4IUuayl(xzW=<^V3y_Fx>>nZKz9nNfk6H((aE*LfoeIQ+KH z8OY2<(kj6!>KF*NPjQ^&Qm;+gtWP-wCq*N`n*M^O!F;lMUz=ePUZD~q?v z`9InjA+`Y5YBRm*J1}#w(UD|NKx7ixY&xE4IqDW9F&ZttkE0WAdImT^m_dfh5&miV zprk}c%0*{TxmWnmY!&`UM{*yAt41L4fgPq5rP=Cb$gY}Jh`QQMnO1g)mZ|LmcfsyM zf8eutM=@b%;{$75#C=!-A-?$-s$C<$bFR7zN;UI&X;;H#&|q%6d;~bo36On{j^1lb zg>)+qsM}HpRUKcHTl*yK!MLWbPT=2T<1FID>3y#}2&9~7|pC{Xqi#(b|sYGjTZ)<$BQ50{zLzyw_4^s`gh1%Z?ro!45rbS}1 zL!tiE+!mj_(| zcmfBrsGTR)AY1@#Rda}tcIYD`dQo^KukT-L}OmjOb$>%#v^9@>mK(2?O04Jn6RhVgbn_)n8tA)9+o9My@0ZTdX!m0*EDQ_5Ax+h`Xex;Lo zPkm*X?*YN>0p%J>t4BF4ZW|0lJ4pm<0-8zCMmJh~(2wX0up8(A*{|#3@0cOD(CT;jx-nDQhb^qmby zaO!h$Co=I!y;H93i!r4z(N{^VP>!?kV)qL7dG&E-d2Zq&Zkyit29pLA{8nh_C_tF2 z#h64iH)L=YamV*)WT zo&)a8m);T^%;=FALb}6|xm3LD+IE9x+YvqU;Dm4T!z2~?XS|skpCXK#-A|+E)h%&| zxO%v&r=$xp?fNT>IYO!p=N~aAB1IcOBa7!Lnc)K3XSnmQles10S$UD8oi^<1p(d~O z0dTXH;b@Ya7cUN*D_t=;8d3~v5TxRMr(jDgVhDQ{4$?=^^9fxF>(KAOJp;(C$ihYZ zQY&K61L?6y*CQ(jw{$s4d~HTrk+D+6ULyr z5}?yT&6FB0^{CdJMeO8YL)gS!&3Ag9Xl2dup1g-BtStIcmUs)xi~>XUs@gImALFF> z)1gp)X~vBW1HazUIl!dn9f=1K*XzC%3?9>3%0NhIM95@|c zWQ8a{ek!RwT3WLv6ip!G(+AByFrn89i#YJFUh;oO#s^qG0Eb6qql~--v3nJ8H#EyZ z*X+dqQ-?JywoKn{@|W-a@b>a&#z22!B|kc@0W+iyCVJ{(8Z@9GJ2(%U0A2o!+yAI0 zQ|#8%)k8cmhzQ6i$X~qoy;;N^#lK9siB6*bZM5O=VZ|+AAYj@9mMB^ht2#A@j~9Qp z4q8sP{%pbA0)rom-1ephD<7fxmrgLU2C_Qp7@-9AJE5vCH%7&00)`gPMdp>rZBb_| z9UkeUh5vU*8*WaT%K@B~h5duzw72vPe?;-od#yfvFC|1SUleh@v!UzUSe}A=KF9Y3 zywCCpP5!K00aymW{ak-`_csHN&+ROU(5GuR355UN15X3h6$3j-WFV%e5)gfx(^%4V zh&W~v{7%_M7_r}bt;`DspOXZdq-l%(&}_si`pNpw@k0&o)iw9Pd8fzeGirop7~FT$ zkNZXt>IC3dxEaK*{c_yV3`fPA2egOuj7}xZX;NUSCbcnwZwZ)rF`CF&>J?_EN#~L| z9X3unkIUc1Yn7MxDZ_t0<#s4yXXteWXg%e=9)}RLZ{ZL2Bo=gl0Af&jefcKckKJtB z0XiF@zGl?n=Y=Izyn4joU9+nRM^u>d?o5hBiSUC$v3tX%`f)ZFiEjYKu>4U$idPzv zrpz667wN~@EzLlExD4jn8*9)`?xaYRzhLSe-*#Q(`p)((=P-91{j~4<#(8YX5Kvtc z?|wroyHb=@KCE1_{OsO*I4uZmD10WHd^aH;jUkxP;*el7st$NehG2E_8;W2$2kmc- zjXaf#&db6v@&Trr%>YTx;u_rp299D&8<(ia8J4lG5+ zWzlUSKq5|;7?es@z;=$`VrFuby^e|UZsN$1QnC&6eWWo`K4M%_dX1mvQx@K-5ap@L zZOaF)@}t}E?bN#WUWM-3Oa}x~&J=ljrPl+l{$(U4jP_H}IEIeNyJso6QL^~-(+#m! z+4*V4CL841p;>M~rp^>SDk#70BUg0wg6zq?>Mm+@oecTKT)Rx{h^sVC!NsK`4EVhm z52ZmDUaOvE-7qX0nj11NKWuRpAnDuhoM$BL^pY)19pWq5#17SccF1OVh%0&QbVatT76#Gy8m<5IA-AJLTZ(W*0t84$$S;ysV{sWzCUVN z8}(^J2eSygQC<33%3pUgY}#`g_xp_xq<)~IRnxt*#;575XFSrcKjHj}39fE*y(n!- znSBLEFAH4pQ$`J9A~a;bUO~Ova19D?`Y`$#^=Z8@6^^6Ul%;Naav}x)j)QS$K{+qg2qS`#(Q31FM>mpoNoe1cC;@tV8)E=`U9B#(!gLxu#MoTr0)}K{lCy> zwyR2wwn+?{J27Hl$WAPIn<(MZ%7Tq0u3@caZ{_QM9jgN7>s<`myMssV2$XHhAUJ;D z2P@rsTguW!ikGumkPjiQ?-!hzx4W?AQ1?+laJu4DJ+>8>wVsipHRWyyiY9TL8#VGrgFLhN6;^7JdzIu`O;74xY!;PLEJ)*i01Mnc zJ@J$#Y6~3p>+tVnoy1S$J2}E$I3C9Y({_z01rtJz6bMrJtn)*lpMfVocbXZCq&lM| zWoH|HOXfDJ(WL6YdU))j8V9aq)WaD7@W+V^o4K33%y4OTyC$wq+U}9}8HYR1VjR#E z10=n%gafgw!T&OV)Niy#eXvOUFphqs*;rKg0QhA~K(Rz2t$l5icM7E1xCpyn)*ndN zIb$HG<2HUsK%Wba{FLNYyj5*|MjMZ}pvGhWT^5DH> zAXjJ7JN$*2natAPDYEmGCbP&~dq2IkTwU3I#OUos1 za4-c^gbBV!#1JO*S{XEZk9p|dShMn z!P823OSlw2O4Qr~SqyHvQ@W9n3D}0k7FG=nQym?9?c(N%n38$F5X_FA!XJSm$(-Ju zD=i;EPz<7|DH>75&%iD3&CinUILJ7QTRooLMh2ejnb(ZelVv?Lo5bP@i3={}?R8%z zq4yqY)7|Dw~(o zuMo&mQ2j&AgbdWyza_DQhL$JjJfX=x6g#a7=igg7HWRaD zKg~_B z=mN%=9b1?9V%LI2tKMZrh z+^f^sUysoqq&PnyO7q$9z@ZyXwzoVnVggFJ=vTE^ZgK4fNM~KsF#018o1d1=@EM(F zNo_HNJ}H@7t3TI?{Oy8k4WW?CCYE}_D5eS$&&rU0a30A5pQ^>+_f@v8G21n$KQAWwMxb#t?`!UKtzwFc)PxA&TB`^J)@_ZFJ1 zMx|Yx!i?nv*fS+ZdxWx1Hw0A9N4xr^rHu5JWaa#1BVn#dhUUYZ8Z((gHJGVAp4+3M z9x~vX=$yFU)Scco{tB+Z>?bw++2oYi=$)P#^Jv@A%5LnP+`uQR#!M%J627#3+4ZTX zQcwk0yPsNRyW-C?2GVXYR*)9(wWJG6i!x)H7i%3%dJmr8+Bnj5g}~yHQLw0yz)@n1 z3+fyD(;v|Rpd8IE*bkcrY5$v;YuI}04#<$LUWf)_ba=mKe#19~>tD+`=nJ=FFjpfl zlyg>@nA+^`Jnq>i0l@*%YqLA_UQvV3BkQtq36gQXw<+$gBRPE@KDRRE&OJy1E^Ivv zWbgqiRtRL$h|o)B^Ut#70xupUpuMGur6y4VxcdUPcDC;mi~bu}y5i{>>P4(EuNxD2 zr@4tGOvJJAcxMv#lLErWb*!f%AV0ObfOT>scD#BE5>wL*j&pqoPI}2#k31S6^z&>d zX)7+0t1xCOFJ64OLV@YzF@d})XKi*P4iw-0`+fv!advO{^d{R8tU~RaShHX09n^)6 z_$2|gLb)pcmt*Pd%u=GpxB13o4iy7irL0|y6)|`##YTFVMJgwniWc#69R0f}7t;5ME zVv6#O|A!Y;flZ&Hn&r!h^18Wp0S`YdIGUBx0H#O)(J6lnx2H)W9~gG%cM|yt-PjDH z_0WOOSB)=bFnP2p;+?e_7iN-BOngWmTa@7LJ@PV)$DfG~Ni9T!O{f>Im2)oAQ*<9d zGyUvEao=5{qFj_}G_H)nk>f!EREbynI!Id_NzMj@SzH$KKQ4XsoL15eerEt5_JM0m7V zH3JRT7;*ma(-uTw@Uy7~PASVphjmg^63g~Hfgtk+3a3K`t%Ne@JYY<7j&COCkGIvp zaSL2s#ZSLKi8wU-N3Vq45#tycC8Lm8Q$G3&=z0fVKy&b*fLw9Ia7DAlgzyuvDdH}0 z4@q;q6-ol3NX!b$yzW^mRos@v0}1Nh`@wQnszXjunMtooW-r}>vn8dG3+AIIW0o|~ zV+F{fl6>N84dIM+<|EjxF-sQ`%N79l)L;3%XcjE&3m#6zt|8vMYdES{_SU#Ek&o&^ zi(3Jc#*4x;wo4o>!c|*m^P)?4>%siw$csYp#$MEh zmU-a@gRhulJJTh}_h|*aQ4*nYlQYDxm|i+XTK`N7AV&<{L$wo~0kQyf5zevCcrPnz z@dk7xj8&cCosM|WBd+~v73=5|(~`ykk-&L@{zMNr%@z*ua6}%mx1d#?k z8a1(SQm?a=hvQKr%2B{wOWGscJO_l*vj-uyzbe={PN~a;f5xNXH?!yiT%c8wo7M_e zTr#=%T$NpwMR~+R9ZW`@N#5O_dHm%`0kS0N^&>y!;;QB#TcfrgxFoLFMVAYv zU*S=uxHWKsnl9jzepor9)*}AJEJ#mo4!20W@uXo2v^cm-Pp1v@0qE~NmLNxgF?7`6 zT1um^l8^4H65V19CGNNi(ZW>Md6MEkEw58LR8vspw=Q7sGHG+kw` zHPRR>{FU1hvydFjU-AhmX~ulTifT#hfw=XVfdyxqWQlfhW@mnGaH>c>P^-!r)s!{| zlM~Y2oi}q^mZ|yMoDDv3$Ilp7kX~K3te7aH0I^dP>kV88o08hY4R zax=0G7j`uQZIvrHEgWg$nOGouutf8mGG6#;0Tz0BMG@(r2R%sm1E}1#{b;eII`@;H z$5RN-+kd`EGQXL^FNIMoS92{$CyHFXxXrKkYL?S)w@(IB-PAQwS!b&T{397+GNBTB zq=B(YxN=wm>5ke;cbnTXn-7h55I^P3PC3^e63!!}M@x$)mM>fM?W{4f+T1IR+7X3sh#(M_-6p&64dXCE|$ky+HNQ=KTG8TJDP=0;uXIlZhTtoKyKo zp-=ATYlC(J+_WELh(6*z{6f1qsI?UDQnrqb3TD1T4t|e!2LMbDU+3v{-TWGc(h<@W z9`Wg%db%jNVj?Mr6)@#vU}QmI-c>{7w!tnbc_piesfgWa1|MsF^(7}PZ_)@LDXxIr3iMg{Yye<)%?sLxvZLYX%+Zoyml1CMdt#f4 zU|1;nioe>8-j^zbJoY;-v<0!QGd?z(^*{Lqy^rGi;IGx=iC^zTn-@U$#CDppkf=e4 zVeMnHD;!au-gE)9rXNCoYpWnPf%CEYvq)0?mz8~IEg3>5bz*I>;3DmfO_tbsIVLCd z7)`v*+-;XHtfrIaezS1YJefc8osGyVzh}hi=}cjQu7g&hsFF60As^VTSD;e5fnd05 zLZb#KdfFZQ3|1a#VBx#!rN5iRlAZlCmQw323jhF@9l_M8C?ZOXm<&qpg6w+rY`G{3 zPKp-L)_ENSEVuTe9L(po8`|O)!XGxdiM+?O!7yk1+(e)|itDR>8i)Hu+rpm?DjRGT zD7?Lhkkf1S;FM#Q`1SP`+L5y8Fwx-LydZ+jpspXs8j+s<$0voE-*fM{9I*1W-3_Tl zp@EHAPJ@lOT0<~*eW~~K_6=r4aOjBw-o58ZX^9BqCIl76qP~p@5%gk@s^t+)i|Oaq zejyZI^>$<|QHW{bZNBJ^4jsfis?zT;e6oZ7WXS&)g^~Xq>$G{TlqwW#T@ucL>ns_32jCnl!JWuVO^qcbOHcW-g@E7 zsv}u1YCRC&JQpTcD@rIGfVm6)Zz;VbOZJlK1k7lh$wH@pgRal+>EjYBTbk#_!mK}^ zsB;c4Z_s_-UlcOOQ&y%?%~E>Z_ciiYXuco9s)88`xE+kdK1c2=>nGV`g+Kc)Q0h4K znm6fW;(6U^*%dg<`3C7iu2F#hq)-a6=Z43Z>468*?YBwrEbu=F)Z^s0jN0%GQIFJ) z?T&&U;AH|R=8p3xmP0?i z-PUA-Hf+tp_}3c&X0=0bg+yS6)?;At*c;M?lu~gTA>NH|5{AExwP4xXw$b<~x`1%@ zc>+VDxU3eA-0mjw>?+Esy=`Q;{d`1`t{i`>sF9#i%nlSJv7iza#)bhx%-ThG1!=wG z;IJ!=lVTOKyUPiTBnz<9><*mLf0_=PbuPQo$mcb1z{OT=)-G7Ud4ei`y|o%3A&Mn5 zrfc?(fNTJOj*vpA&c$*-_F!q!rIKP-Ku!hDYAuSLN>_A`IV_Qd_oKieRg2zCuJ!|= zW=01Uc)1G^qV)&r1z>GMD1SLX8E==T_{d5otFP`&i!0k%4!@tiL3vQ{ZTnl7<`XwuFJUdeVi#} zB)^3q|8k5l;l(CgXRv-6eY|v_AuF~!vO0kk$)bl>W#DB_Ux*Nx9PY+wpQ7=;F?tQi zFgRN+cbs5)-=0rpglsSre&R{gt^#PxvJnMDXeeDI^u_NrY+N}8lDob9acRNFb_t9; zuv3&cgBkKvKUE`ItNH~HN?789$%vqk-hRG2N5VDhayhIUK=Dw$@ZmndmJ@fN(r(=1 z;nzy%$d(I#h1hj)`!ql3p2kNO$yVa-#y&fSI0&fuGG|S4**97?rkH%O&Thz~rEu03 z8a}wY!|+=7WW2&vHb&Z4N!pAwHJUhWs+bl#n1n&saV(XKrcT-0j86j_+rKcCY~NEM z%t^dS;+m(OX5#rUXN{&GRVCIjP)Uraww{MT`(tG=`nqrb(`5%hA~pZTpLI zd`IDaY&U>m4PJq$RMa3td)?*Xt$i~WQ#7!V2bkqR@E}z*q*g{&roy<4LMRo>Tl%DZ zj#BA|a;Ey6&b5Gup2nRmDYQHez;NPn6$2M!?`!y;^WFlJFLdPD$BD}cu+3YM_-;wAXYPUgAP>3*Zu{xYC|-m^h17<4|0m-2Fco_?-5~2p zM+_^as{|fXFjVQ)!-jn90PI;KpS}@rP=UI;HLc$eaV-F_V71E})MgHmaaW(;lG5k? zQrlGYJ~nw_@RO+;)AtAKlN!RraBqDlJ;V&kLNZy_OR0r9`uK;5!Vr9ski~&i!2GFDB2G(*! zgVDQ&VO^j3=ZfIOafYa=6=oMzdb8M z47cvk4J*F9Kz#teqE}Y}@G|_oJuq;hdPH)6DrIE%ulOJCQ@EhZuWI)Mj1T#1LV+n*M>ay*%bIN7z8(mqEFNrJggq|ECqP4c> zkfTx4*e0>MI(O3e07I_}shxeK@UW@u&578(6g6kfW>rYOAC)tlhvtz|l^aq6u{nS@ z3?CS3L1QzT109*ZVp(B+a7GNJBO{ZBQBou!P1E7FTI(U_2p6p!GzU974wZ{(;UP#H zIF5j>d55s}By3%g0C+#<)?%9f)H}{5-7I(Y%T=74kRQ#>`ExKjD}dgW%c*`L2W9=8TSWh<1omL zH)-DW<2GxOYX~Y0SZ+k0^N0b1zs9G-<1m~()0U_4LwQl%quhHYFNhJY^$XD5_m=UV z8S{GGfs<&{V1v#_Q82*7rPtOtOF)5eO?kY;eczwZN!lQ3+jndL0ICf}&@RPFvQhOj00l zd9>p`^tNy@%)|tC%WtD&c#G;@IGUnw-yvy*OmsRf^=tAeU zyn!7~muGO5Dbx;NDiiXv3o&gl2Ln!|8QflIk=)sisg)av$Bh>YaACsfCvR6#Vbvmt zTNN+xuDPy=(OLv`I{7af>mvZ{7)9GrBd!q$^}LiQu{QdECyE}70M>8w^_MYH5W=

ffUy1na&cF}KKb1+lT<>TB~ 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 literal 0 HcmV?d00001 diff --git a/uploads/6deec74d-940d-498a-8f91-38424b935a13.enc b/uploads/6deec74d-940d-498a-8f91-38424b935a13.enc new file mode 100644 index 0000000000000000000000000000000000000000..95b61f65145c07363d4f6953dbb7e71b72b8e3db GIT binary patch literal 354833 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

rqn^!a565Y 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

hZgFuc@@j7J!L+Xgu zC=GQ#g;F3<)74*OC(4y?aYHfll<|hbl|8y@S2-tzL(qPU2rcw#-yRa>(C3jZ;OQHX z4W)_?@aIepsS{{Re1JowIh5Kkgz51p^&tG0Dyca9?NXUDQ)(!UO+XWZpV}e>>ST4! zmPHQaoVyGxPg$z4xsN097|qyc49SCt`~NU8L&m{&$XOKC08K!$zeQ8$>q4_sosg#g1*kn9X0;Tb$$lt&63Kj`5gj! z{tlESD4kx$>Lf$}fPY7H-7G_4s`yx&a~|llP}n{h5?Qq%bbG4=oEA-t=DT2wUi!<+ zW6W7GRw(Kyh*qmKP4k2)H-%Pw3n3k?DN8d+;$OdnTi) zoHI-#GdR|iir-U9)5;acEJBCi&@LK63F=ZUnDX561?k601UFnC`R6yQCW@E-!e5lw7gv@{d@h48TL)p9gmHH0Kw(icbL$ zS|^JXSeou?(Y``pY5CBU=NrZ1cQ(~odcc#j+L~8F-uPVUY03p6N!0>H#5zRG>M6(E z>S?y?C6^Qcbn(7S)zcj(XJgncb6u!6teYQxSMczzJ+GDLHeK4-PmY<H1rc@@ZRljSRPdo4a<8C-(5^=^k#Qo2zD608#$jO%^jJN&AyK4KA?Jr z#FulFQWytRrPtoP@=#1D4x8IW_xukqB$+-U*7gc#aNID4{-M=v0qGjPhbx{-!Ge#0 z07X5-%)7JnHjH-hUvBBh%RxBP#^S3}^5SjpmH7D_u~+fl0c=ZBReIL`zMaKtTx=bc z{cp&G*_2-_l_lSv23No_2GSS; zkcXOKiFQH5E?VyR(H@DEp0=aGCP8ktY^^?SaZjFPIn3Ortv}bHT%NY}>G0y{8);_@K`5?2tXKD?_qlJCq9TQ1*mytxxr!p7QRCQx5 z%o-{g^Bz*p^iq$=p=$L}GIieSRBY+d@kEfW0b^WwF1SSZPIq!cHOzmN@JpJ)8-VVu zKZ?Cd6B-YuwZ`0juL2c9hd7V^UOl2+tm2w?(^f#ssiKy*P8^$sHadh(xpN*fDkSJZ zd(3usv6Q~qyn4;NY5n2D;OLh=8DU8sCT%UsC7$9;12aq4$95iO$3Tv7M?z|1?Y~}f z`#}6V&!iB$rdX_?_SABWb#NqS|Qw9RQ9Iib%}~r1)AAeNmj;EN}Ri zY;*n+_NlsuYbP3mO@TfRMhRTD3Tj69a!tS6KHKv9ttEnOcL;{!A6sL0qx`f5`sN!M zhMfRMVSaUwXW#bs9h%`=1L6n{k0GM*lo$4=k^;ucjt?n$L(L8FGr4)YzU~lc#jyId z38C;>R>e7aYb`qM?>v{v;-801?Y-SS>(DPB523nREJ2(X67CDuEP!(;m8IzphFPL)f#zd~CaWIRTJKFX-UZWV|7Gwl*z(Dkv%iIWg9A_bPS)X5i z)?we(R2omvZe+K_QT;XX+q^ljv&A14f;m%k&C&+N%HTZauaNHq&uz zBVh=G6~Zl(mKmO)5t3OiSu%8z@fkUl$hd7P8vJnG+}n@55@yA}-`py{RsrKgF>L3T z-2<$GLfiyndjivGhff7B4_ozg)^C79Cv15{~8Yc}-qgy4)|(dUhh zvBKVd!ROi)Uq+gni-iBs49gb6j@U10 zfJ>RWEJQx|lM(Ak+eBi$j3LP&1tG-e$XL{r{jO6_=M8B*p*^ggWeXf^CDeEe+Z}Um z_j4?%%;A!LmpC(kF^BOOn0d9}QZ`(;(Y1(@8M9i(SON4a%WymSdFfL=_4f<-PSFR6H)fnDEwoXoS;N{ka%pW4)XYf zXWy-`cUP0KW#jVI!`bt+58>VW&dI7jFmG(e!9Jc<{aH!T9==Kqi$n?>gFD#UH~}xq}X2Il;VMv@B>p< zYh6zg!I%ye$FL9gj*nEWhA_fzX(`8cApJCM^gBRjmUy8WIrfp)NbfN0yr_)|SY`b-2V$`+=CH)NWJC6q`AY*+w;#*?sPxgf ziEe2p;75C`mc(k|{XTZYqW}~_`EX^l$1zl0osKSf8Yqr)V-(y?iR0aW%ZfzIEqX>{ zZc><}fR6@@i-FM^#h;O$*;4i85x^gWz4MXiOyk)0e(P+7;OHG`)hB%QE5zz*;=rWp zBFW_QT5mQ6?Wk9=qs*djKG+!!UovGjlbXW!;0TjVHGHh5rKdRtQ@N=cd>3ma*m9VZ^f>k~aeL;&xK}Yr zftwGw*9L~E%PRJepQXmitCFEo4sUt)5|KbNBm%u$s(alE$7HzXvSjYd__MO0G&GG+ zOl1i`DB|+8&3|H3ln@YYd8`J^oZJyJPbA=%H`ZDKut?JZodt`3|Qt1x%Z z-qaX$Val|4t@@))6F>H|x_1!j;jbHWrOp?5?OdM)^*AIaNn*qI^6LPHgFq&WCEx`` z=(NNnFSs&QZ|~Ct9sY1Fn+Fm@0VB)$X87*Soqn?Y$6_8hNUQt2oWM5wr$a?272ZtJ z%6WsVwK|}?a18>XV$vwS#Dfb>Pbx8yB`4>3Gin zB4CEO%B=!)&UYGGU4b;~>!vGX+$bQ9u#=F$sRVu@oNeeu!50AFcV0mhp9kK>A0XId z%LeANBls)xej!(tB-@1@R2-578 zH00~jqQPtIQ0WK~w=v6zg4S@rd?$baTE-mCi^b>31$)oo2y8ZGHU32s%u!KNP#D|P zx#!2148H4(17~9zAh68!b;OFke7kUR21c0iGUo%Qm-wmpf-rkhbJ*fHTjMswsITXO zwck+G1#a4}l|?snk{#qW?_3@WZlpl0fa05Tvh3R5x2$!hlR@m_?a}dFfI~v~~R(HrVP=jrwpjF;AA-7c7<}OmvTJGHZ7}$%3&sMCfb(vRTLbfV^%lsfH z=TgiE_Oq4M#iYwdW|)KnF;zriAd-#0yB;39ZiqXB=gLF~HMwQX%w@LtMg_hw`Z!t> zwaU}@J_G~pa;;WjIW+Jnak5>n=*HAsnZA9rCYybqR%Ry*mo-NTZUIR3E`~roQDeQ# zLdz%&oU<(oF7JuuOykXiL~CtCb&QFFhtFZBcf3J1LJRT==n)(seS=B~OkBdN0)#%v z9TrY`v`h^K9xgry5!VH6@_|l_w?8%-0OEXM_re?$dx2b-T-N7<0uFPVL?JSKtu$dK zI-cJP6cgX630CU13!lU>#fCoGLwOQ&H0Ax-wBvMR%0`K++s;QXSXh#;ugns*X$IK( zaCH_C<66F#h5hrxvTR{%ocY@}?Mf7hWS1+QDK__!n z%X3S|3w`tz#`W!oM^Roq-4p;V%uk({;~QRJ;qwHlnj!YdBbSQ|j%HR6qhW0!-( zm>4UQ$dX5W)miQd**q+UdS>`^%?6tmkMW=1)?}%fMY8L6ao2m~M@Nb%T0A$;@F${C~e}y_YR1@(3|sDg?unm+XPwlF9Gb?M2x@mh;eqQy~o^gXWFY5{w=y zLWmfIJ*JfkWDqMM1#C>p_xZ9;!~L%!H$kiqGPV`_6rFd}8Gn!858g~6kUE(UkuUqX zY}c8OZXC7V2D@}x-tpHh*&iC&etDYj_aS7fn-Um?!b=z5+*tlphBhv}pwV+1ULm^> z_dRhjhv1Xf&fIG97$$Mz-S_eIb8SyEeCIB~HWbdqti-tMgE;C_q1dZ8r6+3F#mT_7 zbA5cY?YHeT)g#eMvN28)h58`Fl=BJY6yNp3COFM-;Nm>tLrxrrb3qrsVOwB-GQl^6 zJtrQ`Vd|URHD<^L^=A07(-1`cWyd#^neeST+X#p}Y!+q2e@vVs>v{|HC8T_N0Ikoh z?)HY`{YYA#*c}f8ZWF4(z#oVE3fxm29LWmx^q7VFX~2Ixst7h~a#=Xr!-RMl?{c$M z7R?C_vUmZ`Q>K=&*_@hrxEwm3I3_w}isW)RoNtINMUS*zT*_Ba_D4aV6u?d>E}#l= zF6TqAAvv(-IKqL)i;2n5>>=eH$Jb4adx{}-(ew(0Tr@w!FX_|TgbDzT>1nr+vGvh| zZ&ke1(iHpE$vMCVDG2vZSnTvWAwJX_VP;e7>iy~e57m63K%bxa`))Y_RC2;vy1v?VPVeXLkPh~AR42UoZ&8EjD>y@&oS^W? zUigx2eqn3S}>S)MhZY{oPst`Ru0pFSg?&Rb>BlM(q5#C~Ex z*CZUUQ$T06u6GqEp|=_`+eBt4k=$hqR*<97xHqzL^FC}~6|T(>%)vVbhckp-C+_y* z(*|7umGx0vH834;vd3dB2}B2PX>w!1=#?mNG`u4T>`cwUj3*D%cU-9nr=(@$U9iz0 zx_(P$OsDHAch9h(3s$*Zuae%OaPrJ zW08%K_#M1(<$q4qPEPFJ$Du6}4dv&e!2mZJi0BEBGsM|&`jZwoxrzb^d|0x);HFfp>nm5TI!$!GX7VviyAO9zgCL><*iYsi8oZ#A=@!-&4MeX4@}K}hIVO^ zQ%rWdOYTvm@HSBeSdNL2UbE6MdO|rtS;+dr;v0uQZJkHCBz+dh1a0?&P1f}aASa#o zv`dn;<})FR(H=#~ruHJhDB_KH;)bv#%DLw5OGQG6$z4Q#&`Rl{YfA_q&fdGAuVnyM z-E(N@Z%Yp(?0A_S+~^n!rgi-R_z{=HGPI#(a{!alKfRTu$VR3bv>){iFzBfZYi^@` zfl>~5q)R7?|L<1@cAf5j8Ue>clZoLqH;W-?^SMHAV`Tu;Y3aE$ytUjg%)_EYhy!8R z>0^a>zzNSziCyfoW7DW!+`ra1G_xt--oiArD8o#GikboOyHQ4_l#Jam$C^N@b-&e2 z4Lkj%a7I;ZT>}ZF>wCYITi8M%sw8tVFr$bGY5$d4D&9ckpEL)t zHYbYH3iYf41{(o-Ex@>~1I9gKl!_7zrW35y5Fxm^+_KBgj97BlCoX{+C;nYO*w$*Kh+5Y7<@FPV`pRYBOHRubrZ#u1zt7VP_i z8{#`{KaEKQ`~t;Fa$ZeMP0O)l2<(ZhF}f!M>B{#%VJU_6ItsYEAs4-dPh!Xytr7^= zm%3xjulzIMhbuDap=HZld*)4GCi5W#ee|XWh&VypM{$sbyhE$mKIbsaGx2HL2;&E@|nr3 zP7V|r2sj|>w1ecqlqM9H8xA&I1}#(Hb?0sTbiJ)|kmzQ$ACnMrS1Y8zKH97|{)pIy6v z`StC*=;Z*r#$|h7$mqLyk2Th?$yB%zdmu^%w)5|M3z#v?pSkxFLr)qOSidp9J{(5DY%z5rwxM5&C%letM@U{3dwM_!jWcqiWVnLnir(jNI|(CT&#B4}cSI<0)OqAb2N~&GmL~2o5!^Q92kewPtcrm| zw&(=q-dNfW(sr0pb76OCDRM%Tvup$2HHy#uE#S~;xXH*UYlHSTtntD$ZWnU5@eOec zVtiv_91%Mz-Ea}7+|Lu9ANA`-{7Xj3IH;P+uREr!7X(9%; zdQ9QyHEJnuW-v}Nt;-~iVtPe|IXpmO$zUHlS1l#ychE5`tGEY9SU!`AdGEwVqK@50 z`aS6KY5uxVUubI!z_DLgre_@u6!YF#Y%VGD0nkDd2+2zYX_*X(RgZSA%v|{Wsz88d zA$jtxHidIIl=uKoj1pA!gzKYpZcyu1&wI+gCJ^35w(D3vOOc)68P_Flh(}^KH|zNc z$QWKsS}>9;wMy~oYy_@BS%X%<9(#+s8xQ{>xn;E9zb{<+4Yow!KRb%gbyb6V%54S; zKpC5I?V!MT!Q)h^htu}V{eIGFEVKaLFc^R;Key<9d@#<@N%h$TZgSi2d=N`YH3;2j z)MJxnjQ_RFDO0moOrG1?!=2Qox|4Jg1^m8m&j=7C#HKOl8u@ zDFU9Q4&I@hWJ4k!&r65CL$b*woo}ZS^zTW|z(=GKWjKvH@#kPi@P=o~Jt^V!_KPm| zK@{Jr_Da=UIEU;gf@!8BCNx?=NjAh`Q{p^GMin3B$O_38ap)hAgyv-fUzzZ0Gye85 zM<(%#$?PiJP9y!gLZ!?rz(d`D591u3E@K@-ij-4LZWeDJP8L$n)slmD>{7M}K|&Fk z{pR##r6_yb1Yuog_pSy_69vF3W7qPBJ=5U5m4obBVf3vtq!^)FM~V#HV5~{mWxu~y zLasEk&p<`do)dY_g7u)zhT_{w8`BtigDm(w1WT$y+V?Rf`HlwrG8hlzF%YNS0e{I( zO^usNJ@0aEqedw%tA#f*+R*mW!GLwmakkzQ7@^1@fL~F`d;D&Of z(gICi;ly~>>#A|j1B%xPRAX>E^XKa!UB$sPYQBx$KAyA;4`7i1-j`BKQJGJV<0C6- ztOrXE-zJpmLNgxGjqCQ}Z}42wbaF7C9}sByyF&j9xyIUNZ~CBx*qOlzfuJ%kISDIf z^hD#`Pdas03@SgW4@-V6ESaFL$e#SsdEIxAj`ReLZyRDKeJ@;-=xlet&b0>Lt`iw~ zXMV%Mf5LQ`U#*FLqUj(|ehw5CmcS}`iGk5n zH)zdOV$>TF?ZJZkGJT@NNP@I?LFVQvV{a>3fkibeHUVJPmL7qlf-hWFPrF8*5DtGu8Y`@IBM(Tl6;3&` zxz_xc1HyIRqZsu5;)=*roL76S5cC`Ou;~7F? z&wV8==bkES2B>!1KS*q{g+I1xFLQH*{Ce_tL{)K%0P*KivcITBWr`vPQH$x4SzU`@ zQjo<`8s9w*wWoE!p;b^^>bgg*%t9$_<9qIg?VPLhqb^ybDfA7K+?sO(o3!|wDGKH- zT@Grgpeq3WIi0?n3?3*17>*{w?#{h}cBCJ6bo|cV>DK%><5WlW`M}ixBlPfgD04Cv z?a6R0kd9vq=-Hs@hNY0mE)!Ws4%Uy9!8_o6-!UE}*mhM8ApBQ6j>uh69(|5%^ z4OrF5i4qv4u)9{~B=C2+OY81Vi8wG~px^IB`nh{@(rmGBUWfJ`y4ApGpl}_eOlAb^ z37b!|m%AXad_fJFc>YnOYH+O4*zU*VFk0`vq>Y*PyRhcVSdjCJXh>`>s4bLAkbCJ=E3 zeb(&Hm59BgIa&5#m~!OCddh3Fjn-Tm;7XS;0PN^CwbPEBu@1`s&*h>p5R{U0Lc#TZ z;jZabscLjfr0)@hl|kta;%A1Jiz4&&-7w#9?aVphIrU7mTUZQ^=|_sGrsCu{X75SX z*YBA4B^{D{F$S3t_scpR<@i!bb&%3Rt9C14Ugt=LjFrJ6S&kMU0ij2~*L*BE@PqO4 z`=1=Y?sngFFr6kvo9JHnL6UP?_<4j+*$Bm3H9A8o?9oZIy3pgOWcq#SUF_tr*`_Ku zL($TXAxlYqHTp5D5VA0zxgIT>oZ5W`C@tS4;B~M3(-dUkss7oDY2wV~=GZFxX7#H$ z^=v+W4KL(h0F~B2G?L9v=+#vC#Y*I#@Is1E?kA2DIxya<-#yFd!!&qCCSbh)uhuGF zv6?gY*?x1Q?iC{=9d09yQ++nglp)PP10U-r;!`PqWE{iLaVUc`2%wT}672Jh?IUPo zO3py!_W%r{j&WvnshTM5r}rm^pDZ20(HO1Rjc*$^zr@YO>70HyJT^k{Wx*wlf_24a ze=ZAGz2Ed#d6&A?%>}6@_JqNWYO^|6HOwOQ|++*RkS&Y(<%?oomNnS zEYW)CN0FTzE0#aCMGUG^Q%!k8()ZuRqyKaFBHYcB4eDncc#t*zzSi^-(?u}9@sP={ zL4DrzQG+@``^K9QBNBSy-+%fIl#YY29k7ykS&#hBb2+UMPO` zDRXhW>k2(*gPIJ>uKUu8EseNUvrIec*HsYgY5ZI7jw!9;Q(=N9Lh=7KLJU=Y+6YvTQHGJ}BhnK5mhxH*ix_Z&;Q;{6wr zW_M$}ym4P!QlY^o60s8l%lWm?Ew{BpSI3vc!t>9{63hWF*Q}Y+1f(lPb-^H zLn9A;w%r9hWg^z_&Lpq5U>(0xLHa;M#*Jk?-k_{~9uWJpze$S3CJL{o_*4~zI4CRe zVA=#AUB^;e`p7??{4KeXU0TE_lD!pW0b=egoZ)4J;L0+n_etvMmc{j(DlsCYrvn2vye-DL_W@HtiW zqjvssYL#}qBJ~p0R;TI>EG&Lt=ahqpF#*x2bVW@#iH_!r-|E%R7dtVzGK@(;YN}!4 zjvJPcaz@}3Pji%k!2ZA1Qv4GNyI$zzse+ss^HC#Xbr&fjYXIK0XJ4Q$obsXa#G$_h zNSjVVP~0Qie!zn4Z~@^59}HK7Uq5`wm$bDdauiKdAU(U0GDT`kQhJY(r5v_=m~SQ~ zoF@dkXY8C7&H(>d!_t3^H`sV+&WnbMt)mMrB$PJ; zrH{Mg7VnQTUb=Tf!zUY-rW|`ucsbrs45GYvZ5PGdMj z@#FZHWgt2FJlHuL&_6lkPy4r)68CeUV+V?R9Si_r@D@tYU%0|M$<`JH;`edh=YSsF zS(b^b;`1$CLsATApHl`hzcebj61l*%797yFV=A^n>nDyW=q5dKHwYkU@$)EX+bPg- zhZ6MXGr(=Jl!D+#cy&S2@z^y6_KcW-Wme>ih;CjQxx5_pK4?sd=wksNZ-{1Q9Ei+F z+Y3VR0%|H&2dD<>x|%a8YS-+Vc)z$JGt2~S9_rLPCm#z{=dztwp{+=lSW{Hp0Db-- zQC$bJFabdno={K)S|`F(%<+r|K%J&+-85s=#13;^UmM;;JPN;wsz(&{UdM zcc1W&>+ZX-R+|r_osZ~pLqSP*c3Xsc!Zxx-nmpSUs@Z!`b8$1C{VDKB9Ni54^a}j` z$4c{0$mxM>;{iit$p+1mJOo;-t<=`Wf=ydS+tP>R`4vPGBKXQ1WKe1)gG}!B1k8y0 zW*x8*RDvL&VsuhnS&`e&B0oElbOU3D~ zqkH7{o?zAkDuI&|no$d7F#}Cu?5Hcz7ClP6Yz!fTVMZeX5@fes!#I{?NYPQ=E9j@o0@qf6mh1zFs< zmhQnyJ7MRp@-AV2VB;S`xP3; zpS3J4m`jgbLeRp(0hO|2UTlf6*RT&@vYpdK7_>Gm&H}ko+sSvoA+s;Z1s<><&TWMd64LMqvKaky@P(p3p9(_&681ohM=?K>jj3@* zC;U~1R`VSrLG=v=W43gj2jA5S{)IKDVsffRT)V?$XA;yqiXu18ufoJeHl*I|Fx7(B zi1S&*^-(zxLnNck2I@V@G=3|JPJdON7D5lHDSg%n39ad<8m+`6$PwJ486TY;IG9qj z$EAjD!6Df`H#B8ZbPJ*jD`Q%pJL&r<@9KsYHt*2DulDrh+7?!7I{PhZ0+{zoB8K!> zj%)Zs0AIK^9p2)wvZxtIPHF2`pLNXOBk$Kq-I>JFZSFuYB?1s56nqg9UJ1)Y{7lNq z)^&zZkv%+Zu%se{j}R$^UGaJ?>vDBE)|B7stY%md<+EoH_Svc~D{NrUc+DCC6tuj6zA za=JFUCH`p(3+GBX_Nm5~+CEe0@D91+je?k|xS*z z_vL;wvcfmD{H=sS*MiNhU{Wjpo0evdhkhwuG$9Ah{EW54JT<>o(AGU?u{%Rybym>cl_&mKy7JV?dVp*cD{XM6d3rA|jZ92KRS@2wpX z|8CKzA<}=HrO#%(z7JBeLerUyNI&>d<2)aN3+brkY0xXPQB1sw@%-boza7f$!J95h z*$3X8HbD>%;dz8i9QKJEIY8(z|Ad^mjvY*}E$KYzHk&`f;X z14!V(op#uj2-W`s%|9%#3TPxUD@uVW9|y$03KYJxD;0g)#ItH83;8DY3f{MZik{SF z0_?q>n|hk_t3JeNt^!Vrf$pulCqr635o8v85}l}+GsV9^6_U*ft0P5V3cPTevo0lv z>OZ}UD+u~H`RlooA$?5B*qF0zl4r`r1jYnB3g2hxcBb;p@@~_GA)4BHpNTMY3{!mW zwdMI0PQr-w9n8k3+}J`qdjFWKw65Z$i$Lq(E9$rm4gP)2kP?e?YidoeEj)dKS|g=1 zU+U+AT&R@(8^Sdl_)5`q23}bI*=`T-1djXAlb-(@f+>&WC#)Udvidh}1v6bFjmv`z=?eGl41Hp|&@Yrw{HMeD zhlKu`&~79OD!Lvod9c+u;CTv_IqeS<^_251fvVJ!rx6;h+2uVbGP2N4bg4sXwD`Uo zfoUuMuL^*$6O2W?FaoN_irZo#A!3MLuJ`#DP;S1h*o_+3Iq8Vfr@q6SoIZwfmK~6S zL=kE%N0jFx7yKV@4vWqAg0g8LprB$y%r=RwAsmAEs*fDfplw_d6uG`KImx-lg4jKw zPo^y2FNUUl8UoQX9m#rf`<~YBR~L;#rn>{VN@l)`B(q1`r>B8}LW;R2Z#SQ8mE zJTl$a@cS_suW*?pln~Xq-&AFQgPxnSUirbbM!4HB_nP0r1>|~s2ky(ragz+Nspf-1 zkC2O=l}1mG-a+r#m8-NcBkb9?C`_vfl_Vy_`f{`Mmo|sPJRs=w|McB{it7OMe%SI9 zH@64po!b-@z_uye!oQH+7YqX!6i12hg5U!L2z!AMDDEyOU?`qSQj!J)p}^{nN30+=T;Nte0D9R-6mR!vE4crD-)w*D z#t2g};rkUXrU$>zAZU~>EfgvCT-;wa|0^r<=v7ZR(t9*NuEB{$JW+-`KHy+c<*3Sw zgHOckFbfs!B^GUGmo<@+kJ;FME0ywtm@aWhgUbqA77;lcF&H6vk!WS!3gvGw5SS6Z z&53Y|q^w+(KS}evCAx!%v0|Z2g}?w*x+$LFZW#*zci>-~b{XZA7uCjpi=5qWQ&`7! zS6l>smI(ALRBA>G6gEd_V?XGvMF5~6oL^DQ?-`=~z({hk4RGL`7YAp;aDQR-1@9X0 zi)JM96ZPl93sp5UNb>(GAQ{Q;FsKdNouR{~3{}_HYoI{XIP3hULunAVK-koTa!Mse z{A?&QEfD%(jtPj)-6A5Cp6K>*U3oQ*gC3befp`1lm3jpNN3Z2hYyYtOB|z%i!6zvW zt;HS;>kZ~~XfyY1$?ypy_W^D*}T(wx!o!o*3H>^g@zBG|h zzqu-~;*I!!lu`9CWre2J&kEgD-XNz1TUI`%0mk!_=T|hW)a$g?lzfmOt=M!mOqf#8 zvK5b*x3tg{zkR-DDI0Z+b0Tfi`}0s+n7<@jh3M#z@Yi%~B&OzNvX9ICraEbuAHP<~ za*m|u2Z$3G`ECL#6iv&KaJ(`Sb#Vca|=pO)CW+CHO;x$U9)-C=Bb)v7KJ@UE!C^vNJiZh?Z?L z^LG-8^MYRuCn*l{(##Lj(_CLf+VCRcIP_eZ+8*pj27#(>M?&v#D65V8|H2sKR$oPs z-0Kc<6)naW=+(%mHPT_LTQ+e}i2@gyY)gk=Fc3*PFmyRr+M2HDQkXZ`Lb3=~pl9G` zBSdV67lbt7smGP<+AdA~_+eE$vt(j*ar|O+ZjaXZCGFoiX)RQW#}COr_2AQDj#O;Z zZo(8sbMD8`6}!;?n3F669G|+4G4}Z@hSj3A^|C>lzCo@WcX#VN7)?C&J6(xN<$@@FU%wo0o zCxG@_H?4ZiWG@aN{c@1BW!&evs)kEb>vdQ_XubiiZ-c-=UZg$EE#=L;Mgcz1lRF0y zMC$^eO0^qNAfLA@)kz6OAbXdaPIS7gqvXW#B?9brcQ;!-`LX&F>3 zCpTs=CqYw{S;uhFjERV)dxFgqt!`wVQ`PGueLx};<=Q@pgmXTFXYRp&HEU{Q<$5`` z^^$i4>o4Vpd5tC6m-6mv3G=<3wNpoHqm62Bo^(PCoy%t%V?2%3Yy1h%{AJ`iOoU)C zaor7|9~j^c;CkHtz@t+n-Fna?E=USK_8JIc6oZJ%##c^{X= zTca+{{E%8c$ZfL5V*C$cnGH9>Hfif!-Fra<;6yl4gG%j_WPKose|0M1o3C!ZW!g5v z1=YurPF^gktXyK_<9>4eBCBI&-r(!Q&dzhO`YhMEpu55)C`6>5j`ynQV;2EvW* zr4hZ~a*@DZ@l;FXrY7q5LV)}$o4lT_mk1k6=ULKwdDjCnspDVm!%4w7$=j5U#OAbO z@8HMD>$DdKw4Y=#87>}r>VX<=UEF+IVG_97UT1RT73>^x6KnYp;Z+Y3hcSnvX5MC; zxi-}}=eDRN85Xch^uJRMJ$7STAoYt*2^|IZ0Ak{{D2bUN;8vqS7}G+B9O}&HLUM42 zUok?p_x%dlXLxc*V&M@DYJy=*8346Hy&3h!3X+j!GANT^o`)cU&T2a@LJl&U!}NwH zmvfHz3ExF@k@u(>Es%AGg~AtVQV!~_hs#;>lW6}sP(~cYY8@TfR9=Vew&kT}vZ3_Z zZi16VZc&l<&o2Oc9-t*&mUigo55^;QV(FeA2M`Zc7G*cyf5~de;N9Cs?zDlmOHhXJ z0q~hw(OH8CsCc_2;S{OXaW%|O15$vJ8_&k*7>xV#D6vaCbSp2}4QXr+W}P!FoaZy; zm)`UB6G7`s2VUGh9J$SC|B;V{erg=rMC_l)Ix4y9L2n`2>X-44*w+U&`@jW$_`01- z&>^$EiBLB<@PLyDJfu{T{2x)nrVpr}i4=t&KzZjIb*z|GcTzS7ur-Rc#D+b9`Hn|QZOrilX-cXT5~BgqXEP|V`V`+OvTwQUG| zbDJ5^HuUmr3n3)#6b4YuY3{W@EnJMEsSi}hfDI&)Iyk>oZY^-xRj^!7``nu8`E2Iw z;!z>r5NmzF%<y8?2r%P59g;xgP2Qmrgkk`XPFF_eqzZ88czc z#h(9de4nVwdB*nzO%|}O4KPL}mY)iQ?(j92;}A6PB`|4_I5zk0BrbiurF1bk=btU| z!^7*&i5O;Qqd`jOgEGJ0_%r3ZMl?F_VT?|{9mYh(cw;cGXYR3N(yHI+Cz;1wvcJ`; zJgpe7*)qlf+sU7*q}Ol-?zIcy@1tA`UcAe`Fnh7rIQWLbmJV&L5e}5F zjFN9t4Rv^svNE#z?uNBB$Bt+uYwo`}=D6}YM(9ubYbD|&f1XL+6?|rk-i3WfH#{2` z0wPbc^Ls9ZKE|Tafty^NK`qhdIugHi*KJk(9=F4r2*(rjkC%xj)t~+)Ue=`kNPB>= z%j53d7sN7-t;hHRhaKa2pKm#sTV8V4Es_DR8ES(l3aVFAn{6D{Khgm)?Laf&%*jz> zHd~*h8xaf4a)C=kC|$PbeN_Uyij7hf;+{!FtzQc~=IakW=Dl86&ShM16HLB-V{ofg z-1cIKr-=}>He1cY9U$pGWSyo;?=V)07gl^u#~kTqsl0p2A1KIia(347@5C))NHI7` zH^!q|=8c*XShS)nzv2||q=kwenA1(yAJ)C7!p4`=jO9Ap#_g(GMW5-gLFNJrltkUTLRc@RwvL6tMs#p#9I<+$omU{J|qaXx8m3yZtOV~)HwMq$Y$Y^hB zO|PZMO-gdruy@@01i}@-=Yt53kZv?%OV{soSA@SJ>i_V5RiqZSR6+fy%k~&z$PYdA z<@%HDZrj*T6yYgdy?31Wb+n0%^zP2p9Gh(WGvi$VuwqaNmUknFF$!9`XvFAa3gc(m7=V2zi z>)vl_21f2XCiJ~h*^{SBcDz?D>q^wLpS7Trsz?vhfcN)uvo*NMettqn#>hvhuXbqc zyz(9jdy$5ldmbRRt7RSo9dQ6vK&rpp<&g*n#zBAUB^Z!qAVK+`qX{Q0VDzQ@D>pd6%|RXj=Skm22RtUH;dbU>Tn zq))-XzuNeH+&RF_3Po&CV|-|-C+m#l|cVc%;#^-MC*03W{GXV8kM zBcaO*wB91pHcPlUJOMw**tM=Gg70gngJElu=MD`Hr> zu<|#4#_=+sJOX(Q5gGGx4>h!N7f-o+XwVZ=pvDycQ73_Q8<7JPWYCER zuCVJZo7)}60y~N~FzPUknk(c2B6=?KVwLxg*{M)P5&V(`!~BAb%1a1FlbF)b1L#4T z5SO==`zFjhW`<&6j-19{LgzU{A}IUI0C(%Z&qw4fH+M<5w*?5H#*zGkCeJSJ%*~E~ zXZar8tC)g#qMG~Xb6H@Xp;k`~O3atKDUlEeI;EYm*O3TT*g`_lAfs7ZdeCb;e?8LQc5sTC2IdxL?;`2OBK941kl(Q&#A#5mV3m6zd za15n|a?gMuwuCNA3jN!?ZM~^226+gUYwU?~b`(z37(YqU>^I~aNs%~*{h?$Z6us@v zUMxM zt%n93|1>uSL=IfZLWrS>rJcV>E)y>>F0Wc_j8%*!xbXdNLz^=sfWHNVwj%?21!A8= zU2>=o9YBJ8&r)R ziT`fRT8ObB^ivoDMqot00u1GZzrz?O0zyEEo!p`mwdMUooinOA3vl;N&`6=;0kSmo z6+Vq5ox*iwisRMlugesd4NdfKXNfH?KR`kmF&)_tbULI~uaV8!t|+QeOXu#4i(bZt zkp?`)UL$TW&Q^?s88%@vjrNc{2wsr=95}um?6algF{2qC7$c(Z#}q3{AQ>ZCUL1^6 zoB%w;uYn4GM(KeC1VCBx<_hrhY=5odX#FXYwTZ4m@Iz zn#r>@;=~!AdqAxac%{;e3roWKinM59rBZM#|Kunp zie8ldO!^8zXm>ZBzz5*qI;#VoYVH*qP|EoaCG?H}E0=n>hAk+_ovMlKkj=M81)q{U z3KPVzM-|x6gFI~?8Rv%c7-FCoOH#{1MiX!9e-kV2s9E^7CYu!ON#t^e? zU!9U=pFq|Ixar3JBf!nn=PX2By9j}$mmj1}3Y8mn7!hyYT3qk$75M@jtYqMA_PXcy z$dqwznHikT9kE4wF0JMRaM?A^k`x```0zRZs@Y-4A2%|D(JH#D^Fc;2i89eAYM0FR~Dl*a$3{8`+XwG{QkOP0g!?BsQ`UxACdX9lVJYLi>qo z^1$A#A8E7YE@Y_yKyKIwkXug}ovhC&ps7y~mR_irO-kGDw(Q$TSpw?N=;Xs#H2IL| z0ynM8RDw|9w7pC^rh3CUf6W8GEW=q#WG}4D41Rl8ZX%-WZVo6MN%@ z!TSl#r(V zsK!Ekvx7>WKMD$54it63eV-jzlEacR{=%_%422}+yow@?gBKD+SAO!T)urh?DLg^#iZgrFAKQ2p*&VkuglBCA#iE6!Lr zHBk6F<8|#2ivTe4LZXVPP75eAg$NXe;RR(Xy;wOw=a8;EsI5A}C-oN4Av z>uqrl8&Jr>qq2kch3(|^!vwm;*sNo#JUdTvKa9?TB6nLz5jkyqd<;#O8sqG766v*>1U*!zCR zRBQjuA%XmjZIi`;7Oc!bhf+yVX7hp-(7<>J>`9Y?M4>V6*U!2is9;Xx9QL*=tmk)b zbOzk_)lEzamV-Q%I@KZDLd?}ut_qbrjz=%iwT@219Knf)PkfYn7ks(*fdmh}C@cE} z0hbWY=T>57s9hp^_UYp7hh|m*hwBt@i4&B9v{zxwe9MD1$|3L)oBu$KH=(HKqlUVB z+taTbM<-m$}5B0TY68uWYNiVoA=wiU9gbdkBuI zJ;+Ds^}@{&19O)SSq|b}XiC5tovQph(=f!P+{_yE7n{K-exO`;5^N_OeYB>Wm3~|( z>d3VoUKW-7Ha^2@VCxDT+hH1?zsoaJ>swnJn7n_s#PP+Wgl|VYAetpv{e&{OV(_&O zcM)@YRXi9>sY~xcA%s|DCUdlvm1ZTvn8_HYVBA$^hoci$V>EAHZcF*>gurHbHo8|{ z?wWJ*y=*Q4^~ybt%AtR1c4ps}Kkn@nS-sw1wm(n~;06@|IRqsi%xtr&r!>UKJ=3K- zwI2Zu#HmkBpaI9Kj#8eE9}5V~z-wVz!0v3(r{iE6(iT)llG*8Wo64Wn%gqDj3;IcM zM|Kc4s_va#YyzD6eD-52V{B*~Tw_cHVr(*HXrir52px>yJv`u16M@V}<}|=HjwKfO zo@GtUrB@Mwc*XR<)I+vSh*F2u=$Fs94BmT7+!YQNZV#$4A>Z1-y%4V`{KyI8;z!ce z(J1(+yDfZ854!+I-hRfWrunvQe(T6-v|0vq_6?$fJFrrCZ}_2U9MZF_?c?bXX?fDX zYT!0gqv@IUV{V}7TPg>vZwQC{#C^JXYOCEE)8PIxNQSV~qLlF{;4jr=`V(wwZ)OZBH$%gpMW2*`1BNynAC)@=63BqfmZ?KiLdG_&W3!eh( z*Kg0zT1ImV{t|AH8$8()Hg!&GOQ0sNy4 zu&D9V&2m;?Ga?kMC*FK$`^&Kc>r!R%OEqdi;NoIyhTkk@6d`){dbZYCY*71H`FUcx zK;ZD%WP5)WlUx}pHl+DG=toouXuVG_L4cq7KH}zx$bL3lu0~*dLpcwU3Gw7U&&$uz zxH=d{`z>>qisk`g0~U0A=*?9gsQ}cQ6w^UgXRXFuAI7-G zAIgoV;jbCTk`m4LEi09VkuzDQJ|I;jK`duGYRCSQ1)08wu*`@LJ=l-}MNt`bplfau!2v1?O0=%cbM#oPm_$laoXy#tU2 z8W-3w4Emm^F(i`p@7aXM856I`#VT}+C?~IZLgTVReLq%5D5I+iN=DH)EOuvU3`R9o z7F$Ufk+oOY5nz`9v&hq5Gt%)03; z$gMC*CD=Z1-2sybEB`V5l3-(+fM$ZTTxukMYG4G_%LVo6Z^l>0RGHrtPbrL_Ac7bg zvE2xL&7%QklGnJ0nme|OEJ2MJxNq^AqQ14f;tEK@8aGD&N+A0*t62_~;VqN5_f!^m zw$oUl><6WN$DL~}*(W0Vg7eDv$!yOvsBowD#@4tbbrJ9HOm?W|t>IxVW&zy0Z-}+N zo#wsYLu@cZq#_FXLvvd*zdd!$>~|chxm);!ZT{?JN(BEP^%8{;al8#OvER5H9vS$y z8LVG7XP!)@c$vYqHG`O=j(~7s2jIL9E+p?Ar`E%SP?eTpEUVC zUq{kyoMy2H4}iRM+);X=?FNJT?kltn^VmPW z?5ofGje~;EaB!72lz4OqJlJ~L4FmFg;sCe5fFiKoz3-(jjuGVw9v5j4vieFM{%uQ*NLWbvg_6W-2In)WZ-Z@KBSi z3SuWCSN?eqIH}5$qQEw~Yvz9>FXMilxUKxih|L*dE_5aW^#?Rg+ z`8ENoKlRA};a!n_0gp8r`)WJ}`Kq?5Y?3&QH4x)=UHCY*h$3MkE$Rlz71J&lJ+j>o zjp>p@aLO@fzg0rAkpSv83wtJUX`lK&aZA#$TNk<;%!^ltZ!0LQFP5lL+p|tl8_R&8Osf%FZCn(4vB>1i56G7T?G;P{>z#^SP3_ z;(t2CWaffJb=f8`ib^%*KA~H#?`Q9B^YWBLB z2Efzbq4V&odFd6@A~A0n=9Y1mt18{sk7WV)o~DQfCSuDCh4`P}lL5f}>x>(AUWp`6 z)ZZlc;uXH=xYAxStmv(5ghL9B`u6YO!AnitEt9}qJlSA@ixkURE(!8h&t8Mf;Btqr zwcmL|xw*z{`S>Dl6fluN+yYNvBO<(;7lKgI)g3F%x||oTBP|HE*KySMcWN}Q@0j)D zXDtI-lv*7uXqQ}?lXjGm(PC64&D26UPSr2`6$tk zK?eJ^n1yE8uV;?}%-t~+t6}Qj8_z**Pj?gDSZdr#$(MI1S+o`sXNg$^N4@woOSZs5 zn%kNWQ+I-)Z1R)am^O`U5ApXwRPndLdKc{mea!|Byk4uZNb}+{{m7WLmrv}MbMlSy zkv~TN*>)5`83={B%(SlAf$!y@-i>1QiQFlm^diTs=W=iZAXCox7V$QotVwMEAg_@# zm3XRefVG$sg>95pEf>a;b@@sGZbW76KMhLM8E*5<0D!>jO~%c{7Ua~kVuq+3bV%}i zNMshFP-4FI)Py1A(&A`voe10gL)!|lBb7LxBdFWl)9KJNf@MNzlYxSJN@TK}Jc+UlsH~W=Q3jC!qvqtKu zCRtedEKNoB!F&0yNT^f;xtd_$n_YB~-!gg3pWq!AxgVO+j(i#=b$8;!+>%XTfaX!f zZWL=wE~x=3i6&k|1rn{mOp%tErHOS)S}qkyGu6r`K4$^kZ9gdv@pgSM)F-C|IC4QN zk4kAbKv~5M6f_4NYeWl`7OAoUxtD-Nff9tN8QNN_G=y{A_a-b%8v`?)mDf1yJ!WxZ zSBK;1jJTMGm)?i7M3Ot`_-d^Sua|w36QQXfx$G+GoZL9E5~3 zFx*;#9ew!}V*Sw5iE=I~8?VDZOY%~Ldmy`{Z*LWV0T}lwHl~~OVz1J{B>pzWg1&%D zHLrtp?!P)5xALP0WtfBO7it^%Ym!t9Ec%v81wdc#>;8UQ<{sP#*6Viay^y!+a5VB3 zz}u@(9gm@cbyKvDps!>YU)FWdegFtjQWjazCV!_EvU+l%$@P@BN;pY*w~jZ{YJHjJ;Y7S`yT|H|?BR@fyB@%{F{iZ#i9)fkhmJkhrKdI8@10 zjnJ}GAXH&?zKJ!P_$O!{sK8q|vzvH1Mgd zs4q-s0`Uo|H&x6RgF3mZJTr{jrXq+~B6yQO>dSv>)8&HArO7$+X2@aIb$I8wVN?1h z`zX|(4p)f=It`wcBUD39iiiAP1@n^;UKPx#P_PqpOF^F^bwef!fGfV6n9=c*UCXLM zZI0_rBPeIvJRypD{^}ibD(bZUD~??vU)!j_Z!Yy=Ra;t-LYVHJhhu();H){CMellk zZL*u5l5@157xg}M3?doI$VE-UcnX&F&6K^td~n z$5HKQ-|yVQR2cN~D~+yPZu{kCM})^py@>dD+PCLQNdd{)EQRavRK@g;m3dKsROewd z&hN19g!h+wU~b~0B!#z}l3nCk2kX93_!!%ALN=f&jR}vA)dZ0eP0GP%KJ5z2-b87s zJ#TZ_pi=bQ>ijn}MVJNk8(&ysM8eHp*hv_ydJ7egn3ia`T}?II%@P4q7d^T>AH_lX za59?y5?>g9D+)Hs!5}HY6Ubk$l%T)-?{y1& zV<0+}Vfu4#wCYHe_ZuBMuMo_kJ)pQ&I4eHTccFrcs;B6P%sGc;V&l zX{;L+sqs(?gFFeQVnSIu7GKIhH9gwE6FQIkPc9oWNE5&#e>^oZK;vOGyW*vtwG)DH z79ZKrhK(wsa8b!?K7ib0wzLTP3}{3iIFQs2b}xU?ll`X-AfSLe%i&xToCvXI@n?c# zJDy%`Z)dA4S2`uX=W%L#ig|F^R$|V)ve%ek-1}J-%4RLA43QKS_?L*EL<`&e9t~4* zULj~MMg{(MRX6e0fm57UmcQ_B9MXW(-ltTEh`Q&i=Le4UP_dC(P((Z(qG+pZ?wQLo zn|20z=)u#@{EIBZ>eylN$=B?F52e|fMg@yq&j%L~<&Hp5iEB52Ki7R>H_IN*G-v7A z?>)8q&kT#?!9Ht{EGVW&dioh;*!oSI*lCvqab=>Io69>KcU7GO*Xao_D{9CoFJXmm zQcR|bcogn7^fXEV&5f_1`oObS?;G1WN8-(P^Y8OUUC4KL$qpz<4zoX4$O! zQ45%AZ}wwQA?5!=n{Y1X)zD6F6mpmgR3CaIy@N8=@IPYdQPI+UZna!84T!GFhjM44 zAA*^O{6JpU&*Zy|I4Sj2j+$z|PkaYAi9p!_go^c2$_iPgH_E6tDKo1d8 zfX?eh)u5w*I}dRa^(xc(5Zwa|8$M2{6Q?Sx^sHafDD_U&D6?oc9F0KVkZQ(ng#LSZ z1{*ch{-ozZYSM!r-iuWJSWK_@>=gHb{0riA=E7iTG>8uL$5iDb_1~{dbRWQNSwH}P zZ!Jx{Nqw_A@_eMMVufUzT%jnQnil4qwA;e{12p)M~ejKEOC*9{A|C(#*4GfE1 z3rWav-m9^r4(22*h8xxia!!u#;!(saA35nQWKDEZ+-d(R7fk#T4`9}H0shF4 zVYko!kc%$$d(l=)TUfB;pt)g6&xoz$N#-la-vh3kGK^nn@BgzUh2ZW+V{??Uj$JU0 zT6(pIyYgUj^J03vP?JF~`G2%-cyer*c$1YXFY85m67d6t3V>Oy13b>@oU^s+;?53L zImkn*P-ZG8!^k05%{gbP>RAq2Qw&Ys4yyXP0kz8AO)y7*MD;l`Kdj_4pPi-JdXXuI0YGMTIZ&%aTc~kfFCk^E z<6BMrHNl}?!K%JT>k&~v?F}Sfw%@|gP_`qYsWyZG=?AtO2elNf4&oqYVMOHjP=6Lt z4$yU>jTD(_c^Xy_P$3Bs?(CxvJ|Nm4W+WtI-3rpMeSZzXn=LDQLG3311YWQ{pskDV zJx>u!R=9r@1Tuk5U91^!x^ynV=m1OuZLZgGI>pl6s)lnGkfu^*Tr$iIPKCHA zM*JbCn?=dRkW)zEcINy1t*GbrRoF7AZ+4u?e>TKPTXprOqup_YPqIWmB8R?t-OCd^ zei5HWT0{*|8p?}^U64ko3oEP4>(TwC_;&QW;|>&2#`7%TlY|05-8`!23xH$Ys4QsB z^SpJMAWvX%k8gJpYdOR~=SMy!&F{J1fa2T6p~YOrQP|Jh>-of>qko~Y6_r?p(C>+- zk5?Lc_$Im9kR*TNCFo5S3w2_q6gPk*>7CRPu$4m$LIIPZ%r@#_pwZU0aI4Wx!vdZv z@nahu^`}oqkTv~~GTIL7v23h2kM!eoqAsXdyUnu7z}}8`sfnKmErbsrTvPYtk9QKs zfe($@HkI$I8W3XsNTm^gH2f$Zx$(~D{{ zwzQRC$YCsp9io5UVa6r=D41FeX^LNO(sq!f_{7H;-hx3|THhS(Z_?+@u$_EN0j98d z0^kpPca6ragIahd4z$U&fR_(bVybn!zGU%9)ds*3y=+7JWuFzbxvw7s>cAl7IHB?D zmqecooFlpymKY zqs{_PWY8r`d3{Vv3~ozss7nH*t9tm{V#>5mEtH#A6&h62RisA zn6T@!?X^XL6Xj-sLyqeS+;epThP{_r(6v7Mk*sfLs+Epy`rD7MT8*4rCIvhpeCqK% zXBvUFn2i%io*66VOarbA9WVc%y5a(HsoVw8)i4EkF7aQ&^m|%%LdpD4CMak|U>N?gM?aO*o zTgQ?FFWtBM@?Fx)X+#3audyu9ia(W%EHeHFpN~H7lKim?GSoB~POQR)mqm?t*P~`2 zCDt{`ecX1G<|N0jBwW6l`vhP4rINDu8dY$*J+xp0QhcsukHL=;(pmEq{RYEXpIncF z68fvPr#uA=gvgojI$PdXA@mpPAvWvZcY12cTZ=*)P>0iP^z46^4_+?^nrIXj9Xl`` zf1JcTcod!d#H5~1GA(41dC(E}E}*{=4U%&kznfM+M2ahcs5}fOHK7~BdP~n3lNe}i zSg0muklye<8`PXaxSpc1HZ`XX?tFdas`Uxz|MP-Z^U+%;V%^}Thn<>K^Co^oXQ}Z* zlbpnIeQ@{-vYJV#d~oh@xs{Cg#IUrJ&73n<7O|>ZX*K;zztTT^1l*9|17Cd0_o2SO zQyg=(1-H+y!EAZxA5ZR>c2!jns%rW3nELITdj{HSb|+`{hLt#d9B7M~R$xO>4A=)$ zm_EF@`^XJKO-6v~!e~v-%Mi?h-=pUg&qKN{4W^%C3o9^g5Ldv!!`=C3OF1i5N@NxG zS08sSCPA%Q2r9u;*z0q|^}!?MUbXX5`HDZA?=mvOkS+tYz~zsl|8ayhX`_UswPnzl}wNZ#j%m# z?L@Ic(g(e%&Y~S{ zytTB?0=p81D$9sM<)JcrF290ARLzUM* zsQR~9wP~u@pF9A%o-}2eS7=9JC17b_h9G0{KJ^~;ffb8vhwU;8iF>yh88EZ03b( zT#ZF6Q(a__#>Fhm<4prLIklY1gP$C>WfH{u+D z#t8RJNkhQklig&ieH!S$Zs*pB2t{gwxm`3K0iP^09{q28czFE2o;M0nz9-<3osi+v zy4eYoT1rt^!4{7pIyoHwR&trtromF96wf02`$UBt!#mMER&T0iR0@w#4n2|1Wre29t<>fIa|8}cDj$3u~~p-q@`IN*1^VY z*q^#bo`@~y9?X`l_Y}{{>!;fi*@b#Pv!Ga}qX-L|vTT6~uOT`>z$Ug4025mg^c~Ug zLd+$mpk@t(^?VNW`qIs!JQ82pFr+#&X&UOhb^XrJ!wkyYZm=lIz_||E@Hg%p^+0DY zqLJO%FsP~Z*h#mVGN!b9hx(E#^JJ)xJiIYE_?i|fIshvk-YH5lVRhb$5dPVMU3_(9 zU;w@I&`-bNg<=EKXIOSn`BQCu&&obJXeY*Rq>r1}aDR5Pf%3MMhl1>KiR``Ft4EJh z5F|s?)0RC(0w_st@<_lQ<=&{%J@}a$G3x9unVk($e) zh4o6zp&41|lKVedQ(X{Bd{734=cJxg0X(esT0~vWNj7}6pSB_iRFhKY-2`C>iicfw z-Oumu$Z4&{xxqZ%iMhQD$y#XTpFfueYm5tvWNof!G{dhMf_$s>=dU>ZN;Fp=dqywAI^Qh8A%yGNC4|b4r-2_BY`^4t*O`*BC~C!{y`IE z_vQws>{@%*rbZB6z$0Bd=if#PE_Zt8NtXTD8q3|C+%0N#GXz0%mx9j=LtAbTx~~BA zr>bOh?JeLRCMpeTIxEOtUwI+dc^hXG3QH!w$p3!NnsdakTAgKzJ??>Sk>vDt2w$^g zjKg@r29>JGz7)7RWeA6B2DMfl0Ra@l&3Xi9I8xQn8JI zHTFH4YU)A&eiNofZJ;M4w|TP=A4apGA>4r?L6K}6m`l3g{Wy|e6K`>}P=TB$2RZH% ze9uKycyqqN%3O&#%C1rz`Tiqtcv3lXF!9)M+FTUgbSWVO78RNW3CRPTcxp^VwoVHX zwjyrPyTgRQJYzL<(~!7U;KhbGn?clurRKDBvgoXlU4x5nBDNTi_MM~t@BeH~cY6v* z7=PIrPq@`0NU$OS4Z0*TEZnhu{<>$uMxr@qcCrp;U>d}fBCdrpeidRx0p?gk4GQ|S zS8#*T)$w*%jOSmLIObV$+}8CFZuD8Z_q|e5Y2@JXL6pGcJ?NG}Wz+Ue1-#zgQos5~ zaz0u|D_KwDBWaT99Xc^PJqJj;Q%%(|8kVI;Bo^R3Eurvf3usns^USVF;Ia<(pOk>3gv*08V-AfE7?U;^B&PFoo zL88#Jc=4V@?OCeb2DR0nDviYD&PKQ4eJR)F2GPes)y9 zvmqyi_0JsCf3Am~6Jo2t_a8#+#m($(4shF0u^hxX*rY5)>?=6zA9bsN+OS>0y%>#{ zCkJJ7=A$q=4{AQf!dd`Mq2$GdX>cNBV!@~ryankR_{+LR0ay@J%d7d#z+Z5nWXP3p zqDMXsEd@~lgkO|Q6XGgQfc({oB09l|o)hQ2)@>hFb-D!zpP5*_%a3AaPUxc}CxOsU zeZ=+3kymO!&`Y|D*HrV#%S{W8pw45xc^=EO>KBewTy;-9c054&C>5t$ytXXRas$FM zsd9L!Dq#37_D&S}UdUL_CAa(-yb+11HdhiNG1jESBJzzTBm~UnX;+{N!v4ZS;=5}7 zX1)Lt%1RKXCT-Gzp=hDtA6OKsjXsb~Y@Q4ByLDa)>}m$JIn}QM4E%hWax6)Rg7+=` z#C9Re*6e+l;F}p1Af)64wzAnG6!A#zpP}cu$Wzq!fCLDRQCUxi*!~|dv(UH7?*mIQ z+QRo8*%4hO%MC62MGnnZU9x2OH7(yAfx9Pa0?b2sfTC)Ll<%D4G9@;keEGDkx9$H; z9h#RFJaxyh0iLlE8e6{(C)1zXph#F#kEmo+#)7T|xTA=J2zG39uFbl}tkrJV-N5h> z<3VO$%(^+P>K0ZZWmXE$y#vi;BYY1URE7}m?nf%&e!)%eXa~qri=EZJBNFBwjl5ST z&}Wb!`DcM>xcC=X@Bpo#lybF#T~7Wi!L|cpp;3(FJhu)T_F!+1e>@P$Y08VJ;60X8 zUv&C!LmHH{Xyp?V1k61j`kjOa?Ttx{5XmMa{qcuBp;DXvHxr@SiRNFp$AOFd01oEL zsZ<)WXk2{EUl6o5`1OTA=IRE~#mH~?t^^<0<{I3v_*Eb`WTRS9)@x^^m z+9^v@y9@hSVV<1rh<3SzM64d3I3H=FTfkIiE3tt3ll(nfFY@9~mq3>@SVTnI$olq+ zv%Rk*VGDtpU{W%gEwj`sA~n3ZI|MqH8YTIJ%p4P*DV5fgv^Gng<34*7bx>I+XA#uG zVG^)gJh6J$_MYy%RV_Bo)-L!acDQ`J>x6%<=Hw_{XNsHyA_|`^^hh@ho2?y{>rS^= zZ|WK9oT0e}UTaNRZJZ&*?1S7LzRQ>HN~1g1T@YCMduIoMu{yJz2gRi8*9*$R^@3NN zcC2_S@*hk@#rS}pHo2>W-#mOag7wOVE%oCz8<)2&%`OMQW?{C%4M(Cxh&Zr`*H0W- zq1uYDBIh8HxLd#MKA>Z(ax7*!JN5xmKSBs|k?ULw0FXr~(UQtx^x-uc znDCnogLuNX6!%N*DMfYbjAUddW)1=?MC({?_0#+TU9|`FdS+mLuSkhb`cVtfOLHTS zr8&4-sm{vR)JfR4AU>!yX?#31>7iyh+X9y6JU3S~O#IchxfU_N(LwwIi|wz1%8VsK`w7AMvR9pROo73X>407LN}mOkI2nHKe-F z!Xsjz8eq?WR;VEjCQu~&13MG(?ZVlNz21xYa}#FY0KJ>_gCqe-yW$8%d3c($XAm4x zeS1`}=OL!She;wQ$D~u)QHzLNqf+vYVsQ*ut6o=(1s7$Jir#Rs<{{O4bE=rK50W_x zS;1^KnAtXDe*d044{RGvj%sQhTYoc3l zxhV`*qCn19HWwi*)ab_HG`M66dNMv02&b6APVICMx9g24uE!v;-jm{nV6maRcRD#3UwlBi?wgtT?#0?lyo5gb1{ix2>}}0_Xj4eRQT4#yqF+I^ zz8&$>qj$oKIuctasVURsW2_bazimS0Mu9@Tkoq?itx}NUpuZDx?o&4M{sU0CNh)nn$235C)+(#oEuBTM5cnrX#V}Vh zLaf>kHDS(4d_8~^b9(}|LQ8DxP&y6>5A2>HN%g}+*$%1~{w#;qOs*yVU6Z>p2-5_0 z{HP>YB31(kig%nnNq{^byovM`Faq2J48xXk-i)0*}k zGy1RC_rSH`s7{lt^Ku{oNEAhupBp;^Q#u1$xxY2Q_~u-HU)*TUHOdUK^jJQ?OaI=$ z_2PDCLXo@fxXUynn#_8G0HfHH((dsWr0v*fHlTjb;arJet`{|~fVh?Uv=sfUW17cpZDXk6tZP1=SC2{8@!3x*h*X5g+d zslO}IERV??Dx-GEp;okk@E74b6V^_~ISvF= zUL37B`aGG-xWUbnsw6m_YJnncI}$V{BF0-%922PHyr78-l(Z9k{oubPtNH?#I*1le z@*l;oC@hc@^rRUvJuo!Z0cIhQ$j{UvF9jq4dgreLU=ST5d4jzsQAa^>=F=DyOfVlj$9_un=a~ibvWX*0_#c+Z9J5T(F!hh!A=HE9IxbnPSFP-hZ zp;_P;4FdUa$+V}nRHxwjjwJ2Yv5!>v@2l82Ay6qQ%Eo|6FI0t9t@n3NZ(KU(e_3-?lR-FWB z<(0T}JQBLjguykPs{OWnlG!q>HL%JJqeN{63YXMsKtdF4p%DdIMa=WlqYV4=k#7~WU7h0~hqjdp zDMw9o7^Nk5;F|{p> zw28;7#@&dcfom7*i^A45ELh;JfQhapiFgA9I4DS|F!q$(pAF%H1WiT)a6WKRw|F7( zD;b9}u{_MC$$nwlYpzbPIb-jg6EX}w`Uan~A#kcEsNJ&O0{UWRNxNwBoir;gLx-zY zM67Zk@%`R3v{$dzhygXaNky82go9#VDEym2SL2rq>o~JkccB2%{RMjk`mP(uMOY;k zelG+Le1_YYEi`wv{YtoDEod9*3FFB#JI%(MIs#z%*?OOK1A5_>-MDQKV;00JQf(j# zY7QC)xUnL=sFPRV=JxIVyH|E-Q?OT5IXMYk)%r+f$fPSSntn6LqI6cB%O58fbRH^O zhwyPS4J33t)RP~r9?Q;q>8fM3#kusjX_koAnhRU&4`^kaPN^53D z<8+Q;r0Pg^`>L?lM?pbkM5^N#u1|MZ65w(guWxEHWYdr9?m(X8R*>a%REum!@kWyP zD*4t$MlKCr1N0j39)QqYdBq^Q(Nk`-%r zrYuYMVrzpfMbYk5l0#&4-z zC&qsF%hT7PG+4GK2)8|x6!}m!cWIqcB2FyJY-2Hn6T#%N5pNfq`Uc32Y#ltwWnuDp zB$?XQ2~%kgLighjy>pW`>NC@MDBZoL7?yY4Nt@X>iXq9*gBeM;Meq`fvxUACX7@Pc zkWfeFv^JhIoE_R*JZva;C8-l|i8nh`V*{O`qSb>x*(9=ky@8t#ShV>z350g;ldg$= zK+Qtv@bg;3(O3~V{of)9Y8qk}@78Zf^2<_L0__h~v~aPw)NX(mm!Fw>Br@$8pSj-p zfo`%TH6jZykRa|Tmk5?oU1RyYbf5|MbKLX#^hS*J&g7dC^`L=pZ2{o8v7pF99tnxK zUGXC9S(bCGhuj`RH&B22RMS5i$hyH`s9*VIungbLWzw@jUp#&#~1 zCk<%a60(3zB)LoKYD>l^S*tL$3NnA34CS$M50MdPD35ECqiNx(c6#>Ea2E#98Q{Zo z;X~Qzed$s`TrzdX^g9ftZoSeIKkunDt5yoYPocki<)(TlV5(y+RLZWTgh0%OhA2Vv z%H$4c8$ijRGDFHWkq^KBum{ZzD8~9y6T-%K&6U?;loE@7u6B2}+&}ReWp9F4^F^^y zyvk}i7Su=IO4+?&&gG9Jd_wzgmh(bb?J|%P^=`gH)Vv14(TcxNnhm@FrY-GGuk>tI zJx_3{O1+pNqKgIZ`qw$GQ@Lv{FbpuuBE#g#>U11Y8Xa2>=$lW%s_J4zz0TYP zZHfo*mM`^^o27L8`LH$))OD_|PlFDv<3l>Ah|CerO!NG7EHHfn2(Hbw=57hgmvReU z)`IrHNj%;_Pn>hv9RUABJEO>FTxv8`2a7(k&NG1Sw;)rz=8e6?FUv3K;_MhKOe}m6 zuU6TxQW#s1B9HJNmEaDvt9rB6xND=R9gS%!%adSK@U_S~P{3jr?HsHLbDNx6*?`o< z!whYrp`VZ($1%qk-~0afk7V+@e7tZeJ3L$g@o?hUDE8n7K~R8$i!dnMdbHD=gG>QY z?dd$x1r(SOBJ!_w9lGnakPTZj5z?!Og5mCS<(b5gaU^nfmoIiKnTt+hQ<4=*%ihuYR6igFfC@;Bo7@&~pkmz9F|s8Ia_XLu+y6=I`N7 zDmADYZ)@T$7d4O0;;_hm?DRzHA{-pjO)alZ{y8XlG_oO)U3&`7IkGS4(^FM(T~zMa zDksO-<)Dby06##$zXuiW>e9|2DxYh+;&r_*JPv;>BTO2c9pUmdS5nQvuYe(Z&w$1# zVn%haKMGm=jMKR$h3mHz-Ju`Jw-J%7s`cK`4*?Os0MO%zYSJa`)0j?VG*;Q1iEd1 zwS8r0cS-{|OyGsWHqPjV--lX$`(&Xv&0k}pU4PfOesBS=ohV~Qb!wxt_;)<8qfY!d ze|bAz5#{`iX%!ha3^h!kQg5HC{8_+p7kTg+f{r#4x;Nn!EYH&ohfDBezhO2p8iIfULA@OoEtm%j3W$4s9_K+DVuh^eUK+u9&jGYJg!k+ol( zXTgAdM$S)V&E5(-188Q*;#=vw1=^36`b|!zZk18q177n(^rx-!pc&#rSwpi7hFr~_ zK_619kYAXJPZAW4O7MNp+iO%mIUuHS4H-OjtXXhI_la8)B5&t=Llb@?nFYa}V=rkr z#QcTRMeL%BuecyxHH2Q>J5u4$rej;lBd0ZzOB9l+sJd|D4I6&|j*madRJv2d5^-@1 zQ|F9&DwK)CHlx|Z#%o1W+k)yM;FjOVFUyG;HQ=%=tmHq(lYm~QQ5GV?SKk%k5mqqi77zZy>bjj#H;lBvKsNXz_FzeK)&>bVZ z&C4|GMFKb4l1%=D9!$1l+lGH(M5*d=d=#C0T;Q=TfPuyLw^~ZM z0}v3O8$`Y-EH=3z?$lp};KPqMP@WODM)siph0kAho-yce0O~G>ib_15TCWVGpn>mf zV1u0@JQtwNPld;TYo(5J4ancf3_2!{+hB-fUeO!4X1NwMWbafA)>j9TdSOcB&-q>* zRmFyDe$3lSq;C8RAj*(K!OH>K)ZPK9Pi2zzr8OMQvFf3lH=v?6=Jx}gZj7KqUbq2Z zlRjK}@pH=~)nl0+Y#GXNT0{E3o0Q9Fl5bw*wnGTZap+olHU4z|uOkOPm)uq)g=1eC zw}t=P9hw`0RQ}PID!7*78$+z-b*+ZfHTZ-EMnE31>L+Wq@I(3Fi|#NBdLOZ@o6R#_ zs#6#)Xa9>k4kLcAuf9PqFHjbnaL#vsR!HHq&3{6N7m~Xc3-ca7Uf0x4;V>S0LcVKg zAeeFf=P_>|%vQ2+EL~ye=IKdH^P6lj?vz8dwzW236)6;*r4YryGZk33yVUtz09z25u;dPDuTK|_3b~x zv0QuIz94b29fFc*XstvV_AaE)AxpifO7>IUxbwuEhdkaL|H6VH(*HL%CCxW;${T-S zC7%T>QpAIUYDq@91-TI#6l%$Le+ul(36VK46@rYpXnT!mP}H#sQ6%p7eSztGrp4RDmSAVUPx*Rc2Z}%&{$9(RM1l5hKWs_kc60P7%SZw|3e!0*vYF1G)+toM9=0- zAGufgj$&Xt>RLuv@HaEA;|Wiv;^>v)b zx{cE+h{#qURpNVG^q8AiG0KJL^58BFAeQcqEJ1#`Wys?LX^J(GmE+NFJx8y@UZNd~ zOtv$PE2iG-$V&J|wyNPFn8jHNY^s7TQb2b4LI(b&x>=Q>14NFu;^Mzy>9-plY`vpg zS9whmv;_XlJC@)yr0XPfjKmwD9=J6h{M;ch0iNW;;>TNm9?J*?-{L+)9u*pDA}Xih z5QNkvy4K&W(1wS(y1-*Ce=r$bv_gvn6AN&)p@J6i-?3ypUyg_gz>(rFgUnD~FOVJP z!<oCTR)i z(g%n(!GF~omzdcu-MgG&3-Spd@l!zvY9XeUT{9vwUQ*aLn*Idj{;GQtoQi{GJ~SBN z%p+bM^?5{PvRqT=65WvTs-<`^6nsGp#S$^NQ|K<_yUA4eB3Rd}R*iJYmyq;x=EpTr zusnRa++JE}^Yfj0YeuNTwMIXY<&znEtGgnCdu;WMZHPVG%hm7B^Wg;#`C?h;PHBcK z=X^7O;1L?iE8`)>>rbefp7K*}+Zs`-qhOjF{DHol;2Lt-_i|U+5&Gtm!8QM~p}8EpETfCF&(HPHxP?A$A4aQW-{ZkNxLW?itaF0Q z8d-fWQ-lA1;cQmhl|4Zq=jZ5D#B@=TEb10M8bZL9M+z?Y8kVGHa^cL+>tfsuULjc( zVrT%<|9XULsj5}5ZC&aTV|sKoy<5pup8EVe^#}9!FFUk>B0dbtl#3&N5d2%9N&C$# z^?~sgG}9Bk6z0*IfL&)$M2T!rU~F~>viKq%N@J-80mkI180Dyc^ary4GaWf{I6rA$ z!68fdG>>sdlb3zcxJ7}2=Bm`4SxVvoFnCcB8I3QIfHttV;4Td=>gKflRrWmk^?C~3 zmBc|LZv{>+EsXH$VjAk|1iRIU_%<8BxA!Ep#G46y02L$PjQ<`>0G)4A~ z;MS__cG0JJ@Kxm_(jPTo?9rZrq4%p<5VFq@taclTt&Gx}ihpXaC0z+aQ{(2l2Vn`)W>FqEaY&u*lhoPV418B`;Lp%w$4ME>hXRx8X5~d=1Aje=f-bwcIz^HiL0D za)YWa)5}}ixHALiSb{-Q)2h%w`X=Q%ODt94I%j@Tgx(>i9NsUVI4dHvqKludxlCQ$_ncKh$${^(l@+${_A$1H9f< zn|@LRwtsbjG(L4{L)@xYUfxV&2lllSnmX1V8Q~ac!g{DMI>xrrRf=!)mD8q)5O&_LvuZqnP*_wT!NLaF6a>ybLoX~Z+ zARse)KDjeQl+T(#^(M9xC^*uVaOoEY*o#FvLmrfFdo7}LSj4b#NR2Qnt`U}=>oD=^Msq=BbPmJ0A{Z*Z%p8S0{mWBHV2`Sf)-Swr$s z4qQax?o*X$Q^Jl{Y{HJEUI>^0ZE2)g+kYAJ)d0zS!|4*QD`AqG{ee^_+U}L`V_r+q zWM7^uLJ-SkZ88Y8Yvauo(_;z0Cd=jrQw*fC8)Nf4w~Wsf8_kH?fK)c2`|GIX9ZR@f6q55x5 z4}+1Rm)%;#T{p})qc%)L#~nLP>PvEC(+>24Q;|63o%_1n+~i{T^Y90E06T%D2=T0F8-c+-2JN~#T zGFzb<##$$2@5C{p()Hy;zE#q8?(YkYvM884(cKtlK$Q6;|2bv5|{law8fs4YA z@`1I-uIzONVdy*D3OjK(h4CPzY}uu5IVc;_c8YIAuG4H9@=v_4F9+q$K|SX!lek63 zMguAn3?i-$3@g5?B(rc3zoC5~f8DgZ$l$3`DW_S( z9-A9`e2#ebmhs1TA;B8v#8-1ifW{^0@4Gx~nWQwx8)l%zS_~#_J@cTE=SuR9I!jWx zc%j^HU1dRthFA3To591AV?Ve4Za( z;wWa;$6P1&l0@V^$t7pt%2JAVZ37inS_Kc)I`4O9n!0UYer5+rx%CPDa}zJ>kT71$ zx0U4wO`4Xe`>&$Eps#@l^i0es1~x37vx##@d#6JvmG-mJSxg;lZoGV($%D?&+5b{l zt;65r{75H4Jco7Vfo6j|KCQ_$s3ZpIb)Qc*!n*PF-#kO`lMWD5j!%7G2?b3nExG&J zpgO|v>3r~`tJjrZat9D_W=1p*14wNDg@z+ ziZK}zjGj(HTe$GwSx0n^$U#pm9Q@0*w*Ydx#LvQ`4~$$a^SGnrb|qJHsg;V^^wqUb zvJL=!GV2)kew`1>I{N0DO4~h#CvjsHX@|uDNw|&tT;lmTA_Iwvkl@)+aN+ueOBpCn z7mDdo^hmve_hHe)9(-rPs|tkw!j4^U+DRMKmMPB22`>D*DKd1hAyuq6`F)t+c9<=XNC(MvO#%pL=k6ovUWo~1)4VH~9PBU%{-DF)!l0_bd9 zm+kfs)^JBhM= zK36~_pkGbnFgXX(LZUoTwE6#`dPCpPg~pg zsgUT&J11f`4suCybHeBLHCw1-!}q2(t$VP9SVni@cb7&a+rL$Vbp42%%xa%8^2E*5 z-RkjbB2d*#Wx=ZtNjFmWhIKtay4Q8YVm4uQRhl@h29h!KVnju!bVQySbetQWw@ETJ!?n`VQy9X zn^*{twy>)J-n^=>tjZoQnGHL8h}s6;>kNT6ikR6;cmrLOHD$lN;k9*Mk04JEH#wYm zsuK(pgK5Aa5wiy6m+g7IlRiBQxcaA+PN3jT$228SPiPR9;J-LFlkD^^I=ucS>;{rd zGM6rs^SJd*AB_+N20`eeCy!u6u;B=G%RQra9{iWuIN14QO`_cLTgc>(*97dx6h;~> z?FxpFwms^K)Y$_DggsDxlLOP=X@A*q%+@k_H(b~)&*It%cY>%@(GhL+fugDi{uzT? z!!Y0LE(VuOHK`|+$L9B+FO?C;dMLIj-14$7jOd==}zA<)gYmq0xz~hruqVt?>Im7G;wn*?_tczfRd##{$WX3Y7 z)EXQ$;<@%g5UL?3fXo7p`Pys~f8Lh(;6JLrvkJ#$?W$v#E96xk2-e($vW5bV-m;ZY zTV*<7HW*=YYlj``OaUSvQ2e6ThiHAE4}6_@*-*?HOPh&y-Z}~>Qt81%IiakT#dQtX zA5J*kr?CXzzpm-2Gj*SzK&e5loE*=e;LP6m8OgMyWp6wtGn@W*2v0IfYKLo~#C&qr z*o4vB2@2nF+@gK{@?LmlP%Rc=;Uj$E$D_+w02nSCIU3Zq&~4vjb`$8nb~`I2EZJ>f z-SiE79U61ZcD~qVo*^5+alf^-sSED9lGgs6OnS(#in zUNf+b0r~44sH=|>(8l#3cO_?YW?D;pUAp&h)7nVzW?iiT7V6R5hZgY*erT2e4Wjzw zVSE!bAy7RCx;sR{fX-{BNDKMwx#2S)7%^U$gNaxDv+eAqHI!pI^^> z$cVL3^1BMA^##}sZbObfc~AZou<=W8P!h$O9qbuFu}Y~vJ;pP~c6UTVKEcGpjZKC@ zj;JKau<-l>C99S1bPeU4Dq!p2F-oWD5K^~QH$&CCfN2s&dCuH2 zF%i)SCTM2&+cqn4VPN5wnDK*T9NN_0$7IK$d^`2M$Nt=+?T+%;PKq7s*)&~@>V+Jh95a>Tgg=JJ9+fDyu`x=wLvQp?p-fiJiv|Y3NH{w}1Ff6Oq?DziBr7 zLcwxdqz?>CB^NL=fl`#=;L(-nidv-pvn$B(ZA$3VrO#W-4n;aG*dVpgYlOt$_4p;~ z>ZhqNTx7M&CN|;D{C!+qGmH)IwEg#Mq=%LHoSgEr{qmNGwd0NXjuEaJcZVe3D> zk!K{zh^V7+Sj0qaDR2k`FE(T zb+;QCoeu6;@d7lDYMZy|RVa3TP^8>oRl)I-rjyvK+sz9`M56q(vx#jspwSsC;uM=t zR5^>J)g?RTjsJw_0%#O18Uir`eG=-pOIPkl8UH*YICTg5Z1 zgv(BedVy`p;j;7yu7~RMM0|owrd~06wGo3?FvFT#s^^-)OHlNipR{s1zSZ-uQAJAL zO-`({_N2DH_*u4&V)5y}G_U_4cDv^*sMTg_c-zuKLA#e&3jGr0JONe4ZYc*tdQlLn#Kaeq%HI$4RyIn>)duAg{ARU50DU&=rZUu0JX0of>Od6r)Yg zh2%EXjM8{%c17DvxEMSE!jz1!lcdy1Vl~_GI!f=^^7Q_z10fy(p!lZr$Rr0uuGOs@ z!_mdVYx#k5f?i@NC;Uw^*-Bj6oC1eHQ!Qd@mzPa&8h~zZRoWB(NZJ3f%w_QttgYya zRc6Yy$AwXyP?tzlM^)?<`F?;WLX}cA!9>wZao3V!OLv4t>_<862M_mD%gSx5auD{E zf3kqsYFzn(#0xf4$X2YB76}?KHi7>_G((JwKL=LAl3@c52c@%d82JefV@H@4aJyOn z;jTyQu4?A5YU%QyBZ(_`ep|MJp{2x_r$1zXmu&l-jHn3a^bitX?Hv<_bg_TI5%2&T zI|mRe{oe!B2TCpw#h}$43NCY$I2yxC^2q3Rh^I76jIlQG>#+Xr3?u>Gd%GI>IGA5u zTi?o3R$URKdZqSd(OA#ddmNlrn_+;j?;_Yp9wX_UqAvxMY9m#$b0htpJ1ujGS^prLlu1KXzi}-XqJKYy zU1O=oG3jz8>U}SV)=a;EUxE(`ZbjkMYZyR&itNX17$CcZ*125H{H1C*aSXn2fwO0t zzx)Q3bI+X8!B@UkxWEb6r2?nTB7~A@#aHxB{=rBr*3r?00)s0hHT8F&?c3x{p&T+HMq3CO9l5MwoMT!ZW%&tgZr`-iU07IFPCY6PDkQsEKmxqnhoiL zD5_(N1-)M$YAwit>_u!oJVmc*p@T>jkUUMykw|V)H+ME1?jS3^wN>)h+{2dDvd;xY z)Jd!GlTEsYM!;flOOpo7LrASzQRxIgd8>3N;A1HHu5B|QKo*=<&Cn9dx!CU{N=G%R z(^|4%U^#hELCA9%%3E-iG z(}dG03P8?CUZwul0A7&fI9ob^)!#yw81@*cdZV(itc{?@70>&ohdlD!Y=`tvZZfK* zA-FzF>9hvc@{Ghq1SEu?O8j(i2nSPwssohXiZ(*Z9N#322>Rq-$%#XPYSzxLt1A@+j{C20i}($4k)pvrfU?AV-7}N5~kiz}kEVUKoaiJDY7%n(Q5z1OJb&avY(Lm}!I);lj zpSm3I+*h*!(1nAlW9TnlDX>kfZGH1tu9pPAuyByCm~nV33s+_QH-|VlpL*E4Ix9p` zC9rLs%?%|6y#{{N_Z?Bnw2$WBAK23P5sDVxcM@NeUq3H!^p(svd~}Aa2M+@qxpsc)%lrj`y$3E`omhihZE$nCf zl~WJM4HzzJfjH+tKM;+1$bhD?#9O~<_E$(edAIe2dl}iM{D9$-SQoJ&te?6V%Bb9| zlEUFrnm1GKVxAcXlMk(%Xh}lGK8gBL0d3Oy$MRlrcO#6=o*-~xWE#5+wHVD+|Tn?VJELz|DfFkdluKCzfL-b0L zy?pbAodxBsm3r}t-pXu+2(FR#%Er2rx#c{;lxroKIM1`=_ZNP_EC?mNRtMJ?KoibX z+6kjMC-~gtyxxb7=oHzF&0fIx(f$+8g1pX}bemQ>GnYV^>*8PL(K_;h*`Ij}NArbH z9fA?e1uSq(2{K0tMqk=|R62uYd?HdIQ$fKj^Q@&ef^JWxMd84kVc3VMSLc;N3IdeK z=#X6FukBA%I|#=m;z1D>idY~}l@PBZ0EJ+`dk5t|Fb!YY`HFkyx3cBFLsE$|XfG>- z*m2CU3X)+8&TVrWI?O0j$rf#FJPxo`5E4~(^V&)j%MZ$2LsqHUC+1-9=lpG5_52im zY4$ZsfsBwIPbCW9No^>iaFQ(C3E^vWX$SE*)5tiwh)WM>cX#=Cv7MSDSUln3s*h03 z#vy*9do{kmUgU9aP~p??|7vJuLND|?g^gogRRKllt;fFV?%QZnoS0PfO)}X}FM~14 zbcvCSqJOT79fBGII=H<7w`JJz+M=$1&d>%7sMEt8(Lh-F^I2I-jV@-FYzIuxVCkMe z9;5T(+aHA}f*AZ51O-iAU{1zNK;T4K`_j#Z-(b3jpk^+0@EnYZn8wRr!c)&$w7?iTLH9Qr`Lw;)9PjK0&&bnAvpT> z;yo;UGV3ox=WDXnnCBR6W37MUcyMwG`Lw1pTFhFSoy}x>8$7sc-j= z=Um>CM3S$G4Eg*DxkLVNAJuITZ8`^DZffRC+Gf}rN^k`E*8u}5_)CrL#ux0=( zfVNQl@=GP1XOP^Fm;>LSUyx8k2Si(i2&vl%0W;z4)m}iM9)O=MW=_*C{=+4f%)0&T zZtjzR2vrfE2aXhSUxW%w_1OERyx2PK+#{W+@|x1`;KyNXav(W6CJMGx+(4hEamyRu zX6vSzVcIpHy=6O08bISb&)eCG^aiZi;P3%_F8&kaSK!Uh-To+R+OLxXR1T1`X{|P9 zHAaU{>6gP7!VrT#oykN(%wPS^(9T`!p-5MP=VEfy3@v6nCgGEJ8-r zoH2&kP6vtkVGX>EcI-vKO@Z!KA7d-#)o?@H3WQSgU3glPtfHc05B8esp1a z4|}I*)2Gi!=y;n7`R4ZSy7y85agR-=fI-9uB=S|FbFyQAuuI2d2>h-l=C)Lv3+B>a zce4MLT46cm3%y9^o71l@t^2pEuzx}-CMAxSg9#$;6`&39`4w4FBN;puByp$ZIaAak zdV%r9gjK^3c6e+jrls0jkUvBn^fBAip~6r{whNS+E#?EhPZ8tJ(R=m6qj)i`(Z2^( zz+fY`kupW%{O82~F`W86tVx^<+{8`d@TCk}5&)m3Y606)x%7+wnq4eRkSqQ8= zfJRoHWZl!HR{Qsmtp$E0N5Iq{8YdA9#&^l2h;*7F`Lp`TtN;odJA@xMv{KGEcpr_{ z@O3tMReHljy`50`RQw5PtWdlntt7F#gJr~t)8W{4)QBI!f|Z@(!@z^>nOHeve365A zMe|hm{3*vj824GiHys_8!W#N4S0Qzuodh!B@IPeq^*{XaS#o~2D}-RHE5q#bSKs_r zYy{dAktGuMP*PciCMjoqU;u0@(g3|3_$P1^(P1qXiMRhC-7;PraH)bA2yKv;`9wqA z!BYMJ3ojb+Z-CWj*@5L$Z4N#6QcEnH3(O?U20*e?0}j~Und?#cU1P*Lh^fHi>E%o= znR~(q4&)gP3Af`9Ql_V^G&x18(6hzYhF}W86IGbZ=N6W`&hw_@p?cb~_!aNmtyW}a z1RF=G6FaUXfwIYKucDAXPXlC0&5reBBC9k(iqTDbw$Z!uib1;XyY|w%C< zZk)g5j#;V2e4OQ&gxN#UghXO&W-E%_C`j>(5il^wP=4U4BTPHS;ONsCsUCU2xE7DX zn1zCst2UY(wZ~P(Jha@A`=cQRLG(R2IqD)x39N5atK5wd^}eF)S)-!pFqGCpRdJE}QY1x23Zdh}<{waJ zBAIo=0SjQQKq zp;PJ)-!*-Zb17H=&sKk*TiXlK`1YeaMf#a!3(j8u5;(4rvqLp}1r<>f}lsI!i? ziU8lmoEI~;OI%?9G{;JxP)@r(YLQ(!ASWtqVbCz9kuI~oudVEwhGfb#A#DBN%?}B` zM3DMBt(dpLpCW&B+$oei4vE@;@iR%&>X(xD}^vJN>Js; zrHpALxsd*-G;A`{U+uRi3^^_kwQYQbwXdTmfNQat3kG2_C|G($9|t+qDj@(88LL!{ zfYNe+TXP|tnI)@#1cZYtXqnvGvx@dJI5I#eczzpMLQH!g`r1)ULPkjnLv6ah#Ie4U zeEp0m*zeBtuFjVB2E|FHCg-tAqP(YWT=QvI#>6C(G3i* zn=tm`A5#1z1)&G>ey|(BCS-}?g6#u%rnQRFC%j#{A}_>|ASyr792bTMJW1uA0ZDYC z#C&ihLK11Sl2G2b8^@<5fWR_m>OW(x{Hk^num|gYQYzBX)C>18SfeHNvmgRX_f@gb zW9=JC-tMZwDhK8DYBIkT6v}#5ZTdAZ)V6w~Z{FQ&f5P+&7$PX;{Jm4}K&OXnjxCiY zz!4HbQc;0&-c33Hp`qtL>d>n8Jg3befFoJ@Axbzc7<-aXa2`#N5T$~(jJtTJ$G+<0 zQ8b%3YdZ9I5HDjF7+umkg%4Ns9J6_L9ZnfnY{PUsxev;+|HCc4dRRMn@PZOy63C*} zY_a5XnDjqj$#zDf`3KqLHd0jr$s)U9`?GkNhf+U?r^Usnvt?z@nL4nS>Wn(T0BRy1 z8?k^*rxkQX6Fukg;_gg14Lp3ig)|MRoEg?4tCD$amOTagDUrzn2Oz9g`y-eAHXOeC zdgyc3JUAsw{BH~i!&L0>5ML+l$BR~_c&`tId9<4FD_ME1UeeX9pvc9AcQiqC#kDq> zoH>5R0sd^1^jlCMObiMUV1dIduDG{I9ClzEKqv5PzWvb`^)mp-NgC@J&DQ7UcP(n_ z>@Nmoezgi%XB4nxxbEuouOc@K$sBNgwD4o9NdnbxfA?$JoNZz2)Ysf^1hXMh0Yaq) zkyp#Cm2p8D*Lu$0TT-ekj>AP*H`J6;U?C{f0}Z6>qs%(IOuIh%R0=1RA~ z+8Wd9i%*U;c}YkOZ-+ZAm(rEKY0a&uK5F(YbaCvT->c*meeoObEB?rQR+{FQ$4{{P z1+B)OgDwfI`_Tt^Fc(o8kFT&USYFiaT`|Gc3%l6UO*7k@#N&YV#I$F`IBw)HQw_SiJ#u1zFEkSTf_fASkj=nQPMQcL;7n#M06Vt@gF3LOd zd_ABkuS9Nn6c95f*n!|q@aPkj^z(V$UoF2!UOrc9{j!Xa4#fb)C5ge=}{kpl{a50%V=k4 ze~ph*n$9Ae<{KjS=9|hY^YNd{F}p)bV$MWcjH%7G3vm#!H+^GFuhXeq;R^zTi0dZp z5tlMvWarg&^jBC>KL9>gm(0`UkKV9F7<7x47vZeKZ`1F$f>10dCH%2`@hA7hsI};+ zwe`di#w4`i8n%4S;CrTJtiyksw6(C#j;H+K$z)8yaB)Z`P=tMZyEkFK`Q~Pm^aWF5 zg3jdr;Ocp0DN;77hny2<^(qFsuE^BaawuSR{DGW}_)|U|+eP)ZWQm@?aTxA0?RSgw zcu?&vMRVJb2x#A$<_TSv2+kr;a}d7?&TNizI3|bYoQ6rceGrwUgLiNqxTMS{s%GH2sX{OXi;#aYV_Cp6kOmny9%`g6^@k8ENBE*vjFkjf~Rt&9v?BqSR78yM< zuX6y`%TbmJmDaMV_d)OBqVCP6zHUK}gAvLZamSNc0|JDyK@Yp!apl@rrQo4vStr?( zRXV`YH`AeE2xM=!^%XWS)xsEN#`sO9e zrb`{`S^8#9VpY-xC*D}923&lKE1|3#bUXFxudSN3Z-Y(cUcsy>DY0|T>y1F8j0lnO z%|8kqG6BFsiHc@|MzX-nFnfpVdtvYRTMHFD9KGUuHlsTiNMZ<$_mV>L#~mft8{!C0M4_5S2KpKBY*SSijhyXv&8YNDUuyP^_+R6(q0eE7=p z0Z{w5FP-6llE=-tR&H+UL9TLv_+LV@`}*45I0ItP$`+N)51?@IklL$4@D(UM{@(ID94Bh+j5;m@ScVZ z2jE(ogW{e1V>5KxA!lxG0hI_$OlU^U%Z$P0F!1&PAWQczAnr&tca8iaVS?xj`56W{)YC4HEnxZ4v0e3+dD(D_ zqmgGIyu=Vps6*Ul`Axz2xDpL9y~Mu3r`UoT0aSe{C+Igj!ff?<%!tW6d8V6sv~X@v z@$qIRJjYRR9!U^=1I+aYkhzOLHC4fmYpI5AqO{{(+xJF&BIvodjGmr?DJY2WYfuEc z8s!zkYVUGkIvUd|YErTk1eoZse(S$j^E~%b52Ez-)AS+bNcb_WaWVY{ z)G;t9`x^laxDO)s`M=YXXKv*8FSx$zf@#n%PM8BuaI)fT2P>>~ufMM-06_3ttokpi zH7c>y{Zk^=|6Uw?WEfkhwgj807y1NV+mS`k^NWUDe=QP)dIoOy*z)tiZSHSFQ&GY# zQL$kOhmckCk3MV{BM5U=7y62<8KLNeDo)dxme%uz4-sF8HM54%F|g+miAn7hEbsq~ zE>a`QPFI6l3%rWXhLjE}J{65Qr+VMh&k{L_OP&03GL6596DsmUhO-QlQm32# z7B)-clJ1-+8w8Nyo^kdQ)T;LpV#BpXPTX#^YTg6`V*I;Yjl~NmOMXzdsw?NxiFO9aRvnSV!lN=`l!ueGH zPae$YbtV;0fKax#X6r)%-f+$9FQSg<@X`DuVM9Zg9f!6uw!ydAyarjO&QH-N9df69 z#M!+2c8?xv3jKR%R)Pl7w7PhJM~ z=GsYzCW&*po682aCLAr`HH!*#Ez9mv>s$jd6a0R-{}@T74FPFH>#8ihWMx?yRqT=;lb{s4~D?hV9Z=7aTqdV zjQrXVW-Sy@YzvS~k0YbTK-TH0YPpT$D~frYOdwUZ!LXBL)h@#9a9vZ!Wye)rI}`3U zgY%a*T}f^pyq_(+WX9uPH)AssKBkv=lR-RCOZopvQnJG0qN_CV$KqBo2sNM1{>K#a zhq3lruewL?Ywhsdjq@?jaIMFZ5#;u?18Zo#Y!Z7pBqiVQXZjWP5rE9`161m-d8ndM z3l$#9k=3m`J@gZ>Ng7>USbK7#Pv6CYh*F@;gB_pZn!K!I(y(ZFdbK;^c3B_jpxXgt zooH9@MXBg&Sb6Wf=|7e$`Fh!_<*B|ZXpH~Au@ynCds~sieHZD+TSO5eO+#mfpEP+yMWGf?Al!}>;BB%O-*W#;gJs)!U{ESvlQ*B@;=o<=k^gIPs) z@vG2-MGj85f<4gMRHy52|gr(DCi?DikWhf@xm1v6P#yzm#xN z@L$7KS8RHT9>lj`%?bT-qmz+iy5r5aMB#>OgCh8&q&l83;Vgj&HBgP~qud4Cp_o7= zlwqw!;TYj{p znHk~^oFn|@369Hg(}%q+@lalYMS(2ne@CAOv=rz}Gc{&Upf{c&KD+3wVJxD{ zcbit2TOdH{m0%p8>F;TKW6t6mYj=!Q0Es<(BDq(o1F1-F_L3Yz#owFP42+l7Axuq; zb31+Lq>bZd^&}3(d~32y66Jny*%6=QN62F(L=@e-w%u==FZFi@+jdCXj|EkV6^(65 z0LE^FP1E!e!v)l(b2c&v6+qhhbkBSGDTws4pECfSn8u)GTGD4K0^>J{uIB3KC)IFr zalR>6+z=$h78nQK`?e8I1;$G{aKrbUIg*8tOb_d#s3&WgE&1cz^9j7oJEod?Wq6s^buzV!PMiviCZF+0k#swl=AAxTt+su0#55*L| z@LFl^wO}9=VnMInI?HsZiCisoq~DbAQ4nk5pU1mkL$2U)`ab|&^;D161FmR?G zwb4)s!EUbtD|Dy24~$m6ubSMha4GUm7CA_m(HICKcV$skSo5fB`H*K}U06&g;dx%NXQdDn z=Bm24P2iWV?1WX%Bpv*A*@r$I*hx@qFeV8i30C#((x9CAt;KTFR?#)1L>A$s8z)#_ z;jXcj=_-A%v-4$OXnjlerBt73fbNNzDgig|1lqL;`dx1||u!0SO)Tsxx zGg+>Acm|8d2(FUfHl$(GYT#T9``v{~h$nRD2Gaka3SmQqLGQmg^N84qPp}c+eqn)A zF0}6g;_EoowiOnbJ~fUe(D;_exlsNu&t?~zXAwn}Wf~G@q2ur;*ntr)7h$!(omdyW z0MR29105oRhw4Sm7iNzXV+;<^D4f2P#c*1p=G)?h6USK!J66}64Impc5Ru7#HK|eB zr32119Q8HG{oh4ksLOK0lNk?CapUJso3qZdU&gr<=qZ!th<=q{UoB~8p)^^~(#aQ% zWTRp^Zfh97kgk#Em|~cgK^`&^*(zFw#+jgnABEpY$9W_8?h9=&PSRsOe@@s$_@`!I z287<`uV#P}HCLUXvye<>UJhC}1zu#VgJhwpFi*a;(S<+}w-wsAU%h`X zzADr*IN)6A(ZPMU6XYHc;6(YXWV?HAG>JPi@uL+sB0u9#oADB(3*=_%Cx7!_HiAdNsPz+OQ z^FmqY(J$jnKSsWvCQ#^CFyEFk4Q%*Uxg^sfOAs^cS?bpAPnDqJu_?j$cN^G<^SiS4 zM6;S2VIXma34s8+=(M3)YprJ689oI#FMV&nkEOfJ6@QNpJyO?(sYGoQZY1nGlaXWM zRI~t+_~zy=W6UY8Q~_ZB^$RLbh!!v2#^kt;x#rIflx*!)Q5yk7ctPW~6QX$=~%1 zIN3Nn_j|FoP)(-tZr!s5*uykJHcjL1K*!1x-Y@s1Epgw?F>#5-NQkp*vW06_45U>| zUNX@6;6pRMbpQ8NZ?bAk?>-Oz)0*(d!jqWYWf=-2>*0vs2uFl2v%8>$s~)1THAd6ai%YD?c=dmpP2Sx7fsg?@ zU`aGdDl5ZRr!+KO>!*1a#K-J4TN&j->nCIE~yx}g22ZWWpex?)J*?Yb?@PSNB=QiJV{-gAK;Vit(#$T&YuF5B z)uR34R>{nlaTjq;2@j1|g}P)jy#d3|ZDK`RANcD8Ns3a~h4I_yhT~pnV*Cu*J)-m) zz5o?tm$bRAV=I7fPzSus^slWYr9g{q73xij06jp$zna!-BiJzqi@Po5A2iqMZ2xDS zNKb~=U0YNB*f>bn>SnTlzqCofddo0vh$jc!E?~iBSBLJ)Pm4*}3R9P17w4lcDp?~Q z(vJL}W z*X43*cTVVJTw#d{^;fGLzMk!l8^2jZ@^O%GjF9A#X&a3(Yo@xBkKG!XrI_LBsN(*B zCe!KJLeWu4h89CSro^lao?_YDNcDvD|2;we#^+ATt{VPslyo{|)9|Cl)g`SvJAWi4 zfI|JLu;qbE22L<7y-BB`%>B02dQc6!JF^i*!@DC zpN>R&>gw^%(JO(|gOo+7^hV(PW`ff{McmIl_LU$o$lR91*qLs|+c*bms^sD3ka#6K zu(k`kP#09Vmsfi00ob>ga{0TR|Bw{7vOjDOTFNUe4Ijl{i9H7?pzp`ufnRpFo@6~g zO9W?m+XR0vbnPz&blOf5I-|dSHLh9PUoGc`4)4S&pKNNf^83LcfZIl>p}9GHPZTV0 z*&!VQH;lngIDSZJnWuWnEKu`WlIv01x(@J{Q9C`3kcsN9cyUAr{1s-X9JkFKM2RL+$^WXmI^I#fQgnWDy zGTRMr(&*sI0cVdrCCO9&kWKRSLHLVZhRgCb%GF+9Fh%5t)1(O^i117V?9Pybn`Nn9 z-FJ64lXcbT2$grds0ZkxovV-u4nT+XZpb^O zT-YFnw}Y=6XEW~1@{GeM1+05<;KChfxfuJnRrhTzMTVgmf=<|V93DjSljiIk11+)+ zv^xTs5GC@7XFIftmhVx0MohT+3c3)+rqIb!JyOElBgW1n(Blg5R zqs@S7ft5Y|g-fN9^liOfy821cWFSGuPq@(Q(h%K3H@5T_0&oOUyt^=r8!Xo?KfMND z_UrCQRbSeyuiUr>AqDLPFt=$avw9W$UH|qfH9Mmby!KkO#lK zPN4u3vrs|1i~HLqIf3?i1wbRXZ>0^F_=ljP;5&}{T|-3K9dHrQ9ubzd=5mPir^hB; zxN(R1dyb(LYSL+w*1re5(fo5LFXrBeYjA)w-b`<kaN|z znLD*@BOCEeZ6KJc^=*Lq>GS_d@WId-Xbnn)ZbQ+wrCN2E&n3!mInUJ8?dPkYb&)wY zC_L3&(|krv!j{sh@SJ$AXNoU6#vi0_r(xWO{}tYvyCuM8Qg!kjQ| zm|V(-9Fveu;jh)~mDaJf2)b0u9eGps0JXBNaPDeH-$wJ4iTm$kkZ4 z0-48?T|b)T8Ai#JmJG^zQ^soBgrvD^; z*ED19J$!-~@r&yWl&fKlk*wp*>ro8LVKImp>w{qT^8@K6`;W#OkpadK)I7}roj1pX zyXe^~nNUv;a~RC*`)^Dc(+Hsov6H2@LhD*>YnSAyyFKHa@VMNGjUXaJ?q@Jmce^p< zjMII$1uvj0+fvc6dG(q6cDoyANMrt_!%gUA?6JfB_D%wF zQ|ugN>G#ZYOkAE$hrfuDbB=Wdy$t5I{Hy;#XG)Z!3#|-bNsvNrZiGaK3*XT#!Vt`U z+TsE%bDkbE+xdV{6^YG&htVd|aFQedMlZHymgPXvVzQJ8K_g1%t6{3KW8)Hfu1UIO zEp9-VN3XX0GmA*$bjGV1mFob}NiItmr;oP4h2?%0r&{*!SrHwOG^g-^Wly(G9j(ui z*UUHv@g?#NR)3nS4ud!9DK$=N{7URnM-t**+6E%HEY#A+rDT2Er(mxd>h8$+8>4vWYq!)U?dt&wp$}XSORGAT~F*V=b|7 z^!TAb{cGKnjd3hpP37tiB6S~?Z4y`+`p!NSd|Zc)Ol5tDr62g(c))E?U?3`ZUpNWM zVIJznug~z-hPv7wJ+j>I57}%q-q@UeIPd|U7F2N%Mu;e-BE`u-CFZ(D4f^qm zp2!O&*PE){5h2(Cz$(lGm1K|zS5QqtEPXNmVAN(P?xm!W{i!_fv{YD;Ip#7A8lK3H zDjv_u$LQ-M6KYH)Zx7?9gx&rv?~4qtgB)}>5^U4OX%1W{waW??+{{9W+a02&j;v9P zeNT3?;%nXov-`|Dm&l+Mjq9bnTxGZRFuMbfBK#&5D=j} z^DJPRaVYW(Xakc~sAR1KQA;%>V`KXC3W_%~FIcbAuEJiO!WPEVc>w?ixBN=U17Dr? zV?SA~R{#n};43mRF7;CZ$k|A5iYM5!0xxR}FriIPv{g|~pne%+;Pa~8ivP|LZ*@vA z$`&?x{Q(Z%+ECBqXWKdkvt}#_ z_G9K3czGMYA-MkD6vPT&Ut5O5apEI|pa*v{6+& zC0cbX>qs(N-i$1eRol20V%TbMHV}P!5oqyPey~`n#!l;KLOeI2^W@*lj$6@rV>oKY zoX@VFo)Og=MF^*f??RJu8#KS9>#!{S@JoxJERFDra=S7I518;f5nQ;OpA2@ zpGj`=)G8)Ir1AlJ3831-z%)rn$PO4A^BWe;fIVs$C$;Ag%y2AS&y{6bh-s4wxD`M`0n1QF{hzMq5_RUf2 zkikBdZ>JNY%3?oQjUZW`O>B^B-}{A|P@BP{+a&a#`%DYn%Ro@4OE3M&*&x%ecQU-7 z^_^00H@Lx(6|e<_shH7^uPvJ-^(sK{oQr%0bUuTmCkkylS`c)J>A-1GHN;jZ8|D7% zYAtZ>0-98wrlnbk?6ok6b#@ncq=u*vck<1&+MV3Y-7ud(GK0fht;AJa;NY;m0jTb1 zMsf1+Dys12dpZCYu`}_SOY?#GR&yKVf(CIF(!Cw0)65)P6G@}{AOGeSkE(GL!X--w?z7@%3k~W_ zfmta>{)(B_J`RBZduDvMEQKPlPFkK*j^zK+VX$sn0j@%-i$n^|*TK z+n4mX-Q&q4uSJmcw*Z~WQDBDoIOjR}Luqc|4R2zo-(Z(D>BOjq8z2dnLF#Y#0gwpV zkrZ0WnW42X_#__I=v|aXFs}9=&%L?n*wQ4T821 zUDUI0tw-OkYG=NCim-F)C2j5#{Y0ZZNX%Ll#uvG14S2NpQ9iO#B2kUZAjqG>QC2&^ zX8msYVJD%WD>Q=d9Mg#4QWS0R6Z0L~LB6@}F&yQW`5P9dX@Z%z8o_=N*p8YMf2duR zfpftfazrY%iX6mNPEkv+V|&GUF$IhW7_s$7ELF9o=?N|jnwHR}VI94@P^?`#F9#-q&v7sm>H1d210X=~Wt4~JUQT+t z>Vj*2^FB!++dts!q7y6B8B0vT|3jvCE{GsiSrG52h5G_^IA5LyxMg6aX2%}%slX9@e51PFZ9z3z7jJ38;PWtytJop|E?8%&?Gg_o zEgG}b{eYLY>^K{(Sc5tTLrC>p7 zr@ya#eXfKmMi?cElUiXhFQ!PO_P#H4w!kWdM2Jo=Iba5G(?Ds815@15I6npu zIB4)TfCAY|V_g^zgedB=n3Uq_R)$d^$b31v>*m+ma+$LNr*Hq$i)w}L3<4S=JF%Du zJQToB-GHc<)4@@5Nq0=p)L@Wz9LIe>xFq8eZQhKMYY~_Z5_k6^&$k?kkb__G_iZu7 zP<+xA(}X`{3C74R0O~o@5w$~JO{}fL`z)&&euG_hZl}~_?pdvd(4!r5{NNEJ=^TMh zYScSICiTD%xUjxg`8^j{IFVek)%OBJ7-*=oEa}%IK@|w-u*xD*#2po*6@2EFOo}9gV7adR3Uf;` zFQrEur^8E*ys^#p?mXtSrzKBbQY%Dz_Z*eu(J!AOV#MIsKL3d)H41@z8_rUYwiO)0 zV(z0Dl|O_@Zgda4#BLt?-`s8yVcBrC1nCat$iQz{2zaBjyswdbsK(&sE zNvg`IoIRHINsz@#Vmcv2rE8Q+-OECYeQRj$0%w1`V)-0!gLwy>10%b$B9D=HQxK)! z9LFk51Gk_SKpQEm`I|qZ=B6;$GTfdidYQZD`oeziRJE%^dSG*;DksIVO@A0LeM z=35hurgl>wZr@VuY+U_yH5VHzV*-3b8lCzE8%Z#w==6e4uHnsWv1H&O68~kLxp<%7+eAgX7<4R(w3L7 zk|`?rY^7jxRNp9(^>2lf$5BDJK&zIxRX2JC=l%JM%_(9LuN~hUVo)pZ)+W8@O-jq6 zEbkawS%D82Jxe3vvsI{@xm*v0F;G;S;W2htYEe=hPBx|wcve8QiKUV4=h(ognc6tp z{+G(iiBdK#i=Xg<;zdSY+t50FM^cXRbTOsbtTkTLt^fO@%Nt ze`E)fmtx$ODe<<%pQ_X;x(c7QK{Zm(JdY-si)>ID!SB%gr6%T9a1Jn zvEpx_NI-Pf0rVqaBbRRsTnvPA5zCC{5ivQO7gayV4i-8(&Df3qJi( zis%XdR_NW&t$ZCFq8H`9v;&m~3eFt)(M(zKVh|6I$BJd?(^o?5$w;M{#7i=H-aB*(pT1)T-+0u8C~(wudprDc z!0H#kL!Gqu)8|D75P|N~5)w<=Vrr!xb)pkK8tnYSxG zdxUF&kC9=17dNtbd}vUfVW4EdKB0$HBBmj+JVwHqQrlSry)^4}PSFU&EX^i_!p4+4y_r}W&f*{q! zp;V<)SDcglR`u)zt-_Wufi4n+RUb$(ZlaYQQ-+=cB5;k>pjS|wc*Q9qPVMYoIfeib zB+mCOO`9X7o65hi2KcHabtXLbVqK|gRIJK*BM?%9J=aGchNyUmBKU);BRb5W)=$|L zn6n0GF!UQz_!Q1vx1^{=m1|iQm&88f26|VE_3d55>_!*-;b{??FvZxpaxwdTf9uH> zP50&hrKJjhQ|#(Ot#~6Gr<&)2S!2)pyAP%IKj;rUe8b^7vIC=qYXsInM+M5PyQ2#z zIPaOGFWOV|xf*{|&5*F6Xzl+XL9~8s9;AK>xshnX{KdncqKRHSYJHoc=AT_eio$)RP$4hwd$DAfpYaY}V(CCS&G{1hfLdj4p`4G6XrNaroz)m)GH<1XEz<@8foxyx zIYe!X<9uZ_;H66+jTy|^RIWmX=*2=!&ESuZ<4_`#FevU>r^ElEDKg;VOf$B*xw*#o zpDQyrBj?#nA}Q6Qr!_(Fw(Pi1S2KeV9!ls9NqVB7CKN8;!0)`}R~bnxB|0=k!DAHa zQ}<#%!Hb;bQy%BO>YWCZ13$uenEhMp8&|qF=}Sqr&*G&E+XBn!;;3J2<~y*CQ5$Bb zW5G!)r6xVRBC)^^Hz<}!*ZyxKcX3La0)C+=#T#%bVyC8{6f6qpY)BBe!%39d*$waL zss&|^9N7kvU$Na*90RBEvH-uTJyDRhe!FOO?q4~NOC}EyjrsgJxetn%z~!9hq4$f; z<@F%RXNNr1Z-w_s4Ys);<;zY`H?3Oe$Z)Cl$<7pLOq#nTcF5rLHIN#18hmeBi(~IY zD2M(S&)VHqtn5OmZ_!d|(SM9)bfQg>h7J4y#WUSxQvO2yh*cD_H`Vo!7;l5cy7_~( z1u9kDTm(Jw<)K=i;C-+A5N&wm0=ygbep&%n0VCW?gt%+{V27s5dDvQ*9kR3{6?@Lg z6Ac`9PUtAiqQ^R2eUHk6>;C;d?V1St$8*t~A$=(`N`J!=(rmLLJ!Lg48RqZM+!`h=pv^>Nj$yS`flJ zyKh9@*{=XKEdE}8*2j4SGaRY$}~E;j&E)8L3A5H=v^5=l6ds@6yGY)cAvx889$13=AM5`?QYpFLAmyzWGPq zs24|yZnSAq>PZLv9QHe!ougAwAyfzX z9EbDOL0y4XLdPHNJy7ki6r;F6DRHjW;WBexssIE)BMiNRtD%>xPz4oNiRh&e)C-H6 z{(8~@7eRt_gUGGy%Bs;+yk@qWvL}V zgZr;IIJS|!Sz4`127$_pnNGr6?lVR1qdB{5}QsW3QC5T+uj?7y;|Ev}55ZxqstXCQGJ8U%Wj z3Css?UQDTHrqM=02wGd`yznKN8{~6hwwh^CUV2RCUG#{xQDbK!4Gg0UNs0^jmcg{A!+{@CTFW1<-W(0zSTEf#(2`?4=U zvt=&6Vo((vzjS~Ip9)p$HOLhxKY4c@cgpdbqXI*!cZr<;y5NeJW3lk#2f`fWr&F|4cFCUHYA@Y>5*M)tK>_aS@SY{+XzYRCp>Z2CEJ z8qppR`~^fJAqA#{^Zzd%FQ@4psZ67h=WkvA_qYaQmW>qi*agw3UES=z;T z6@TB={4(PWnF;-4O{KZuB&BcfVpYwDmQDFwH>BTfoFk)k20m^)WXulFcmVM6(+ZgzI!w?h@X|*AlkAnY}f)T>Xd;HOk(y zF2X{Y!e7haH)te{SK?`W#!w&`p%q46bA3Q+;PE>5vL*cj19;P#DNhdawoTo`miy~? z)J6+luV07<42NZz`sGI`9tNwYSFDpiix`uw9CRBT59%r@0HG=8?OpTreE*M>XP4Kng?F#i8DWqZK zP7<2mKJr_*0mO{F_j zO6%oaXSQKyX-vemqGmv#;~AwD9G2?&C%=IC(V24b>s%a5D)yNf#R7?2OdJ0ZEJ5g!fMA@x; z`#}l&H5bvxF%~gPykq?NCLGI@X*pm;L#Mia4Zx7t`3OpOQ!jo`>>@={v*Jzuy}iG+ z01udNGpxAjN+w8X7Z&KUy{9|K9 zKKSuCcxhl?J)L6fMfjFtcaHVe&WEk;d-OCe@y88?H%`)0roskAX1M4u#H}&?*w)@GRxaMwn}p$`@}BV zkG2*`9U~#C@z(fJ6@!@_Sxm?J=q*j81xzv~OG{Smk4;n!PUkHitJ5yAVyd6K^D0dr zsiPC8oV$_e65Y5+mbG4Cw{Yu^7rzwcXb*PYAg>&PYxB8cAa4lRCf6xKE%Ll4);clt z9G7pf(!o@wj+ZuKVsEPsM`W4oz?I*}_KgcG%1MiucLnkp6SsL9I0}k0u;$`|1uQoR z0$hRKg_Yu{YFE?;Q6!Xn!Ere=6Y@c`QH1e|3^d3U`1-^U(CTk3N(q=$(z0t0ZttUq zt9>Hi@Gor`KcirnizV{h6v*4QCDYfh*Tfwx>Ze{V3BwW+Y5D)3siQG1Q*w{Irk;kl zFh<5HSg#fQ&jOv51H+i2s|OOQ`Y3d`YmwHw9KjsX%vPXLK=O&*R@}=Im^Hl$oNZJ9 zl8!^9vloFSB=aL$#NN@pHe3c#HjwG4j#`5A?SV~mWRaAZ_}!+TIrfYsNe{P$PGUVf zSaj}H3<8CXHf{QeYvL(QHpSS?#Lc3@<_SY^0pZMjPO-D{>jUSX?*>!9-cq+Z#ke(Zv`xCz|;1R==m$Sa(RHtvrfkFu!oyBp&Tynw`rj4cME$dti@knT@2x@pB#3;E&Jq zdrFJAW(lP*QXG>qeNVg#)*ugIn_Xo{Ob?-n5agIu&b}(#$-?iuTGII44oSHjJwy$= zOa*VGiq$`l%#nVU~PdJWg2V+WSj~>xhiPNBWa3 zF=9?L8QO*YGj;3lCHt!6e+GXASsO=K!<^dt%CXx}Wa7BL3gqfUJ>O($bl-(^aZ+BA z_Mdt=2!)MUzS`nydx(1y-C?-sW1;d_FKcp>&a>K(P`*@}9~`W!q&SHWj+$*GZXx^N zU7gJe-FdCp4{t*)+A>XNAAW%S*U2~oyWV~T$&M5;2_=-^hsDBq09XAF5|-Sp7qOM$RIh7nZ&dgDd~f7$}TWVq0xhx|It_wz_F^Tg#o*Hzb}} zSMT6|(G?Mgi>K(uC+Hcp3~gSv!UmcqoQL+DF;wJ$1Ap@y7zRHtA22aM%ra5Hh?wC!mcqpL z92BQXM#>yrTi+ZwRNRp)9rM`)unGox$-B0#uy*#16n+(ekuner@X^evnbvu11hfDf zlPD4#eP$UDlp!2X7Wxw#BrWGY4+=buVTnJn&Xx=oQ#wY{Kt)biS}~m`N4qyh2p1Ja z9R3W&$d-)zUs2Toa3%{0=N+f*dXvbg)khSgps;rTih3t`Q#|W)SDhn4&>XS&cjvqX90f(kK?WtN`AH0;12%m_W?UmjZybiKN2amtkv;^(W@c$vKce-JG22=o^Z@Fz zmY0#`QwjymAy)R~tAo)?1`^iIlA#?PnX1`|J(V3aPgn@B-aV;bTUDK%Q|W@*%y)V; zup0!ChF{dXncw^GJn*tbBT=NTmCD{Bzgsz%JQIYiihzF|!*r;RSDv?Vi^)Bi`1;eW zEj|NBiggLnEiJbP>?%y`ZRU>`(_;=r0_Ail=iV|GWc6`)1X}CQ5tcYL+pzjAUetQvdpz zXmm_;s^lLbg9l;uZ2|jF3*B{My=T%-L^lFc(bc1-YYl~MV^4iG;MQT6G0RP+2`Tx2 zs$nj0RKk#PXVOs0&=u#vt)#X*`oC*w?kP~Iwl*O|wztGAKh^S2$_ zfDn@o@ct$XwmG_*Kru}Dr^ zd?93fq2V3VDUXfsf4u%mC@K^$H?^-furI1^1I0z}i$k_ka#NCif&h@6Z*#15f)mOK za?@>EW(RlU8)SSm`&D6JYzcnNN=tKbgEmcI&pJ(%a$>0MnD+7~c%ZLsqlOM3MrqF) ztYqM#uWuU>c`CZ36A+W{#1{iw@}jwD;9>t!2yPDitgM1BN7>g-f;+*G514h>o|d3F zPLW?|jIjz5v6^H8s}ns}reB@gn^kEL`@Ow*xZ<_yicPTB^Tsv*X?Z39)1k(#baEbt ze`K=w39M1IHU~9XVbe$yNeDT)Bb)sRCr$2m>g|M z;o_o{2I2-f$~hn-(+6~k>v+#gkL?GLR1}Q;sBYnHh;P~S5C9yf-xYp{pIp^5#46@4 zHGhQq|E;jV$FPo$an#mZB#uR>m}#B?P~7qPD~2P@;mqSnOi74ceXoJzLx?L8ZPD@- zLnqoe+MtMb*C0tDj(Nm}`VeJPY@ZGsm0ieGzNGwA5p4@pd38Tl@%Cr?s zKAZYyDUHg0_qpxkC5ppvn#g?74?XtD1vyR*Vfg8Sr_0M6foL#{L3XaDcCM;r*(mT%!^+LEO+{-~O_juo0*YlH3i!QdXmy4WaQQbI5sME+ zDl5Yc--g3YMc1@XEF`^C7O{AF?!p;@;lA^dWF5mb3mR-_l)@bpq>W;C{jkKcI@Y|m z1>+JL{I8H8SBfU4*1D^O;i1C2ocHf$k#Q!)P!dWO#SS~eVvgYu#JjjM8Ns~qwP)b= zSY`#*N}0ezg|oB<<1axNAW^7&zAHu+gDj2tzZJ5tH$R33MisAks@2K@TSWzqM#A06 z@>U910Sl&cGbFS@{^TPo(sPb5(I`ys0ih`32NZ_p^&|*keuPmXTOelcW~?AQyj8^b z$04;-i>W=-Ok(1SS`42nM(hqWRl5o7*F*97>-rQq9p(^dGyloyvVe~?h+#a(4>JO; zd|B$yVBYUe1(dtRb~5djb`*@eVF(=1M+Vr67X*|Qt&7Je7<_5cCn+w4?FK_zAm#F^ zIFcC>85^>v=@e|2pMSmspzWKy#o~$2wA(L`Kh_aT5UVjl`ER|6n36f#<{_?Hw6gQ# zKO_g&UNE<#V6GVv-zeX`@|iX-vd{qnfqfEF;BIchKa$2^qWGx!xy&HhDn>3tax;bz zZ07lOR?u@0#3v5D!ERZyOxH@LZNjQG0V=F&669#+s9_-rI!85{*Jo=ILnb#Ah-`TR zG?2YsiQMwVnAVg;6?#WS+lndxNVd#S^Q$QA^p2>2^(!Isa37C5R8s&TBA&&Mea)iKlqm#Ly&+Hg!|R_K?!KBP3QxBdQ<&VpW6x#ZWP^tj`8w_tH!BZR;(N->_h zi!ZS6nr+3&6OhP@r^`@y5hyEmfWqR&ZGmO{TJ!|%1DaV7d*{iHh%I@G-?{t7mk24A zYZp^EOoHB}j%Y*fR z^+kPWwIglXkjb;X%_${r;5cfz&u$1ol6O7|hwH=8L7&0Du%(Tu8@kK5rZ_&D0$qz` z&zmLHw{`|tC$9G@`$E9j19+nM$d*R0-5-$17Ru+kR)QH|!YQ>*H*dPSa0h5%Mkg}4 z=hH7zaj6{Be#r;_Zo|uq(_{@*|08VX!>VePct+y&i9%O*rmWmxu z`FtGd$;1?Bi4F3R%0d6#V&WF`clb8^yH?HgA}r1*vQOhgq20&?L%vl;0aabvOIeV< zN7P)E*m&%PK9atJ3Nn}@1CLJk=B*v>_efn;$C(DN@&9+5fzgXCBXiA69Vnnf`$I+s zit@0x@j)5Y#045^kJQ+FHYD)hc;7W(5rsU5v{9cUvoL4ufRlV zPW(fS(>DJ%;{bZlna*DU#7_`31J_u7pNur&aMx*A0jB5Ci#HNl%*Tm%V%IuevKLz$ zcQ_abgdZ*QQx!>C*D>c(LU=6`e&S_Lfxj^l{H;7QS=vDCL1i`~0NxeUQ~M?sf|mqo z;^-mO&sbM!dusZrl2se~6mu#;W2Mz`|AvfE^G8Pc9F^r82j7y3qe1$z5=svtwX?GRiUd{0UBki>wb2P%D%4P#ib9N z^Hc7q+`eH2k`1&h{R%8bzo9%pGt5DP+EHHz)FELn&Z-WOKK4oh>c=rC{I9L@RvG zs*c?(;iOGS5w1F-KskpfpF*?EixQA!M8sUGGSRidiYqknd0<&k8SW!>^;zAzRBnB= zYG7y#Sl>HRtUqkX+mTh{V}{3*ue!M?S_%3}PtSZoOg={rwB-kO@rY?A&JI z$ubfSj<8KeFKXmiRRwUu921Nyn(jt*JPPFIR$z=FVYKUt3CHx1U&K)Yp5O3gWW#S9 z3ysjhOeblGY81a@SdAXw&Nk|4p>Z5rL6hxrsQX>Ghn(TGalh7mug0H0fA7(gvRbOO z5l?GCsg-z?2%pZIjQkOVL`7d$%2e1MF%`oc%lx}E)flH40i=JHY+RdR^Uh+Zx>ufc z1R^>$`ARy_<7Li5+OcZwCvRJAv#qma6L=(+EK1%8<>q$EtjZGK=Cl&e)uyug(leRA zX!S?doxYOD)l6DaDe!QsshD`5 zi2SjGR-j>y&!{O8Q(GX`=k z5i#kfio*EMRDJr)NL5PJ8y`=`Xt>&+17+BBv1Ularb^97aYNqyKp!H$I=9jbiwUDS z+FbN%bCt+yNUgL-5h4cBLQ2^W)R=rACQ?Q4jY;$VBY}KPE z^(p%+?Y2MZF9U=F#Shtwulums9(#5Yqft&(KQ8?D&dA8}FpWqq3`*s+Q2Hn^mIJO- zionw7zVTcia&$lGvj;{IBH>#CK2TNCy|Lu=Kp6R!w$9`Ck*oU7DEkHxcgBoiAIH*J ztSsmsXk5ou@a6NN|5mr+7gd7}%L@Hg08WZkQs2|Vd1Zr?-GD8Q{wblpIG?k4${Ke{ z+s@5{21%}d;{Ar0k*hQ+u>VKxAe-5%#Ry`Nm|zv#Wge!Z_U*9tMS7@vePh90mE{cP zHHvrvYM*}TA0(1yUg%Z^aSbbJr9&rm44wL?_fTJlRyGKqM_D9pxgZ+dov;SxtQiv? zsdQ6LGcDG;N~yj07Kro5<3``yMrZD&?Yz6MbRE+JOpJ43bMA1EHR;l3pcm3H&)i`U zu05erE@c}kt-xgWHypyhv;-S?4^AW}@bb0jv5`Cc#i@lSZTs_9esjPIwF@BfB*AA5 zM1RpVXxJ1&4Xf{aU0l|NX%3=c@8n4=_pHSZFFQ}ypItBdIihey{g7;a6GI*(p}Zq1 zYmKLQ&?M_;`~gs#>{OD4+2m-WFcWEZutVg>gN=!ldtK-Q^}c8fB*MqLhJ=k~S8B;f zK6-&Z^liye%tYu>NBR1Dm5>}VV0)mGlAkO|E^$ZDnO^B%q8Y>QZ(*IDQtZPY1rITdljH~`6~i*L>PDj-h`e2TmS}A|5YbUl7Zpn5jMT$MpJwcu zqVD(~%iLjIv$NhZ7{$OTw5hVm?cHcjr$jR=3q;mlWg`E{c>>u##Ktb6Q86R<40oGH z1*3AtB@GVe_!7oHp`IBS1Jmd;Yt$oOQj)FL+B3IRmIl?WB%B*pu4%;Q2InD|Op$y9 z)yJI7{VC0il}r(`e<@Q&A~xydTv>y;oM9dgj5zf!50lyaTjqkNQuNf{^yrD4A-X8- z?kt#Pd%`pnq85!SFbB4^unz+f`};rtZx=Exj7tHwMHIwT|FSiVO3A~t(5c&~P3`Ey zX#3LAb0{z}k9)-@JsIT?tS9w0B#dKkNNaNS&you_HI7E$d49#b$H9WcrG%XfaTO)p zf*SP0wE9BUq35fz%+jFN@|oYbwGN2^7cd^s8^zauck48Oy#s5{q1!Ks<=>*}u~--> z(7+N&a6(h75bPRt(|rZv?F0Ka(m8UobN~*80xAKKn}d4qbSaZNAbGedord`dgPeue z7`Qb+4q;Zf(&#EIz9of3%kdgG$hzp5oYI~?B~kV68E8QH){@+dmYYDRCTgZ#RD8tV z3xA7sHi2S<+?{gJa0))kD$f!}=I))WZmT=U}aNf#bncR|W*Qn62@6q$j z4R}q@%iw z*rV>!eoE|3L;k*R!1>2Md_dKFlGL~kI_X2mGfeV?46w+quRM!f2|Z@0NQRUnXJd<1 zdd{U~DS@(^+OSy1@SaQZI6EG{5Og{S=oigSfT_^PWZfz23Rs@S^18m%#^# z<^S%d+s#!-;O=hb7tOcJUtT!YdD}27<*?JIEfALZ5gr6kTc5UV;ra z{CrDk`LfI2k`sfvGs51SEgenauBN)c!6#!x+8uVJTIt{#k~_t`c!pRsVj z^E0!O5ci`SMz87&ddps>1UH%M0D+SEK#L&{G6< zDF_T@1^KR##iQB+=lE=Wq&g&gVs{zf5Z6+Of=bxrCUBugE;sjbVwTalP+j)yxIjah@D2@NnD*suX-$Q$R zxp(~&^hcV*1pM=? z`qb%20n>VE0Y zXKC6=5jiD_6r)r3xEKQtw*48Rb4Iv&U^@;V7x7D_@bs7x%z~{2ReCZCVwRX-NA9_9 z^OjtB)&tcO1_3H^^Q|~^)L{V;GN;J(F^z$)T*31O)eea3!)aOU%vMaphBK-8C-+ZZ z+q1wd3O;4$^y&ag+#r-1p@nnl?BNvYHg~)2ZS(tPIV%}_Nk<8I7YmwPuq;ob&@z+p zyI+B{Eu@A#D-yDwvgjNyDJ&*R3WqO&b>n|kU&ikDB9FKLmh>EIWdRmJqRrF_+t_#< z0w0=^i3Ocxu7$|i!n01{V$L@TpFP$0u4J1lu8L?(GH0wBm-lp94MFn>MI>wrxdT6& zUi}&RlP|ZH=s;aN)E?=*kak?KfYhaOTr6pvsUzJac4l$AXs{h=AL~m0GTOHxAxBb$ zIo0g3%&l*=C+o-AeySV`HoAjdcUlRL`kW+k)u1&6A}!5}kQl|}t1*pOO0LN`HoDFp zwWBQvRxkVfJW3T|`ZgNGZPsZ|J`k=! z(rXY0oKrFOg}1BS7O9X3RRoT={nDe<-VwJ4?EXL*opULW*b22{kh4)3z`lau6v1q1 zsZMqH*ee}oSs^R=u;*{%jd8@{v2*knp4)5RJ{~FQhlhN2uEFt=8qlBsUFmQyOh$k* zk_an7;2tOsw~2-SH=HRG;4|I$?|MC6GfBI;hvP4i zW2;YDU|n@pNwjTmIp^hHL_&VDa$uL>U7d`-H*NgDTP0XN;;dQq}>V*HvOKxw$)Ic5ZZq7 zYh~P&ZB*EkepcuZYOaE{5wPxsoLssZiGI-agUk%e)svIQ1jd4yzAC@WPzY-=81ZQu zKK@^%yXcdKnp-IGa~1o8i1&9OvAX-fM}}Ti1y9mMLDADEDSi<`S1^if1CB}82Yv%J zASySg0wwnJtb`8!6Tjy-ka~#SWmkyueH%-C>_r`#m1BfGB-+BAk-y5Ed9)LH7&lv4i}kRQl&0;m{qMhbhQa16m`)^4ta^uf6J z5|$%pw9=KzNY9OFq|4h&v@ULuY|*(3>m3T;oO!e>o%X%^^034H5I}~Nq;q;3ueT%8 zpakS$fj+HkV)=B4K0tAMPDaJus@7^L;HEfN2d}<3R}|g~yksK1#-thlYZghzps{UX zEyxuDlE#ev#|~A&E;QPo2ofZ^6{BVPd?l6hMj?U?8c)p@H;LwNu!8^la0pq&6bOIQ zsQ+TNO@JI8jM@e~vKmSa5we?Nibm46!!*%GfIZj3E6gerXt0^9{`Lt@BM&}cSu!JH zl?f)W5zR!V9tLP8`c$yn*IE(4kwW>u`zQVx&7n+0=H@_W4a{Ihw&7~qC_&TKGtLv= zc0Yi*vPWhFQn)%p(+vv42WW)ZbBucY?n{+L#ulxFh!}ukulp9R=tmp9#i+GVNp5<^hljpWuA zMD{UJbZ$_mf05si{~?*IqV9f+ziDe4ukeR)G>bAnb|#fKHmnunLg0y%)jNe%I=3*# zG9Z*tHJ{&FAGKzFd}_4K2w_olf$jd%v`zMt(dcP6jE_|%6lYpV(wY`<@L-W3Dqgt7 zVLp)`kA%m$&aLvyps@qohQB+7Op@ZbHz$A|OVg5z)+``!Am!eKlB1O@P@jM?iD$q- zx9}tXZfQS4J-W$?lBvqMjrp6V@p_+HfUaAVSd0xbtU9k60Nz2^DtPkJKqzZAxCzY0^F#0B@~^WNC{^*#5&8l|N@Q6Y*Sg1@vb z4;v{!w+IyT^u~hG5?kB6ohFk86+rDtP?;5-=yoFKVq5#=WN5%6w8^y+A}F`k6@-&= zQst_llJscY5RPvXDMZZ(&xB)2Srx>yYOg$2?z-9P(3l-%UcLiDm;P0@7T-zkv@z9G5n`}v*wB1l=a|)BB8dFuIQUP`ll<<-koE+;Uhz&42F$j5x1|K9 zVtw=~2~803CHO?-qE^+txRP!EbgmbSsj38LusQ?rBBmWJ?_h=lBXj?(P(ejk8u$G~ z6^UxV?~F5G9A;`ujNj}q$(Cd-ugwppCaBPPqGJbl=DSiNo|GblNRn#}u*-7>i~+Pw zIrm@^iYlf5Y+Nb-SYkcLu~`q?hWZ%;;L_@Y_29Z_2dJ^F3?|~{iA*r6>KK;z8^BMZ z63>Y=^fkx(0x=yQ;Iu-@RH$S1n7{Mdb;y=Ga`W!okv9|Y$NuW*ps;A7V;-qJC++}KZA*(N78_S0Eg;h!Z_8^&spa1zO1@%i3+N>?*gudI1EokJtJK$9#omz4|ZsXrl)Ik$|M>HE}~xBDjo@gE9aS*U|Z(=FQYNg*+2%NN1$(U!>m~gZ~qf3B02l1kN08D+0!< zr7&JdH0n-`pX~)r)`;z)Crmt{)@zG$$O0+aPPb9*70MxFui6EJWMT7>?RQ+9jx)Cq zatHx4%J=O{D~!jEWGge^4!l4$plIfTx~7Jqfz5cEE$YCxtg#yWN|I=F^AROVani}N zT@rUobEN`5>}wLhgL}`m$-(KVu!3TmvXo|c=gw2TSK(N^;O{ggQO6dK108oNjZgwW zaOrJot$WhGAX^KKfU=DTYJfF|Xe?)xG&6_V*#-n&7KtBOii*6{Svf6($It7lQI@BU zs^>7tz|b|`aem9UrQc{`Ek}#92f$+cA7DXRH$F6(lj^6i~% z?y0U?iD}eA_EbPZHYp@TQ1Tkc(=JQlxszwdbNvvn@0X6T4@d2wFKKqN+cm_9c`Uw# z$r5&$6X zBi3|3_$tQrtHe~G4Vt;rbj2gV^+-MI$8LBvH+#`^o2sxkbCiZ$s+$7xlIxR!T)Umq z`j0a%i_oa)B0LiYuxM!>l2p3ELMMEZ5fnt87^N<1NRdb;z8&eHru88!&wM<1AX(FY z_`Hlx``?c;r$G~yY>_PfiGB^-b^QjWClAw+Sf?P~N?z*M?-E;2mvM3{O_Tw5uz2-I z#majDN+qt=N(|eyMB9+B$GKtT8m1cl9-A1?mD*P#te|hWxf~~9bm>>SP-KDpS9Z;b#A zGUQ*r266d1Imtz;m@?m+f~qnQ%EfVu%Is>=!x^@JKN8m!fBU`Amm*#?6+n~I7Z-Wu z4AAo5y6(3YSaEzkY7t_mMg&4XHX*OV3g$#Q^EM6S!qorf&$2>92qPd5N?l5^gHp3m zRp^*RW`LAe+RA4}BwComqv41pQCd%A1gFUoWtGbop6=lK=dhmq(NTo|A*4JRyl?10^(w&ffU(e2ZnYO}rN76*8Jr*+?_ntIe@RPJmc4^ z8#m=TF(&^+bI2)%&lRg<4??o1GsWHnG6aJAN9)pj@yqRzTb?7@7+3@z5^V=8agRU2 z#ASX8jfl6CzREa-C4u9w4yzN?2jQK=q{le5AQE8 z2&DokwsrYU^fIwM;CBrJ<$y>g_yT@fkX5hzsyCEp+Q<0W9^L?C?D{Kt;jT)YW6|2j zsC%!9VQHbZ4Bc|}C2S1EO?C~Qe-beOAp^0{Ro}a()k^pJ&t;4+4GTBEs6y@vp3J#X z53mHKG1V%?zLtVB$k?babM)fBd}3QtWn{m99BX*!m-&^ru2f) z3?QRaCXpZam7v|elJT99>IIzqYk2X~YWoefW~gsS1qqD42ipL zScUS*NonXPO2~BD9BTW=e8uV~OH}*?z)K11`9>k2o_VT-SqRKI^bb`yM~;x&TNxZZ ztE{JHz!sZl?ho7M^exVhMr2{Nc`>kPBKi*UQ6R&~iuCzYG$O<%A^&O2L>2p!LiZcN zG&pe~Fa8rBGCFO$-*jpdv?YAi@)NG!fa%qOP2y?p&?NDS)2YGQc4 z0;ZQRp5o77Tm6(R?p+9Kt&fII!Z1AZ3X}3^BMuqho!=0)1^)Uyg^+)8*phUJ*ivPq z2Dr64WzY&hR_g9+V1n26?)gxe&Bghw)6jD`fyE&1=Yk~5lvFfELwBDW28N7cc}j^b z1=?4d>eeq$^{@)>wA&}Xs$$HxI`>4b|0&%Hxp~>W!EMgq%8iCSUHf>nhqA%a z$Cjf0AV6rI!8mRKiS4`tcZpZV^wqkOrXSL2y+9|*T z#F@3se$#!re|@Xmwf9h4=EoOH&v^!jtlOY>=9n+y-nl^F9HwK82={DjoS7Djk>Sen z^;o^HvWm!BjX|aQDZ-C8sZ*A~J!!%hxM7dK8<9DJv-;}E6>qO52|Bv#DcY-;Wte+s zWvl|0no3&_BBlI~VlZ>&Gp0H9yg(VpHxpS8{j1s)a6rA0#nG^V>1C$r!L@ra?6 zGP+Zn2L$4?1TT8Mr_0t~^JFVFL86-#N4Gc)V`msEsJ6p}MgHadLZDS@irIco?QvEp zP;>w%>}3P}0`pGqIz+JaW!5Z{hR2<}Y@uuPLaS3Pu=Ao(STuo9hZVKz{Rvx*ME zreh0}?JO$t| z+*|Q%Z!lO}Vd`M$0z#?(&g%;h5y;80>^^Rtuigc(bS_!U`?+i;y-p!^)=N-u3U;ok zB=JD!fUB(qH+AyyK7j=1$8SCndUx(DpO&-w0X7Ymr0k6w zLdw7QH74JyZXtU%lklF#c-i+yXoxW#1g-=`p6zXL!!RPgyyM;>#0|Hmv>qw+F|+%q zmiR)ZBBM~0f31;z=QtSJKf~C$g}0|!Hpzy^THe=nCKFX2>mjgbyXEAJaveG8 z=UtjJjSg@l1%$4W=W!$vMS~o>`HLjeP;ccz|6qE|NQPEQW1(uR=GFL@ueQ2uGBzdK z#w+<@uV)XS}E^AId2bk=cde0q^W7FMG6=s8J?`pl3A*bU*>&5Oy1f_n~^Ak|V z+S$4_F(+6*1G|KkN4>(9Ixl1HbtfK6hJ8SNu9qd|-9)%LrOrrlM8Ev=ouc3%_`78n zN70}Oq-Qlo>2@lMV&3-RJFd#Uy2$yU` z;V0_#99YRqW( zYV`@GK7kdy3M&VX{EDaPysUfi^sXt=DE0V}oMfbYrh%{Ac?0Kf+}_DcQ6uc@_KW|46AqYNf9q%X)5G-{PKar7?&`7V_>+5k0HN@oS($TUh5OL1$Y3kNFo&e z*pE&G3noeI7qGSAS$xSB7`Lz)#Dl>eM)0u25nFMRcek?rv)f6e`lRt21l~cD^ zp}c@6Lv>C35Y>!TVdrPD}MPNeTK?g#@p*3HMtYnHMPFe>#3 zw#T8D%Q0O!o8Nbq-#`Ih{x{by8l;X!87IP~da4ME8sf6>QNESE=Q_(}1?4z4`j;c$RRq{?Q2OPCgb zF}kEWwq4mLi`ur>sGly;^atRzW*Kuznd(Zm#;|jgXm-0EP9xX`5#(*>!nob!vj%}2QX5cfz^VN*f>A1 z$x|fcERZix0Q)b5qAw&2ZQs&$<>4>$3-23sOh`3cWz~W?I@}`CZkVlay#QGMG~EV_ zktJ82rkMw6?dmT|J;o)0RsuONT&ZnYs(-jouh-p1HKS#$qq>3f9! zT-R>O+3JkIVlCyv-hD$4#c4mJe5?7FbABNcb@8Y+>DrV?vhDUOVoigQH1Y5L3M$H| zyM3vYRJFA#^x2bbzm;~VfQwJyKR6oWidPRgTFNjP|IE~Bw{nRdoPeApKFaJCrLL$a zm5aqc+nWhM!p$3>3z@n@w_yi!f6`mw!(e!Q%88G!GV>+i8@c{_F&UQmEGrO*)jJL96U>p% zO4H|Fzo8ecq)Cotbeb10QVg~D14}Pk#ktwbq;vDG>Ig}mJvaS=M~Z$IY>6v0ng41X zUgVTLim3Kl%&F;VKp)%NEw_`Q?C&p!pcbnDRCG@QkE=7^9`I=&a5V^Es@AN}Sk@Z+bYS!+ogCm35qfZ!myiZ&rtepJ7QVl=EI-=& zI!7C@13I;f@hQzW`>1(BaBXK-M$3TccRx9Z!4VxprlvY5=!5L)_P|kKv}%U}%gb2} zJyLhWO+&q`EVa$~Utps599i%Z1j*}vqo1>!OJ-xlwL&lCvDAGmdPbsSoZzuqXV zVugB~(YuW|?3bZVTiwsWU^R!23}I+Y$Wt&za}1S6+Fu+%Dov}+W+IBeE zM=(Yuj80zLj_Ly8*BJ}Oj4Oc_FIHySUBF3@clSsahU{?`>g#%Evowx13{yBCII5=N zT$)V1jzYSMcm$BTuSFVK~4dcbYV%MJpl9#qzMNTy9l(t z3@uQK00SU%RV?ctTmH+>e>l7yHbC7|Kv$@Gfy%?{1970dUFrA9g=#_!d`?MOLO?z> z$&{|01}LF*YqXY1T5tf^FZ*lDY@EUex`5 zJ#XbotA2CXgRVD*-CR1gGc#5cb8p(fh8Qbr?rOy-H+%1;TDx;qbi#FR7LkE@#QrC% zMSe1GbaG}_nonxM{At;LBBmzHMC&++iz+DmSI*vVER{W*b*tiW?l11~vwqKx_uM*L zMvHJvLnHqQ;6xYiiV|EEuE%2ZDvH5bzuUm};KGml-^)bQSlrt5fHtXTMG3%rs7z7` z5cI$lA%N0?g$kNn*&|gsi%Cu;3KC6uGmOcCjjkO3{Qo)O6h`{ZPV<18K@7YZDI|1W z`;R_iAz4hn$Q2gP?*})E^w~kTE~U0PW9}S2-@m#^phD5-Lbq*>?}@D5DsuFw*7zR} z)nj6rm85PPEc`kei{-!w{I*d#22TG8OA3va4kMTk zM$c#V6IizIr7jF2l7I(JA<-Zl;x1h4r-lYik+0?)QGNaz-%yO{jhyLnzkD#bme8}$ zHeI(jzX6ih1dQtiW?KLxEvqc(u4zxhxYC!>o^GzRo<`qH?d0c|qZtnswg2|;k-9>v`cCKN)oHH6Rw z`Fh(w(ziuc#d!D8G({R6G^dnR4NL)W!ke}C7Sf1chI{hL2KC}ZweZW-!moaiWZ+jp z5{cR>X#R)b_SpkTJsM`59&4W-oU$FslHh@1;#$H2e$q;y6>z4n@%MVr{$`)+bi$-{ zwTQ1f0XZMf3rjp?pslws68M};2kFavP(QE&f(5{`1z*2V-0jJXNe`pzEM`v3_39x1 zAXdenY`MVNjE5j;n*ULhx#n(@Bp5d3j~1`@5f<}#MGIY*2xX70@MFo?@<6yQ)ik^n?59b(-j!F1|iWDLS~z# zSU}Y7rN24-A13o4EeU3kZW$!MPJfjjV=@yB5MP|81dSy<<&M8xei=pD>u;HggRG__ zdgV%z02FiHZ5!&FpYSY#&pfRY?}8)l)-0`B4d^_$Fb$2RF7bIlDuiuut?RnQESv z58J}w;)~0QBQDjOj!gd5%+lCY2S?98fYES<4X*s({qewk|F&dDe#Qp=l<$$0kBo8N zt#bVpLqts!k}&o~Y(~C~G##*a1JD#?X?mg+oq_>Wl0ZpGd*xULtKZU#1Q<;mDNx%FqcUeZ{mkecDAs8eJX7tUNVsZ zAo0wyzxdF+iQ{?%trbV8%rAuvk@9QVeJslN0|%;faYkGqW=sN0MIR?VKfQRb1ZUtl zB7$`RVIH}0);W*vH?@Bu>%}lQUI9iL4r@WQ@LWurS)PRt$@HoZie+wL2IL?NFi*O{mkgX z$=cH8YJwtqP0=)|gCvIrYd^}v}Lhp)ISWx|Rc zU?B*@GZj(nv=EeB$ri}hv1dvXygaT&gnV$8jm_wrn4e7(M*-9)yYKkRKt*v!j7HUOE7tSZOm*oUko(JDW$e@Tx>!EARp`p^Ez4nLZyhJ;>g%HPn`R+H+NJe)MR|2x9C@V7Wz{y zWAg6Ku+wP$C*_?uS&n#2lEUa#gWDbr6P4h$ zZzu_IA#iCP?)cGwuEO$gryXb&HJcx8qa+vtiNsr19PQt%x#1!R2^xcAi!5whIfY

AGP2cd`rTN(V-*+WH&U`C+t})Phaq&B#Xsll~(pdV$g| zMc!aete%8s4P`x(w@kn>nC4C%B#SZSI377n$LUC6gnnwSry&+C(VT6!@ zFZk)dpSZsG8fu@7$)qki_JG z$5&O`VOkW3nO@cJ1Izy*38WK^x(ZDGPcIR5a0q6J%$yB(7-Zw)6J@NNzB=+r}evX$eTc@<%tyskkj$ zGhe2ykw=yr;Ttx1=6CvvDy>ZjI8~r2MAWOo@dhBW_3UG50DXy)!7IlJGP0RT%~*?g ze~jQzAY$h-8g4*NGW1R*6bqvka0Lfj`r^$bols*ev1x8gJ|?TD-nMh>%mI>mp3mC5 z7J`pdjf00>DdbTsCgR_+C})#n1^Ppa8(^IyLE!zyQ*=GIUZt5Lk+C($9?Jf<6#xxm zXOR@nOCOBq{7xG?4d4IISuLmy2%HZbXUKqqL|5^oL(IWyx|>}+j@>1lA-1Y@o3cgx zNx6^}X ztj2U;@gTnLfb_$!Gg&)Z8lTfMFK}M1UPoo)hy~yRc|_r8 zUt()>sd6wFXnU1i5J^W)3VUu=#QjMboRvFP8z`sUbzDI~Uov0?7sYxf}WQ0t$I(yaR(LHx?M*`^RBwnv?elzr5siyk~j$ zaGXxcA4q6{$`r z?-K`R%_lBv)vl4x|L2+BbO0HUPA5ulXg5&=OLrNcvmC)h@Co?d!ih0LSj%079Q9iE znik^kD3q+$xV@Sw39Zz`Bf*FQYN9oLr_x&R1K8?#fh)a^D)=Y~f`Z2y z)GAGjW<=_jjBQuH(dW3v6l;=Fsk>7I`xwmT8DsU-cmFj@h_a%5WH(|H)o=?Cq|!{i z4xDZ#kZW`VtTv*t!+v() z<08)X`^3q0AYoch;Svs|*%g0#D}5@yq~^dH$ipy9`Pd1XaaCP-Xb?P)r%jvuembCt6|{OS zjw)QZ!ZbE^tJ-@}N%nu7eKIM8L213b*5S`L1iV;zt!hMuNW3J%eaJztk*jX+h-7x2 zz&24BQe^&u@QZ1Am_Z8Np!`Y3MT@vjkxU}gq{+j_NQ~hocH>C~3;3?nhmjLaAzjwBp0>nZ&5iP0Bq&6U@O39kz>v8??EEzh z=@Wg;mbIxvWH_)CA}C_dKunz8eiq%Hz{t4r_gSmGd~?ugalU&!QN~N&S#9XCWc>L& z{zG2{oTt&2#wG0ARf`*tEOK|xQFOcL3^0;d=m_?;8-xRy^z z(yK$abah2b`JN{44VUb-sc?``&g=O;04P9Rpk4vLOQ~vD--N9~+Q^RvlpZa%YCk$C zD$zzAzzeh+Q`$?T->F zHMsA=igzmxinx@hK@^vHU=-x#)UI5k5egCut=HFF3vxx6vURARyp99lxCVbw4Ezq)&$bb~nwmjfQN#koR1-eMqs z#;Yq*m$mytR~R*VG!W7Wi&_UyK2ecOB#gwnj&tcl2m`Rb^{>xIb+oekHHVXczWu<< z-o+0n4xfmdmx#@5SFt6HyQ(|SakPyM)*7uf!)y!lS+?IAt$9K|z}RK^py#@|5Tv8< z+Am8-b;Rh%GU!P+XEq`{U|XWuvJP~>MAgAO;26yr;b$q-^2>qWD7T4bfcp`JNI{L_ zXBy4vA%f+G2_|h>YF}1KfGefytTa-FjHYDb_^fBORar*YTd=L>B>hA|h37qcyVjTR2$TqNB)*dE5zoq1_eVYr`RR z=u-(YvPfvEUdc%E>|&fYvB(hU(EvPQv z5CR;|dUg>S9P32qEYK4l>Q%v`ixgZkL7{=fhV4B07V>wiH^#zQvp7)sOCj*xfJJTX z9t7s+rsKUg1k%MoH9307$%Ize{$O4gdA;@<@*0 zum$CHqxg^GTq<=_IeCOyr<=b^BHKVwOsx(zzneShuU0wK9v*1{|2S~9Rp;_75jsP) z^v|eeb4Ro|?dLMF1w1%cd+| zuSu;CffXh6F71$ie8>8@{~!3E-Gv+Fz?Y0evGyZ`OPnLG%V~%u_C-R@vPsdSx{^m< zOM`QdVt^|hxZ84R!@R5Ps#qM1uwe@D6%kbNL^}V9k`=@U;(7m@Vjb_y|BZKw5)0W1 z8H-b4E_E7+Z+z8VjJdnrX9s}r;hiESmTECufbdD6+w}g+L@MhfPe<8U2`oQ9qrd9w zlWNty(Su0FUh!_P!P$<^y7dx6qhUwOuG=$vxAW{60Mcy;qXg5R1%d6%j*xbGR24cf z8h!m!=+dg&wV_!VHE%eEV;~zPAS$kjd~rmtqlJEbN%4NZ1SC?k5`$VCkz(4I6J<@z zxzydEruD2cYzp1hhq-`gznqUyl>GW8i@$l*0f^~27pt1@O7(n7SLR-Ccg3{X_*IP2 z-zs6yZthp(8m*yAn-1ID2#(^qg0p)bu5POe60D>*-FjDe{A`*L^oqsF{2+1%KbcuJ z70PT_sUV#A$Hi;{NgDIL+%pqzE8r%8Na3ZZVvmFO9bBOluoLkQONSUaN}b=;lNhRx zNC(`{A!zPJ@pvjp;Kcz*xg34xL4iu@K(%4tM<06y1XWBbUZdH04fqZlnq@B zKVb`>k$7X}UF88UBNiyb-nRy2(Yv&i@xbx=zGYu5J$mbaLX9CP-HKT8EF|dYfp%JE zUfm&*2vLU)(GNNm?CU>xk+w;*NQpxS9j&xoF6t|pB zOmrT56bA*9p)S^uNWHO6sK_%#57>mV-L7|OR|E}?S(_1vKLBL}jgy}39F;Bfj==A4Ux~Ls?)T-^pyDu~ zSvetJ?$Kd0xnWa8U{J2S;_> zo(qgs0pR+!=2_t~U;ANi^6Nc9WeFuRy2Jk5n^G6HhjHZ~O+LljTdx(az@W96`!YQA zEUt4g2wt1#EBS25(?%a%f5>dS2fL!UIoeMSeE-e9_}Y50JV)>Imi*i!>O$KfbU5N6 zLMwor3}h*SZ8~4j9>XZYLvG%^!M&NTJ>1lPb+S!k*3OZE6s;OADbT!45%io=Flv!Q zBbc2C&nI8V;uL%KV4L#a^Pu*AWWI+L@^`NUx%HnTmZ!bNadkP8Swq(=B0Aqf^$J^F zv_{cIYbcE_OH*_+%QPfas{$6b2=U!g;eix)AhoSNi@*~tW&(KCz0Z?vU1vixJoAW@K_~#jAfJ zxUR#mE9O@OY+Oc6A9YPcFCSwTC-d`f9@z;zWF%Eh!_;@s;=(#O#DmaN;shc;*$0=m z%)kwhHHGfpbwiT>s*&xLIBQA+tHe(9+F&1?_;X?yd`3+W;L_yQ8>m^F0$>xWjP$nW zKsWKJ3j>N*vePIM&Iuz%$jYJb!_paYS=;rL@v_acjH|B2h~*CRlE&4eidzPwwBEc` zFhuUh=!*rj-3%vfXuw}BmYn15O{0%9hKUv`@St5_0~il_zr4Tjh31FKeED1dAB#@S2F3G%*ILU0qG& zJ!>j8eCTU)C#aa^yQ1LEjSG3XCrPa0FKLJwouOZ-2t_8s*oEf6THRk`8r(M0)FhG| zvqCnB*VOMe`iz~X)v8l@QSqDa5_rmhxHGtvn3XhuBw7#?8SQg$8N0Vwaa@Wl5>H=( zn+SZIiS`}pAO$8*j0Q2@A+Cq(E~c%Z_sl@2-qeWw_qz7~>ZX_+EqN3$5=#caPk-K@ z$&TJ7?V@xd6I_V1da*`?fiE@^feZn|fAdk-{=bY%rrPz>KP}r>)-m5*4`QqYFaWy9 z$7Yj3rs3a^x3F6yYVXUuCJBg~ffDj6%G3!)oSue98#MzkvRArAII*-pyE5!c*c|TA zRkg~I^b7JU7Oe{&vA*=^ykq}9V8)0!hgUNXI6%%^)Lx?aqYIaY?Aqz{Hv<_o%+#ky z6g83QlRzNEeyG3Qj%ofE4N*VLKh`M^(QKGLS%3?K%_7Nm?49|BmxI92g>)94T&W~- zlNC|#J>;@{SOqe{JAGl{P3wZPq@ZPafVxdtm#0B`w>%!(JVd_5*PXx*sRAE^mIZ`b zgsy?)A|;PmWCs*LH$c6U4N+lH_$Agva%I_s_i-lQK}bbTQL!MR_14;uTBmTO4{AX2 z41Ny47UpeB8^GDZm7oyA(X3Rqt(uaA$X8M*!{jr9ehm0}aZfIPAlI`ANvekUE}oL+ zK!!0A1!%{CVml$_1CUm4{FvH$ny$vy9asJq>_^eF;F+AXBAc94}9z=Q) zDPn&B-pZQK&UdYaQqnoz$s1OWT6W6hRWIi&;r-q!*z@_J^ads7U=;kAiCw|F4`BO> zNpwucX6(?$yV=;b$E}0n(ZbbE>m7=978zHa?$f#FpQs|?x4rt_E~R|G2#r>J_84>= zDJ{AzvJu{G*~xJlZPKjF#-0fRfuC)l7I^sreVEMWsL6*#c|L+dVt8h%_V&cC*w;Do z>@^S1d`KgJtlLp>Tj>I>+He{y4Z1(Vx~po@Hq)Qxb)FD^5v>Cr*%*hM^{aOcnUm|2ZL ziJ7SS&mb7ep_P8Qfnxw^?RF)Ef6qCWz8>LoX5Ye8ZkvR}^q?%#QS$j{g2JA@$*7n- zX7@4`6c&};*=S2bvepsz)=)a*-oardMAZInMqT$un%k2fr+RIuDhM{d+iM$#S~5f*h8Ha=-q2Q$~e_MH&1-^PE6F)J1RAaFc1d8 za_(n%inrBSFAm{dX_Ff*u*O}4BCg-gQ!cmOepcY{4f6{Fn2J+`vCEii{cn)|)}K+a zM|~r>P40o_c0zT_wG&goTwda}HQ@X6=pXcAOaTYxhJ1_RJF2+C*ONBz)JKfaPx*`* zn)G>O5715Dn#T!|kI847Lgg!zg{g81&4FqH7t_Ezr_6)5b2p$T!C~40Lu^`s zUH+Lm&kpe~W;Q$_Iivv=-@V2JgLeT*;yXBr)q<%`pT-b@7E_5Hg2i%>`jj-?Y$iV` zvC-<&kv}ebAcVtys>XW1ve~%0bT~dOcZAsP)|4@%ah)?arg^xwFUfeS1R0e{&SqzV zF?5{y8a7d*N3vy>uV7>)I;rRKnd}AGdw1^zAjB!=a_ADm=Io0y_ncQGXa&pNgYngK zSseyp*Lz~FU?Z@|{x^VSBhL4X>i_q=vdq_FR}$h4)H8EsljXL3L4~fXgxK!I`_892 zdyj1rh4-NFKQS`_=z=hXCZB6TETIq_1MT^5s^@K|HZX%myFW6}Gd-U<#Wuf|wTF>ur#09kGayO=Q1QWXWDD&i!r zi!4LWZJr8)&r>>54?lP+NYB_Lm1y)unU#rby1Dd-Gaa}7tC3GgH)L~Ct7MngbZ!>&opUp#a}~)m>)wb2`R<6A;Ni$BdA1X#z7J#PjWO0hXzX=vVu_g6-1WsR!l1` zajkIZA_XI=O9Nhjtj>R5yqZT}PBzaxrb6G&xwH8_H#{3v{4ktMnf_3>ITbO<)zqDX zol;4wk)!xW2mBT&XY(g1;qWpg#^GCrAQKSskTG=4&HEuBReyoMrU<=(Fsar7d3~ZU zhU|=!vH#9{*JK&e4}3+9KJ~NLGwShy@GI0 z>Zl4Z{uz8&%|thicv#bN5z+10;e|lE1FY~X@SCtju26@*7zZpUanWv>Yn8a-|RHq=e}&!+8?Op=vWHc8tTZnh{2DunKr7Z!TQWAz7Z2 zmfGbs7F$cR)-zdbG@sAuW7M%&6uDbd-vUM1($N2|$rep#cC!{bm$s+OKR;d!D3e!| zUDq^#r6hLMsBMF)cWz8>PDj+GS-6S?L{4NaI4VRgK;-KYj&$$oaeJec!oCsQTKc?V zJ#@ZOaFHo?Ab1A0#bd|BvzDddaA;ZRp!YJolhwX%W*;+nV$sZI-zdv1>3Pk)#ot=C&bFQ@4E?4m+?-VpyfcI$t#b-jCr)b76m-p#45C zO>cXD>zzCDHz)Ho@XIt{Ra+aoD|oxAApy|ViX+7FS=h&orcOV*APDh?(mUU_G_vpj zafM1n?*Uj*kAg?5U21qgFK_F7;7HZ+oi)KtAnw*c2X=U5EbPvmOvG&XG2z6hwelE>G#fFxG3 z_Ebd!Z0TLpStEfHaxnPKZEKi5(l&eQce|9kS9xE?^PkX+9|w+j`hX~BU7Fn)t_$N( z0M>2IDi6NC`@@#Ua!ix{o0?fM+Y_or>odrqkmK{H4Hzr>f5e_Qx~>;lCzowL!~pI8 zIU%~)a!4i-0l=^XqsW}KsQz)cue2OU0SG1c+C-;w)zq)Zx@mf*{I~-O(OaKkWE)q* z7;8uqEICqc7AN-X-%+OhiHV?_xItjc?lQe}oaFq$ktxR<6*cf)D_BiiK%a&tGo09N zHq-Db0KzM5k+ilmZTl^3*uQpV(})tKVcB8tMyrP0JAufwc0mJ)@f+fT3JS zD!Jz=VR8kc2%FIMFBIhu4Vz?J1r^b1eF~`EBF&v)le|E)7Db^%7$a%X_m?br7LQRx z7x<%?fdwuc(Hi=30B13ikE(PpWTzJ`1p-p3OQ_p?Qj#^;^RjV#YY}aK|De*k92Azl z!5c2*<~0M3@iIUv$N2Cetwm_kSiR-*9X+qF6K4h|+!$dNDLjjcrj$XaG#E7OnvlimIO)+B80ly7qg!witn%lLF(*0DD#&!l$Q5V zzK18FD<4-kUuB_iNaE#%ra8xPq}PDi^k~f#*Lr9Bf7hPY;=lu1`maG$NW02_cb-{L z%I8zmsDv!cWGk0j84n@>i^_6S_Z6;kBZA>8chy*2obYec5p?lmkqr!x3JefgQJe;4 zp>xbJf-Y;D!c_}k^*b$#zbWe4S_2)(cnjZmKFsc0Eccq?_vCOmZ6G1BlX(S*M_j=W z37sr0Pa6x$)pVE;3`7{loFf(X=6P5FXga}m&lvI z`?^)va%1|v2Fz#=f5cJgKv9$m{8i@m&H!}X-7(n=&>1KK4#b9$vl)+FpA7GleAEL5 zzZ8eh3i!}%{$`~UHuxeX6}fuf=%EZRE*Z&w|{t zj0+LF8leL7O~6|U)bZouS18Iat2j3D?&k#Cl@QCTRF}$OwC!X}w#uZgmvp1PzUKc( z^3A=x`tE1Dlz_*-OUb|6m%izzpu+7>b^nkq+4%SJT*bt|y?-XMhV8VIMqh&qlpVx{$3!WfB;*5mkgK{N8R>H9gpGKi=o0@CJ+J=e_z z#4cEfZ)K2|^9}_j$;xmbubBFsR&>uPM_V*sxAuYrU>RpVxqJ@v{|b{AKAHGCor&Mt zkTZaq9nK%r;59qP5cC6fjclerII^BeiE3b7lOSL#8?dn?6miM*n^qU6`_ZisUhMZt zU*HnU&|${5epXNoyD%`F<|< zGqYuxNxE>>NClr;1gJZxlB!aQGj#w=2o-2oqN#HRrrx;5QVgx=5#kjjBcLn68wZVqCWmN^;L>?Bbdb*a1 z(TPM>^)>j%Kbf8&EyTD(r@lqAITu9vf%-;A=rI>`I*AIi0DAYHI29K~>iRxg)YZ3@ zT0_8$jWOsjBp6KSj^f3N7zFN>gcLN!HG0QhpP^p^GL&NE;an&^OCIqNBrXy!eaDn1 z9jYQTBva5&9W2~JCt8_ZPTdumV=f?6jh*ATr&10vdfWB{I1YL+a>d%7iOe|t`+RAE z003Pex-jk>a6s^dh|n;Zhw5puHjBE`i_ft0b^BCN$Jvv~vUf#@OBFFF4n;Q1C5Y}d^d>v$`wJR+ z_WH_0%>OKix+v+3Hi4xmLk7@LDC4e;2)h?qAqnwRnfQ^cEVsNXg!xW~(ScWQRPfH~ z8n-L*#fe65MkFGoD;AnvoF4y#bGrn#xPNI%KbvNLAN6<=_qOqYrs=W zwh^)N>lVl@{D6}_d&d6~UEP}J1(sx(E#bqSTJN2ZgeiJ>rl&Ow!1q}Wlsm+o5dI3Y#BD!WE$=T+T4VL^Btlq1_uy=xjgM(Bs z`&65&hYI;$6C%oLK%XSy?(_}|K$hyRD=vWoFwz=Qy3!bH|AF9B9|>ZOre~tQS6BYn zVzpWwfsLixDJqUvya1md;VFB#dxZovdT%6BZ6?}>t$2535$v>;e2^V@^^nHDkC;Fl zJI5Xrm&%o8yul>S4{b7()3)fCx>FTvSUKUL8edo+tc}56nD)$q>wzP-Bj>#+Mz3q` zCCO^~F37WX*3EhSH(+M zT_(S7(2f}SX9OsNhuVRF7_zB5;}(8k6I00J7iI)QDF?gFlJJtM=P!q@K;21I&P+2t z3!ct-J~^LsdW&q00lCGxHS5S<ma22h(kVezk@A0}s*)ihW@QCF z#a&lcH)%%0^~*SJa%H@BWud29h2$Y!84yOtC|@9J3n}DXk_!>vwSgtb<$?rtRRlG# zDyv2go({k}R3~UHL1x*+#pUIL37wxUMAdK}8=MvH8Ho#}3k2TH z2~C!*xG$Dh4pYfLdCA|q;*k1k=-qCtT!@D=R%n6#+=MBI&d~*2mZ|7W5S`A@~dZ+fGwyD8OY#rLofAwZr z`IT6=L-t*&Wbf_ThZ7{_C!e>{5RW*>(cjG*eQ%7REJKC}d8& zyjbFxaRO@r>B!V9f>z&?QhJoA&FmO0%LfEfhhH{762C!w3yf zy(&_S9WBVz*&7#4_QA(8G|98-!Ja`**n|cyebTEM+n@Naalf0P#IKUHNRot_0UW|x z(cIt1oosum8}B7AnO-hH8z3=+3lbEodeJDuZqaSssXs}66)EIOcp&9X=gKDoge67S zzwp=M%xy`R6q!dnL!!MI(xT&Kmfe~#s0Rt(&D>QPoU0l-hBSpJF3zxUcu}? zDdSG{8twf#q@arnH)$4mS_qt)sYp6x0IFCm&C4X}_{%@8xek$aM@{jV{27RL!~k*{IvZD=F)MJJGLbC4p9=z^c>_IVY?Cn6(R6 zk{O*`$Jdjgo)JL<-{}1BB5|HXZy5kc@XH`54UV~ek#W@ix9mc4%BMK|-RmoR)mJ8Vm- zVUELdVUy=&5%q_f)l(!G@4GDNQu%u`V=g2ShR8HNn3w}e6Mvn^<=Vs3#?F#K+!%?F zi~xRNvnddPEzd9_aa))JqSh|oDBLp#I2H*9C~)dOZ>9dWCh6k9$|~2kCk}YlJL$}La8Cn{hfC4SS`B#aHQ1hdNFBVy3`J{o_xX5#VkAA0tP+hsx^y(TENOMYAj zEl5Qr7bx18T_qOgoEN~x@nW5E7!aP;_6P2{cGinKeaomDgj1JPdz_mcA6SGcMM7}WL&ch3kSxXJotG8ONOP{+U z^1*@)A_;;M3MlO(5OGdQnW$ziz@q#s-GLe%Qu5R4kMvtL_e4TZVN3U+Oo^LggI7FN zsq{2Yy1izXmklUs4oD2l*TCboZUJ0N(bxItjT%W)RF_fiff?eylhGB5gl7=iM_=Dv zocJTMSrkUVX@VQe7sl0+m3Qvo^37TEJInvptG{4E8vgtRIM`0LJVuyWwHu}BH^36X z)8R(`xp_1H#+QYpw(m@v&PxkJYvv?F$YaZ>YeDqjktkp1}&f+8N#VYnkrP8!`XEQ_A7M{AVyX?*wRf}n+oR-0A6K)v|$Y!$K z(yqGjvyb15Rb6V*XZ6(*z$-3{P2_v!ecO3Qr~lVv_Tgm^N+fCATRcy^f(xFcDbU+B zu+{2Bl6p-#C8=r?MSvuHOUcoMk4R=p6@-P90SdJbNFhD2#no~Nn-^g+J}a+4+*N5$ zeZ%yy@%CTyYcI7HW@d+8?eyX~d*Y+6&M;gHSvQ@V=AM=je!DW_9fk!}`q&!MEk81_ zjCrytGTA3*_I)5TR=s=+6gi0%sJH!;M^=!ZMQ?DN^U~#FYY~SW`g^;YxS&kaP8Utg@ntDUPXV7GbDyVtld_BO&bw2yBgiMlHOUYw8XC2bK}$=zF9#LFt|1Xuscm zO7p&%I}wdIbD^FQ;}UzQje=P0Os;nTs8ALX)gN3>(o-2}F>+}!qHf|ip~q-{23uEy zsFePGG5|BUSNl*SpxR05DNkGo?48={TSh~;!7rf3Xf`4m-~T=Va3NpTy@bmtODg7D z@pQxOkJU1@g`iezQqvQv9Qplz?DvAfQpsE-4djxPyeU zz5Z=kY)B1w}>SryMwM2$q3~MG5Ku-_MQ~T zt1;Fn3$aI30k1n@MFY1NvZOD0fySHq?`(Ntg3L#g$C7xQo6+o;*JtD8e^pwv1(TP$ z=S*W4kPV(4APZyilw9f@qRtkPzhYRx zoy+Z_iq8%z?zrOO_ci2xlB(OYg?LBKbqWc^kS&q}kUL=VbU#_P_D#kmC?^4}!*O{#-Twr5*BfO&8p)oqV$w7(N~#_Fa!g z$At!B2<@nq%>bpC-8J<@(vqIJ1G*O*x&CS}*3h|z+AFG*vnmyUa{moMSOML#UAoyxXF;1 zl6)9GB=;k>z!Ei3ig(#G`sGeZ!AwfBk~-zqxa ziD*E?$g7zPXiv+3nMMiQ_kWF?3#f?t+^`j}`;c05f_*{IM=X*byx0j16aMQmExZ23 zlH7^hya^r^TxY$>J&oV_2$e~ExAaYH6Fz?px;Mo}fXAZ|7XtW*Y`XKd#7_z(`TJj2 zi}^DMZ|!{Q*ujyKf>s;aNVE!~@*@hoDDMW#jeW`mrMZRx!8Mh~>u5Mhuz}~7<(}NYtNjEcNZJK>j zixo~!-MC`Y@|dH_h2&lsZR~Ov0vK{f1w`_dlfFfadPyUh7_bN9K3?sGE!M`lgZC8oGQ*bLJT zlg}y?k9=RRJ5>3_an;2vITJIw)6V^Fe`x(8TvL^>5N0Z|56`M%;RD`gAK8gk?=Zur zp7zp*qvfYyUSC}a+bY=+ccYQ8GV2_Mv0;0?ddMk?-b0{GD^n=8?X?~Hw&pn(eT!<_ zInyJW?=3~!ouPOb7p33H0_u+A5H9(sjOV*W?#H3%XqOmw{BODgd;0v}+PY{3O@Wpkwc8E+iXR&fL5mRdd&8auHA zKn2)iEWQwL&Ll1sxpjgyxLCN~BG6n8?_R&|;RW=lw<5XMD59eFI(s4;6JEx0ew0k!T*hBU&m;ZmJZ7KzN9==LQ0tq8rUx zT-FZSlK%_Jfz@J82IpQDI`@Qs>LcFeM_qJ+(X0QQD~Pow>qm|pEb zw0896b7F}j*8@dY&p;PJH^G2 z_w$9f=Lsou9m(;5st}6-j0N*PI4laDrhbq$V?i0zhF@o@X7*{KPHlS)OCh9J8l zMTO<$AI^N+O71+RYrTEo+?gwNh_|@6dO;#kj_SBgz&UE;uo&%(51H&>p(NPheq;QY z&>=F9-BFzG;{#R3jQ!vgC54d0KYxH#{b>8`OC9#&Z6}ezSBUY@1K9R@V?nQi+<|PG zQdFz;!LY@_m{NDmTxC9a;eMWIkqpB3wpKI!~~*?Gm;mpd{@IRx%cEGN7Kf9c2^ zsv1~53^_eXa0qhfM$uXYl!ov$R@X*tiD{^R^HnyOtStD`9Mhb08VnAqoe32IBTl2K zfVy2RZ~3H!qr5;5<-WLlZ1X~st)WXQ>RVx1%p;A$GAq6Qk}y4SHlu#VB&`~AMzGn= zI`f;pK?k;#=UNkto~1r|T!r|8VgryM0)R`C5>l)0__|l}zndz0pL&}Ypa8`=aiqzH z?4uBtHD|K|xD7r`)X;k9TjOUI9CPGA9i~xWf7r>ykzY9C;61$?crQFxQ&GoaGK3U4TD2GgYT`W!6FT0jI^i(VbYhp$z_6V`3RPA6Q|;GmmSsAA=_3 zb1Nb!hVxFEEe?kUaH07P&R81^SE6R*t(Fc@WbFelD6Iqi_I%?24Arf`Kmms!P?GV5 zB0W^&pa*@#q@7(5Hm;>vz3$z8s*v%kS?FIB`w=y|-kWU}iIZb>NS~!Caxp7ogTI!g zH}-PQ%3jk2&oThr`3ipn)US+dW&{)m=N(tfQmOASOH%p2g;AoC2V)T&NCX)9YyyYZ zSpuq*Sir#Zj4gp%RBpiTulDUnQzs#!5e!s(m;Qk$a!OA*{ckm_LD$k>*7+o!w_=ib z8g9ROvR=d9qvgvb`Z_LX<+S!g`E&?0Y7PUy-uA6W_*4*$f|vBM-5C;S@+-yeLN_wv zBw+k~U^{ujW z440<8iC#2_QR3?`^UzxdsMsZ=eAkga-6AEzD@ysDP4{fvi86mP&>VwwegXe=sh}u2 zMO}Bfwd>1A?iNg}xXbZd_ z1qiCSW-=a^Rerkd(g>w-nVN{Q`izF8r9pYL02Z2q#??93mOMY75pEW1=i0oVU7bXI zkc$HRI_Sp_DP@nSP}&2;Izi}nyISpXM0?w3i*nc{UIcpAn3eEG)DL{YrVIIFsh&T- zldf10ILOlC+z$A@gQsmJ)zuol)GbOid zWLsa4h9oYWHCHu!U-;$2N&J1T7uP3>>DPUe;Efzo#*F>ozP91Hn$%`iZL3Ur1Jz4@e!s7HrZ1X$_}&&Njj+Ha+8 zaFYA?hJQ^nf|`4?UJsI~XkVA=R~&E7`3j8E56(yIE*hfZHg9s5s*<8ulp11W@EoD^ zGvG~8E_d3DKvs!-jchMB@o9B7l4a)__6 zqrnvu0%FhXW)Ff|00T)rXr`H9xH~U&Uxlk8#dW^HS#`a{T{(-r_AN4bQ}97gNF*j# zxeI^QNrHOKcJ)Z^WC_0?82+IUmUItTFMLdW?#8w&Tu%|}>{JX?+v|L2G$AHXj-d+o zb1lG>CI?R5d`md_cd;lD0`U5>)fay;JS6aN6=hO#%190nV-gFpnlr<{5z zb`p4Na-T>}Xq3Hx+NS4(E**qg7*{a5$lQgVZ!Jzq`>?}bRnW^a+YZUdwXjM8_IVbfTPV6^Ph-AQ-K8W7C z0rh=6X|N!?Z9`!;r(8PBU5}4bjh!!OwaSbn{DQ);4y$>F9`m&pNJR+RmC`ZvcQJCcyYNr#~cA*L7ttm(RZkLQwPUf{r`LBkVUBm3(e)vxC}uSB;0`kGW^!E(X!@ zVlnQqYg>Vcf`5yILEi%7kI>h&jUz8mnLP1~eTf(btPEqa;#GKhP}^84UK_8bt?}C3 zbDs+^!oB3=7X71Irx@L%$iK_^Rn2KSY04u3fPO=}50C;*E=#;ezOl1D|8^+IFUik(q_!i49w=>DuBN|2b0*!uOuoq*PuK{)J`|WazFW zeDl}$IWZ6%;XH)Ls%etoO6|K-;`2{N;i9wePP2Kv?awQxBlPSN0+tDspwjo=DS8n% zzeC|psxN=-k|ZN5gtxQoV5|XeD+nNg^E_0<#_H@gt%(JNcBwEm$$0tv8n9Jrldv8R z=BN2kWg4#Pu`BB%2XG;+jp~5qF4Y3yVje&oQr<&O+RPe+`W1Xe3(NA;TO2z|}Hm3kGdV1s8`GTu4G#$9WXw%vIit3+ne0z-M=5 zgAX$B5s#;MGMI{7!xLty3|UUg>>PXcsUTFxxsH!8d6h_r^*HiZ5-{*9?@;rj<^WTh w@<`@%jSH#m#GW|Tm7dlS1>H^{F>1Tqx6&Qs!obe8du^jR;4TF>G5RR|;Q34zRPPYLm{3U8N%F--w z4L83{+eGf+Yz-eO!yb>m8)1Qn_GHNh;GANyoHw9^I)AP}>5drv$cMeuyo=6{lAUFe zok729;X?JRj`2465oga56Sz;c`o|^{qS8xK%Crmv+nfz#ZYm<;At!;89P6uGj%pRd zVk~S;B?J<$K>^8R)KnlEEb#Mr%ow3cQS@o+sKxiJ zORaV_RLSJXlOz#4KlqQgBQ^J;AdK_fX%6ZWbcND`wK;Pt&3NlVnAr zp8$;2I&3)|XQ-XIiDN`j)X@jU%I<|o$6B2>QSNXtKvknX(b83r{dj&`J9`xp)%UIH32^CF5 z)`kHpWwLd21^u*A_2k8nkt1aZJ{8>J?VI~*Mf`EBV#r}^Xv95fi##R(U*&?rHGb8( zd7>4&ium6TdQoZAm@GgtH+2WOfz^eVxwG?!0}31l8Z(JF|EPAa7bG6L=_*L*VIOX& zQ)Kt4N(T_OoANuVW4%=|Xi`wwx+D0;y9W^xMt85-&$7CRDU6$tfvkDQ7DyDX=%Q z$QhNA$H#K-;6`hfSLlIZL!lk6xp$`!6x2^-gq1eP(r7!m-((cZfcVvp)iNWS2m$D%Kvz+^Dy1yv=`>EouZ% zQPY;qbo}AVK7|)1?r(-d{3xV+mI9&H!;24TdXr$agAbjMTY$B7mZXHUyqkP1HzXcJ z%Fxp-#sj%TM#EWLbbDA=;fA+a%kno+&Ci0z- zt?&7F)ys+i&ARTO&VmixWQQ9q+dI+>)uB%XiB7_i2B^s_v8DC-0MIP1tI`Fw;lKs# zIF=(ob|*lHO?yctMbHP17O8mB9Sd23&4>WY`Losf64g1<-S5Z7`r0$2Ye$VMv z(m0#C=y)Z8I!_2c2C>56s!y#jPGk+^DoToFmIl*7Y!%TAti_-vHYVxmn;36Qw zg@p*6rs>xMGa`#p%ZP={J-{`-UX~+6rNP+F$eFG+kcW>l{8KbQ*Y_a4NcL!OwRYQ%kZ>L~a zoeuHpWl5CkM+kRobi{MF35A|zSwz~W_p6L+doMbvHNL=Z?OhKvG=g|YdnD~%tiZkJ zx-bA6UE~?1e6VEUgmj+VVgvo3U14n4YHEV#eaJxx*gN2@_;RaNu{#JJk1#P$zw0Py z%isW6>q^n>34XD64}(O_Nq9YB>;w2Bw}hvmlO24%3%2mVsn zp@$tl5tDEFbSqvh#aRT>YrFBw^6Yo*N`sFa!3thH9>x2i*YRN;nWg1!LwLjM#GL0| zts+`3BRP#JRmj3tQjHi+vXWm6{Ot-*MLY;2h_;HYhMI=oK7Ew@=D~uljlKU^zJ+Go z?t_zJrL#P@8aP>ixs!QEosf)leoWLh_ND_HpC`#F>~)ta$=C{3{zCEwV3HVl-lH-0 zopNQlS>HHUuD3*CDx+b%#Yo9Xmq5lV)75k5;E9Pr_hfO>tq% zst0{A9lZK)7D1yMQPyf@;SVU(5v=LWG8JYc_~L9b=vrHkv=8(567%bU`}<5SlG;71H)7+_1sS z_H&Y?qlUCD@jB{34U1XaiBiB)#{+xfSk7lokO3DL z%S=1*WCv9VDAS@_&q{3+$2Mh#&r(VI!EFXqs@o)voC5@8n9v1`^5mnwA2Z{6=BeU< zE@dcJUQVe>8|Dldim(kJ(mb{`wI2mxxf(Cb$iQ|qb_5mbntZh2@|-=`5E+?8(M2yO zG~+R>8p+N>2(XOrEkJTYA~`)x4Y=xQ-3FFM+Ra_b3)1K3=c)mlhd~vO_QxTtvb4P? zLZJQhapa|7K5_Pe$!ZVl0y(khD3VuU6Hw+&sxGFBTZTWSCkAqB+H!M~>syMZ)=5~_ z{3~Z&OfY+-faze%7l$)&wM0Bkzuc&zc;-hSav#^diBTP>BYJ z3GRi^vO9hgzjobjUsj5|vb$gMllsg6^Rl4ASwK71nd0VJDu0L>PWVc(G&2Ki(@4$b2?n&ZPx(jcHnBC$1)$ z?o7>H`&X|X^%;;gJ*%3uKvZv68$czPXrkL!g@WIEZ+Xn)@3HEQavr0Hpm`L?UruT= z*?8hoXr0uvv@*rv{e6Ve?h7T~7{Di?kbFF7sJ03kD5W>|o(M-Xm_ z+*QjNx-Xm+X1y4^)|8rob1>Z)D<>~lz~n{`v3&mcmy!lRFmI=9pl!rB0HE)1rR*nMk9$8 zC!XnFY{LVhWDSna$8lvR--&5s)q?*^$kX<)Uf!PDiJ#aaARj_`Ks-SC>9dEGy^-2V zG@hqtcRpr?E%w$ZwMC3#Ot!d8VdJ~)N2C86C*ohrNWQm*{m)iI%T5)a*g_>YA_P|n z%q7iZ$(_6q(vj$qhneatJ`PCt$(!&c*kr99sj`*@BChDs(``?@@G_0H%Snc#UXoZ6 zQJs+4mf|)@#`MDz(+m!*Ybiv5qfTGftFI>8lI}K>(K?r-+t?T^0gvQ1hrWEcGpbv7 z!CQi{Ai&qd17`ygFD!HD&;^a>RULL73UN__hi3zg?HhTAuPMA)r3l295VSpOK`n^T z^=_Wsu_dZR3z5$zj+*m#Ks)W3EgZJaK8sZiAgrE!B4}V&%}+5aXFEIH$+_IzLpL5L z1k_?MHOX)pMphF1WKbH4DvK-O#PbW3%>_j;jkwG)(`h{QS85%W37N@nJ_;tEJ<+>j5C=Y(zL)Mew?n$Q~Y$ z4()MiWfgxX`dl0^+NR@x0n#PvJaK6jYez4DXNK;_+oWk})wAy4-YHX%`>jZN3iw1n zH%Zvby+ z0bX8Tq4OkspSa?#jc_x-rnJa0c6Lc>IApio{So&vrmsc0db=ov^n(hkT$)+z%$fG+ zOcE>t(v%663`#Ltays-KE9i?V3uv+3yUgAP7NxabqweB+Px?63^_4E?`MJY;Hx><9 zgTeYH22xlU36SyPOUS@5p9RtPOcNB-lkTgg5vhjf=yYLap*{vjpFg3s14MZnF-%oQv^oT!uJdiOm&pbQH%!={GhRw%qh66=@&dsGYf0MknQ0 zzxeFL{|TW3G9HOwE;VqX$Tm4=sFQ=paZgAy?nIhjmK3O!jt>Q$+3zXc*D^4~eH4xU zAv`nM$Uj#cuAhh0*_c=FL9EO`lS+AE02Rp)JK{)HOAR?2CdKR9`P=3gFk*zZ@PJ3n z_nx<*uPfbLxd3VsLufP`0`_FN1EaG<5w|u{>>afTyX~yVp5e0?;G=FB$@#<_ECtdY zem(D&a)nfli;rbns^}MCa-uOf653O}>gcbkCAJe~&9x1>TT>OYQ{Yw`f$!+ASe~`# z-~sMG5=|6YNbw@Pih%UE+kNSmmKZ`6s>fiF>)@M^vZ%sUW2sy0!nxWi>+`2W#wd5{ zJ(-TuKA$w`(Y9nWxJF7r&usg?Nx(oPnpzA?C@F~J+QHC^uQYf9@EJn=Z&qDqf3&;t ztnUa>m-b?f@rc+3|=##-cllTVht6xq*;6B_mm`g)T2?4C29aWiCJ7 z3o63z{rqx6<%3&~T*kOMk>fjUiA!Qsx{S$P$X5(u4_7pUd@r^4ro&k{D0)^c>}qfh zvuQ0PAh{wb(hcTC35dsQ=I)uimfbtLQ0RtL!3*oa_Nfgi!6>K9O}D$V8kj?~gb0|r z5Tuh}6C1Qp)#oS#I6r-w9}}dAC=OM5!I0XhbH^>PyUIR4--hc%pinjS#FSlS3c(DD z=O@MTB1taKy$Kz4zfK$G!DR_u771GH{1pr;IT;el0AN`+fqxvY`sbq_;7n)A6JV?f zhgc@veSnqQpH%Ok&fz?f-7`PUMeAbrdr z-=elnaHU${p%neqGhwU|`)uS&7^!~m4D!t!qbu!z*0ajXAOYS1+W1RNCN|L zEGDxf3UeT0e%vF(Q>ojmJZR0p{IRrpWU3{z8sBQeze5ba7d;7hh3N8-R`}u9`N&Wg z6+R%KtgTf|9nFy}802(LRkcz3O|IkOnq-_76sjd2=c0hWT@y1#n3N#Qsv*?Jc*5{` z7Q*cK2=_IQEt#7s3IWm3vBv^RAI4jGoyvod*?N2#P2pZqF7hs5KtU!5K907NZEX4c zt=_6|Qt-W*cO+QAGyK8_e_l6!PhkLi>3vFSkzYIU@*lxkIKc#$9baou&$~zk_U&Os z*x|t6@SBiAwQ{1Q|4nQ%8L824MM2pmRqtvE)AFlj2cfg z>?`R6yK-`tSeMOg2(+e+y4bCR9~) ztO?~2K{?=eFRypu2F_*xK@Cc$Pa*72_@pVq9_j|*RFu}^~MXkSVlkE>A3OVBc$W;?fh zFcwkPn8OtV2mHN8e;cVr%nZ3nJ{X!GMv+T`Jb;lTHZi_yYCNN8H2I$av>@(L8#a$sEj` zt>Dt2kWQ(HZ(?VjQ#DO>YD+hFMK0lP$e&Lh$)TZ9Lx@Sw zmJNrW3v42x>dX|k7Vu`lEzI=!E=U*`WE;Vrbh|a4~4!O z?^2q6!8s}4m@3es%2S^-8`b=5m!u4PjNRH!F(|AiKk!_u6dVUkheJ_Rk$M*&Km zIVJs3s($L`7z7((fePjX8i7_bh``34$iK#=@aHlCP7gPAE!zk9nSMrj+D**+kY~RY$uRiJOwLXN%21f7=a!-QJN^?9gi(|w=P@Sz? zjySc=+o&?HvlDX#3d_4u4~AMJ^oRuQk4UkKvzcd^jE?qSTibsXIs2Jbo*>Do44L>p2a6~&IN`Q^=l2n_fv6!&SGjP9f5T>%?Oxyu`8f&6{ z7Y=iFEY#c3^&eL~J%L@`jtyKMg0%o&bwO2Cn-P@M#P7I4@z@km$nubn*QOKdlD<>0 zYjdPsv_z}_)ou&R3CLLeX8!R6rstb@K9z zxc-hrd6c3oPy)F6dBvvd?(0L~<_aRz2{ENUPkZb;K(RV{d9T0V5}C3&Ggt)-LAz`!9pqCv~gHfhw=;)ija(g?SKg+0=kT}*+4%R45mQ!hfWpT-c9KL1ydTjtiAIOPM6$RJ`;7y1jU_9}i=Q$Z9*(I+Z9XC>CF;WCc5#Ont*F$bbHPqPF>h29xTX+w^^L=m_8cIiS4D)Q&xG*`q1rx zr48OS_PDi?qd?7+7rUlK{c`Y|msns6F4D2K(3v-nM%bjx!j@?Od5BlRWY+XWWuj-vG*E4I zA?|Z_9%#K;F*^laQE;@UX&W1KE52)q!R8kSzy|;jf{A6vtvO*%vD?@JMt-kADoUHx zP(%9H=)e-U!#M`MAK+JWYV))q1PB`g7`YikqpA|8gv&0AU(K!={+5gvw=hR}_kN-D z85Oc_zwPe_OeHvn>)hXj6nn0O5U!T%v@`A^Z{r=6JI-Tf?q;cD;^2DlKK_K!6uaTJ zVl%c^wDBO#J?`)y&ur3nY&bPl8f7NqKo0q)$Yf* zRioQ@iWmDh`N!+*%Yk-jYpa7i!NC#b+OM7S$pUBNmFsU|LaSuaTG$f@A#3&)X7hX$ zJ)d|dk9cyxp#zc3>fJBK5+OVQ3sYPSAX7AcirDk@EY}ins~uF}hHC7puO%-*<(1MW zA!iz48!%2n`7RhNm+qafx$!5*lUvV%cR7Gu_z~+tKmtp7J6OUY{+igvE&Jp(ln?t? zePN23=98RHPZ6#>2WmdG{g8BbOX9H)W0eS$pj9@6QcYcr{0n;d`|wQwmnRJBoZ|Er z4paUm39Ui{bZriYwR#i|!}W>BN=)$Krs!RPUL-79+@>ZEuiDzf157u~ovVp02oCQ> z{TD3{qQAk?#x9b(SS=|D+u-Idgn^u}WppIcX)y)J*FKNfXAK3F(DD4g#cSGAeMCA@ ziPjrGfY%)IA?*Z1S_a#^{}b1l!iNnaRH0g8jcK09*g+vws;qMB*l$-jTfbruj?&kl zM*tY6NzUpE$~OLl(pN4DiOCB6u*Rjiew4oNIc^bVR;jBJExWiJjPesQ+A$1e>*_J+ z(f~P-r{k;P;kImG5}Y&I9@|y#w)O9u@Mz==lBtj5I=v=aKB?Ly9+e(3f5YD-;$%%Eo2yhQBFQY(r~CE6J<*V@v^KI6&slnVu30UOy0je4n%rWu>ud!%6|t6=D- z3IuJrU!R8p#U|rm7WEP>T644x-8f-iL+aK(>~n#Bzx8e-kZ4Z`+9!SJWFDR2a_I)( z^35*yWKmoX*tjBeaRiu{^)_)@_y{2Pj6bFLiq79T^2>@a#7R@nG`vQbX1iyet3gv3%l6Elm2QG z^gB(;f9uEZtz}I6_ChYGHCT`QpuKFfZ$#11)?x2~6=NkYB$zhcOqH&^4WOe*+er)| zH3ij0r0U7WY`%Z90Wm2Ps;h%wB0zV@PJ7YH<5P~vN!~K0ginQY)&Zdx)_B>1w!8c> znaIBXzobv^{{?LcxhCcu)*{I&H^t<>o1290QhSizzs@O&xZzHBDl1qdvif8T8f$L` z_2@5d5x-E-krco*)!Ph$h8)M8ao7z7ykjx5#d#7_XffT`=N3I6i@qsHVMvz z_Ly(%tXWO^En}`XOV(AX151MRbpb}$2(+h-oFc5DOXW9&O9Ne!)&^dc2XpfOJ|Vo6 zqpHo1%R-9h1;qh;;)u%rmsAQ#h(s%ubK=pkHvxQ6z20`aW?ycb^+P3mee~AY8BN#1 zHZFPLP#}1c03rn9Hkzs<&?e#K2YE85lWP#zks+_%ST72ikdHR*%ZDtEt;64{Q;}>y z`7WR+*C3(;?=dUPMH3z76(vk~!Us1)n9K_KE1^jbffKcnh1GfM@X|G{B{YECoi6kE zxwttD=%!O-{^NXL;KgV%_yT+^4Dq?sJ0Md!YG67Fxc<2I?=9G22N$H^cg=nL-gXk4 zIsV@pAJ7U96{4@kuHqOSvW1i>tS-Q;3w#?riE)=hid?(0B3p(DA0z<$YB-E8gR>dC@m z3~JKZcPP7$hHBH`X*&aSV5jtHHc)15 zr?r!VF4tX>a+n~%g$V9XV`b0oj&mZL55mm@i6J}IZ&_8UBm}$`UU^m1pVFsSU)_Af1%#P@uv_oqI-EKI_*19p7C3y80(g93iPM}}*N#!<) zAoe@Ty-ruI9C#f%84PVLa9QyNjAlrfZ!lvaSHp*SH9Wt>Nce= z!7Fq@Sxjvi_bJ^Y250>HipmB#tHTJVVd0zKe0FOSKFN#0N~-keMof(XNH?khQO^k=x9 zVbK-%;q|hr9|A8=mRZZK!s(B`LN%%PrJKc80QHRlFs>wlmqu}GOcAh+Hb-9)Kco@41RbWlg(LBs3E6^?t;sg z=?-M^ti7NTIBhEMtf1O5zWUCNRY5~96FMb|l}jYG?_8@?vA_`v9K?}Bp)M7o2C@iu zMS(Y*D-5#LTJ_g_m3Q>pLojQ|K%n4q`Lr;Y5X4$iY{@;gwtoX{`J!t_r`iJ>4KN(4 zWX@D$P*;I%vS*exTqP!+XFb#X!fh`IHBUFd&v6Wg2Ktc?v{@5~`3X99bu}V}J-*g- zgCA5R;3c>!X`m{J2Zz`|P*9Nh5Cg>pT$|;av`l% zXbyGh{hna3NKDxQYpq?e$hoRiDekG4W}5Th1DSMR6Tpj;TPtg4D$79gqD0zzbDJAK z*vdw<*<{mz#GNkD2E!+dWajWpT^@haL8j!Kwgmz}L`PxM@9)2F9mZU48aP+Wg_P?p z{|-Kc3DN})+6FVns-u{~Lvc?*;7YI|Sh{HHxdQ;=x(JvIupdQb;?JJ~!e?*YP zaPs15+>45dI7{a1f(Z}{P9;_0V#*oZ^j)|io{`tq!~z-8{^`O+2w5$T?+V7zH!`N6 zqlU*O@s9pyQeAx{dc*BY?%FNr*eU;UY7pvpNTn20A@GV=x5kS9Mubu0?t5&=c46@y ziNiH!)?x1~*T(?s>e&xU`K{_hPtX@SbjGBKWYwB+85eyMN?lw)b!d2$O6kPCSui@n z#0+&i6q>FtiMf!m;2oOs+@O%)Xm;u0w{TkjRJ@3fB0VO{UeCo|lReqHI-pvFWxAxd z9JS7!(UfCeWV?2bE(?6N8R#~!H^O=VPtXP3RNinOg9uq9i4h%VuQpQN!%zMK}UHK!VZN)vv~ zk|wwik8I7r6QYEoIdbgL zB^)fSx^NL4Ogl=9>{pd&6>`hpR`)7i6%-ln?D#MvTNegCJY)^QiULSz2B+ymfCd3n z%+Z$wgqJRjZO=T-zYRSL+SB$7WtnQ>OY)G)xBYt#vPlUolL~~-buv8u8hyE+RrQnL zo!5-U1`;!8gqbg&y{rMRtRzLzfZaV+^-*zu$qh<1-IA!ul$NdO-E1ev#HR% za{X-1x)a$F3g44gqMgMoc2AMp0pEc;y1ad65g70Y5FtCOnOrw zS2R*MCr=l8{mmT1!=>Q^J%dOi?!XxAeJV-mDms|qGbe?(hkV5{my+#9ms!gPX0{(E zg2_d*JA#RXzwrWzDyUKqUuc4kc0!Q{@u<}lRL zQRr1-JhMLRpXTYr^Q+TKB~~7j02T5Zb?iL)oa#?-mu>QfD+9%_YSdXI492o6+kluJ zS;~muuU=-mm$=OUDt=RLos^xNkwvG{>Fw`AL#W(P(j%+1ZyOpwntSS($+-oh5~7u7 z7@i5r4>~vryhipGEEsUde*yn5#}7;a#yiy$OpUZ5GEOe(rk~u|BkZi2*>tBz<9BB8 zT*F0%XjWF;bwaPsh5&Oo0sgr#G@;}&r| z2v+d(3Jp!H?BG5q#~(P4@-9Bcs?F0)PnhB2Uq^8Vw!XpHuKZJQ-nQZ-eQO5d&qHOQ zFNpbC_Tcz5)?I`ivu2fDTV3M#^G$A@@LQO7vc=wCM^Pt!PKd*>O@r)=?utEBx0|eC z4TAI%>#SiW1{13lxS_EG;Z=-!B<~~_5w(q!H%l-ss2_S)%2~wfJ0t%L95YeTsMHg^ zD80G%8j>+G=bGljYIT&SveX*Go_WFKf-WxmDUMg{eMH$OAFIqP8?zI^v5!Uj>+?}P zl5l`Fd#Pzl4{tYPDSol6NwpW9hTy!o=m`~<%eW(;$$6~+pIm^N-Ut7@w7D%EIEtq_ z8b}X7MEsk^ZXGKYFm_n?5dJc|gGgO}Y>z_`cT-21VO6rlj=!)j`Lbhy5Ja}FFdR=h zuE|O7i+6+adE#D~QJ>~)0?TqaQW4)0O=K404H2Q=zM4^1WLwya~sdt4rTr4qFSg1E;Gl8i6fyob737uqw*vJW*aZxaay_{<8f`Y68 zq6oLVPG<%bF3Ymoyh09EW^K+jfvF(wqENyDyi^k5 zXzHhSR3DZx(*#3u(2Uvf1zxiZp_GYYPGc7ntxg5|FU zaCDspvTF4$<@HIGlg_}PYBc0yvSGt_o7kQY^GDA&?2cj5Kw&s9EFr68i7uj-J2dQH_Bg?1skiP$hE+P3U5c;np zs$t%K&;MkoeOb}?Jt4v1L%i*{-vdQ@kwxN6Uh}|z=@y>rrVQrFvr|Rn{52i2nd-vk znBit66KkbhXN%Lvd;I_xig1UkP(Q=iA>x+V)9 zwdmR*Og7;mb(Erg`F;FS3FIQI0qCX)NUld zpTm&#f7NW_-%)`C(kX0=p@3^|ALa1H<7z5RQ7-0X9GAkg{%c{frsTGH=`Xt}P$&-kujhIS+v1 zg#hGQJjp9-S)|o<{b`E@pkh{F*gylg^*si6k)As!B4CVQzA6eD$2I|4F}Xp9PCmbV zFnQhaJ-?MsHxlQnEq2(btMspNcNPrkWQ|_FXF4XlO)6{M`|BEj&nBL7)d);es~aBL zUx&u@AA10HW<1X&P@8zUPI3;YdiXUpDQ)VYuH$fp>q%P`iNDA-3~CTRJlK3fg;N6ED)c4Z(wC}%-+gQ_Ta49!V`^{{Mg19 zNr6nTkW44*zqi24u(#R(A8HMPerYPPx6t6D(3 zDI9pPK&}a`rX?kvhFo1c2vw7sB5g1A2zcS_7&HbrUN>qukD5SwyHi(c2!8U>RmLVw zg&XmD5&f&!u=Er+r$zbT83?j|L`0wKmMdW%4~7b5Hat`1{usj5@DIl^Hz*in?O ztn6E!FEwUMK=hdSNT|D3SFF>jQKb@2J&P?pUC}%j&{cL02{eOYu`#fC*L!)w@~9Dd zJy+u3+`fxO6bs1sEH6xQ>v&rbN~5OTdKoh_`l4c-osQB)*w_Oyk~scoU_&lu#;Bw; zn|!?$Khxdi#1$^h{s&WlF!c$1;4x`tXUd1kZQrrZ5yl@yC^BVo1^M}(mbSl><=P0L z+*=xopI0lGM&KPGg~2sP!;S(q;hH`R)eCLIel3%*kDnTKBBnj^XH?4~4>wCRb3jra#*|d72Nbge$_OkZeWM}`;-e7h zr6CR<%sNmA9mK`MR;5FnsQh(&n$#!?>8(=k1VX$ees335enncL`0AD*kdQKnK)j+g z?d^lOSmk{fgZY|PIQ05bXLFd@nn6}OHVMdu%PVE$TjGcRBfRe2IGxTrF?6@RkA>Y; zFz^kv@povGaUxD*>poVB1~HLadiB1T2PZ?N)pP7X+c?Y~H6`xOLBQmCFTu+I%{aBp z(G+)VO%S}`4N;%*7=>TIMHn9&(+$xeb@M64>LK0IR7D4Wd6c3lB5;+zeua4vyi+o4 zo6`(%>F-}CEtS;=QbEBVAy0Gs`56K=j}{kyb)2xZVMB$FKD4@Lc#^s?a|T|ztj9tB z%Yr7jb2*(){D=eu+4r(h2f=z5SA$FXE- zHFt&_kyX(MK6KiPh+LG)o@*23kQ8}Kh1#L)3}C;hi{YHWP2aPoemn8+GMs~Ck~fPHsdfOX%koW@%Lj^8k>bJksA=RLl~M--cwu7 z-QE=8wjaXZ_zOxHH~8t-%OqT#`a5G_*NC+3zZOZH3#eKrAhluv*ZZq4jq(~&{s{xq zey39}2&4?j^;AGm*|($VTsu$w(+W>3sk!qmEST+47Ol{HH67y5vbd=PtM$;FZ6nw1 z2!Qw@-+6u*t=Ze4PixDYW;0;Xvwm*x;&7$^7*ZXBOY!F&INf;BT{(Ut3Ol*myaGvt zMC?3)GiKhQPloa4(O|5pN#GzaW{E}WmDTiaC&>joKxl(;etGX8^Yo`snAppUQy*OmyWaL3-yPnx+Fj)rwDn`%nCtopB8{>i zlc%TE;6Cfr!Y%7W@})tMhLhAEHSR8kHk;od9}nF(h+o&)xV%H~VR9N3uftz(&X)R> z7}~0RIZErQ+nw~CIiDNZI6A(+ zi=N3#z=rZnhPp-GIJH}}_9#}ZU`fntz9ET-TqBGHv|}@`>iyWSD5mbRR7^x%n>Bx zJLW_HiPq5(7d{BwVQ_ROJtoAx@i5f}J25yKg%_)5`rzj_h@7QUpGU+&^~`TM!#9+q zCz9Aw+ky)K+jE8F-^`LSYL`OSH<(!_BAU>a!9D%qebs(_O3$Aaowd}xL`%CEC-MxC zfy68?iQ?ZU)BRC1>*BnnDJ?~j2WDws|25+An3&F#r*?Vt~muT6R(YgjKB&n&p0^}Z&YCCRM%{wzudvkn({;}<`Ch67zY$BjX+qsz+~ z_)R*Mf-qPg#FSi0aLEG%xAoHo;h@wrw??GtwuzBIJ>MDD^I2*|=z#xRSJ0cYw#Sqf zA09707`@9Q*pU28a;BjR{SAnMcCuMYK8krCM023kUZ7b&)}QCoUVwG2r%=kjX^Rht z&95ji*m>hJ;lf72NCZD^pW%=-DB*PO^W?lmX3+N4QA;H#V1?%Sq%Q3_LA>Ihf7fY@ z(}gw_eJeJHqSr@0t`T~!9h`jRCBR|7tu)A~$ys|0tP&`CQF7K*$rEYCuz1`=Y7+TJh^l$Sk=csC$~Fsz7~E>SOsM zmsB#nc1gRc+zLC83q3e_ra1sis8pi1X5%l`+GsblPtN z1Cg}zu+Ph$71L~k$+jKuuvEIg8sYFc`^NeygtABg6NAJZF}5U>;RAT(ghu7u5PRXh z__An<@C5U!)LSb#n}tJ=ThOm5!$Sr18Jm>oP5!8ku8JDKqZDz@!aOR*TPf*#`O6$#_rk+qUSFO6dhF;s0t9-~EEqB^?1>viNZ zBNI{pNK(}N7N|k;=^9d7YEn>GWG*Tm%1NeX$^WMja#|pT(&8Bn;1MaVcbEC@r}2T$ z@O(Q8;bO|qd_DZS4Rh{APkPic$M)B0MWET)paovf@tva@!5bbz=+!5Nw=vW?D;T+8^Cjy6#P7u1wm8Uxcfg}0;)WPY-jR>Pd%E}^| zFwMTS4Jt-DbyQfqm{3HzXpd8F!8zlI-Cgvrw*Ym3AA!!(*EPncByGTa{-Bj=hz`<* z+DM&up+t<5A+bvS?!8QBgJVFZzuJ)!vjS$)4whFOTxoEZ4E8pg0Qj3Ttr=yLA&IYD zW04&|TIjRi$**d0!=4z(o~7cu8ysF1cI4opN>m10iw)iLR<740F*(9^S@y1L$2J^e zWD_Ccf-%!a2831y2C=XgFf8QfrI|*|8+)iHy9nMfb=vY4iGzsntF04MrS;OH15ENI z-w$5XEXUd|=x>$L=mB3qRvL=WstYh52n%HT9x+cv0Mo=?+&UL6lnQF$I)+uRV1}yT zV_JLZp%1?0ar$IET-2|a`y%ctL?pms;D`-U8l=OL0xg+ftGU$o&C*g_V{iFWj=j%)6R7nD|K=kuGpXqT zNTS0K*2WEz_-C0Js;Ana*Ch@N;OT5&A?V}!e0TiT56W8Wo6`l`!1L`az(mE{e_K_K z9Q;qlVVoyqv>y#&&=|`tgb9{uo3Zgoa0xn5Ow7{xGD+TrKw7{k>>v9Y(0&(awMlHh z0ivG|@vfthgva~rn%T(s}z_65VwCCU2}CBf9M-Ae+FrQY}?3LMc7bzm9y9fQXv z6s<*|%LTS69c5j@Y=7D~>=yQIA*yHXIJyO%iMqAkH@>$1t0pIp+lB7vbL|m7^LLs* zQndZQ(h#!#nw&VhuGVEtP_9l#>v9xxRimi&y9`N#qR}{ianaaq2K`rjcIE59o~YEs zs^%-i_1k9yMgk>1MTBO|+&N?xkJ2pT43#qm)O_}O9NN^7uu3t)+hWVLwZ}+Q*+>FJ z8E~&DX-KM#y*74TkfJBtIyZ;i$t(D)F{H1)zrp|(@wnII0Z`hAuHB7WA?aKu3vvu` zF#*-05`qhTu^uk)B)|~*9 zKSlR(x3!$Y1M5S<-460FM`zGmx!=HL(A#|kT~zc?=_J4OlK?G1(!aek=bPxgxQVpQ z8g1OXcl9D4bkF)2q9Hg0zR&wMWF@&EY^jd;s;&gowDjmlOe;-qPI1qjnriTcGq+cj;ejTxf7f2%zK*(tefXJ{wEks-Nwew2MGl<;Pm z9QMZD{jb-R8^|;_VXxoHXmRD-R8^l2t%5BwiBb0Kkg9fJxZ36mOAr*WDv9(0kj=`k z;3ci6Je_SjkN&rR>HZT^Si0JHyHH2DuYxgPmh>}Qk3(u4PQKFAxbzKb8naQ8FGAaz z^YU6Bacii;hgv8zi2c8TnR8o#9@j0o6fy zc;ka`sZM~_$9y(e=~j;vgHwHIddxze3w{0`%UxC2epk6>g$WHfb(vkvQACgI- z$=hQqlS4*s>YiGY4~F$r7`9kDPLUTJ(@nM=6!*xBeNKQ%@DJ!WU|rDgG@@zyIF;h7 zGyrkKJ)^Dj%p7(LdXZx#ypBqrq928EjiU(pv@c!})Hb^(WK#k=RSW8c^4|HT5bb}C zfiDBGn`KjDP&X~!u>pAr)2;9hU%tdVH(`CfM0boO1$<~9uh&S`rjcZXb6%OVD%bp$ z#=Q?TEB6h91$YH*Y*pA!=Ts%uJ|hiGP3nX$Qa;r45#2Kb;xn(s&;lB|0F;zZpK-l( zsQenGQL)Q0M7t&o?M3`wAC!J%SKu~QLzoOXo7AD|V^yE*&QH9Na7kWinu{$g`(yv1 zE?|LnoJ`M6{*9dC_ttSqIf45AxBqRZPq2aL9M>FX-+0>WimSdZ!J54t?4R--MrJt) zo$(K9gr;MKcDky&f6{HVsYXF>5GVS>Bz+iTmCyuhx04R0}u%Q zTB+qq^N=DI6U(Ne-ensU=IBM+tj>Jue0I1fEbD8{A}or~BK64Ofr-HsU4dMowb(=G zf;00Ac~V7`&M~QbTd;1hAr-OgVFY==rlN1}ZM3Rc*A1@@vv~=*%3BUmznv_9MB*mT zLDGatFZ*L_%}iLX^sKI6r*$>H;h6;4uqdzfSL)lkJE6kGYigVr@|7uTb+zE|-F zRiFXZRFs{&;%{M@7i4MBe6grD-|tgA)4L}?VpcqxPy6vcJM)cl}w42teH>^he|KeB2HMXAjd!lAAg9eYyjhRuC!!KJNd? zj^@%%&CSXDn#X}1G@ z{H_`oY%(nKne5S6&upf%(+yEqz!+)xx2}G~gemh5#b}}PeP9PzHLHQr2DFt(%^V

k=6j>T0t+%+d3Xtc4}%RX98!_ZjAzwqp6Hw98aJ7hy}@Wdr4fSTKQ87Mqq;j;k)Hk4k)fsi$EpK06DJ( z!nBQ)lUHM_H1J{5a>xi-Q{O9_m~(_6<}emGA_O~VPzs@^_@r+Q)0LEZ(w2Jk6Vh&R zr%fParM8`@6G%9*#)0afDI1Y=>VCQ#OvguV`tC-<(g9MWz{PyZeWGH!1%y?Z#NQyO zCeyJIfghO=M4nR%a9Ge)oag6!R|jvAWA+bT9fy?bTAwj+x(TbI2ELfJL?s#E?9b^r znA=6iLCHCHGzX5>5EbtJ4*Diu&lY$xesR+ZDi6SVf<-9t<0`#8GXH}!@(j-5F~(|r zQA)3A;gB~(Tc<2bVv#6zY+FR2no>hKS95^5MOBkQm6+-V)pUN(N2^bBirqqg>|T#2 z6Yy6CwPxYoh8>c{cBfOi-MIyOppU*a{eI2aF+nQ5n~2!NlWH+UxdM}F`PU-THJEHL zs-8d5)RajeXkm17#9g*Ps1CQHcm-psqFm^p!w7JM)50LBx2qj9Ly!GG4m&NZDOA_$ z9au>4tJyM0$nLU!tdPfC(5rN*Rs38DvsZmkB>le`#vq^uCeUD^(P;Mck;C zq{>QUGU7K>LE(VM<@DzI-c-1ESRy~+*0>(*BMBEPeBuT-m8UT=<>9xg7J|C*&g{M=5`x{-uy{*O50GM zF*;=V0S5lTpHI?&algQ4aN$ud1VA7)UU_cjbx|g&XFPn=%5RrzN31X6H`sggqj)wy z#YM=iWTvAQLk<-A>UvJee17s3D=Xn>L09z-vh`@U#LtnGMAM^}JkONM}yY2>Dr=PnmZp38JJ zvQ$hi0F_1)2neSRS2@J6dBSI-gP2~Sj8SPp*n2dX1t_{O{$BaHGx0d$ZkVGcH|8Ev`L z$ofwt*nga8dg%M{B{3wY`K`#!dxh&5_{uz&hmT43Q8^>^1$8f)p%Gn)I~c5QheTFp z4!g^@&-GoboA5hlz1c2MX$a%$sB#%gyue-6J2J6GxIBTbUKPcjwi7_q1k5Z3?doEZ z^+pA!hKpus@!?`2rp zGw&M#fWf*tb=y?bWD}zJi5d<)8S7Spy^$ip*M%7tXU1k$dTuen7BveS3LcMe{o(8y zNAvf>6k~q-O<0lZ&Icr#E{Trdn%v4FfCf7b26)iy{&}hf>eso;!3rX)K9Ev&H``D z<{ZrvAzeD2G|-_{Rw)Y66PH2ck#H`=1=8m8wnM{{al|)P7&D+-2<3r96ELcR@pRnR z=BDAA(r6?Qd@AQ{pyHtR;9dHF`UuuBjbMp)yRloSS-Vs)3b+sdJYzeM^~f)Xo{wnLTvfy-N)4&EcSoZlf`KlM_p1F4^69hlgbdMdZFX$ zh**2$DyzC`V8QJ7CT!>9s+p8@tR{b1Wry1NIY8|M7X(&WVI1lGZR09O%weK=o$3_s zI2D7EuXuGQgc#o$&BJ!HgFfd#b)z(^x@lG>gFswvp`abk1}^SCc4H)Nb9_5s(K@9? z&8vBklhE93KfKgXjtnp=1y%k#3t73S55#T%bGi#f_~6c_1lgWm6(!bK@_k=krwt~# z)L1Ualp5&)_iwznmUrXM3F_{3>S%7hAM@O&uia&^Q}2<@KvYP#;>9u;95e9CK<&ty zz6Sd7uJo2FBOfEOroX~&L9kGtx`Jd|-t_&*`>04Bwkl`2-FhV)+iNQ=u*Pa!OtNBc zG41mFEOvW(cec>DF3oXHVq|C&-+U>r8c@3wS1F5N3H_Au<~9Z2mg|4c z$rciQdAWH7QIu8i_btM?-J`j)c{QisV`lgA7IoKcZ3&i47SdBK?Xq2e#pD3|O7v$EGp2iJLl zqKX?^7Z{=Wen6fAXbj`^mB1-+FnZmX_tAfv26dbWFtAF3r%M=^fw43DRs^-9?E~t2 zP^}4+hkr7VoT7CFM1aoIvUM1UISsmndvS)8-lsZts~-a5c}imhS0i>V7w3o1>oCnLelkf<9dLqgXmU&nUS2= z=8*5$aR60iamLdwuBySkFioOol9L6nVSYX1VY;_(3Z4@Ld3wZG7t8=_qJjyxEimp( zeo*CNe57NW|KK~&CN83IYv$&HC)IRJw*=ErNKVY6k9CF-&#h7Ng(*`BbZ>s76iIe# zqPJM#YnArg_KsP&W{ygCJcmd)eFPqi=EM`{6_12T$llzr-7h7!PNiZ{&lD5Co+6)usWi%-Oaas4~ws!)s_ zb0Hqilej-}bR@S-GC7E=8HY1S!uH_RmR*r&4O^>r$-~1$q8HHW=t0j_KTYdOprUS!ogsdNtYfo1mj~+ zv-`L0@6;qScx@EfwMi&l_LYQogXh54)BSv90bz6q?r_y&P8M zw0bUSzq0$tgY>a}MjJf`+r`!|1V{yJpoP|YiaNgA)iReH!vdPSkBJLZox~m-yW$pI z=8FSjM(_1+%V0Z5Z$WZbQ&QuuoSj^l(bAMvVLYk9AH^r@iez-6Lw3*zV&9Kg}sUfm^5Cz@=dK%%7~Lsk9Yu z5fD0$Q3X+N7~Ki9u;YpXa+SmO*?7;3se01O#E|HtWp6H{;zYBDLVET<2dn*=iWhl$ zeu}PV>W4{Wq3k6NG4o5sKX+|?fLGy#sJV$9@=R~0O>IC(q6#xx38g6Dgjs_{|{pHqNjp=q8slP}R&#t~zv!S1uqQ5}D*g4R)P6MssH&%_F3~4>l>Ilb&`4m)kcevUniZ#=4Ra z8jQBL&OK9|LB&Bd4kZLj|p1Lkr!e$m1!YOqRU~-@R)o z3Sn6dE}~r0wmvYHvGW+ge0W+lzAYr6HUfp(9(2)*l}t=E@U+o9pRhLd-e--w34j|1 z8O=7~)ICCqloW{6MsI5Wcc_4PI6HqFZSL~Vei7_Z;`paC!Z7fO!2?1(V9zt+@+_RB z8ZxVEg=m98Wt2hzQZ2F1he>jWU%{YhPqP|bo;)1&Oro-;MS09JY>A!52jWp5HgJDD z)FuK3|FkmV=3WgbVXs)V!wqjj8ZLz0h+@={?b7>nTWKXS*b_n1al3;D1-=;C(5ajv zi%1C0pX1sNbUZPUwHcSIY+4*9(yPz|eeAfA(Hig=^eZi9%zkR zG4;%*4f;7=(~G;ai>o^)ug9G=SQ2?SR-nJ`Z2OSL3?j#%4x&6>@-N;}ifWGd#eczE zrXPKe{{*%h5dc{@WV!bW`398AB3T`}lCOk8k&DULYC-jD`W$HjvMnFXr_3`y>i98E zsW+OngYvmY4+@%XwaQb`GujaO);n%~>ZgfglId)6=cQhOh{b`r1GzHgVxu|G)&EcB zV`?`?I-WpvoZYu$~N=9kQ|$A+!%59ePU4rl=2YH(Pks zqT;+&nQRDwVQQ^zn4sO zwn#LW5!yU|zt16I2Wvf-GZ-9*WR^Q=R`_0XgoJ(vO=FzC`$AP)+Y>y&nxZgE2Ydt? zJilS$(khxkwSJ3VpunA3)om>`?vlWcQ7Gj{x+6F2M$!grO&4Zmgt0L^65{jK6hlYH;{pTRo6W zvQKKv{V0{DQFx!*K|rQf6LFkI1*Uc6O+hqhoCOhyHluzY zIk#M>p+RKjDsPdpea|&Qd?V0BZ_G zm$QTd*%y)xCg#w$9mE_I;+wncLEg@}Cz`B1b3alNS97s=2T9lz~QB3A1bwS#N zS}uDD78B_0q-}8M2P_`fiu^HxY+!1M@YG)JO-KsPhH@&&%HjHo-*Z1m$0*K$iXmMmz?7IRRDs9jSk_ z4i`ds0*9&GIE@m}uz+$B5a^Cm(g@1T}H-|4I?0r_a^D-RsJ?aX#us?(0O_ua5i;6Yt*ZcJw17 zew^)EBOhjyIhFPD*(J}b$8v)M)ABl_@3W}yi_PVi`yc~esXj^}?ai_s6*LcvSFJq- z%VY|~4p;(Bl9ybJTmG(;0<1MKK!;Mzx=G!)W;13etM#434Zwfp6bBP*xF;>>3+@kR zv|AwS8ru*7`B=D*uA$)5IJLC@Uv57E>o{>HJwenvOa=v=Ep#%1=wU5q5IAczD80L8 zeDMKTVNUh(Bda{&hkz-NTYkA>W}_0DnfvBl$VeA za{;;q5LpWpLfLWTgQDnx3h(1fFh*y|iub5Z4^&+TUG;r;Oy2B-vn#8K8t^97w*l`f z7dZB>SczWvBZHm8%{jpi7n~qA6Z(@w2HA>%u)Zdu883ESI7MLsxCFE5`?u%VoMQEC z)gOQNLbKY1$?uri3#588O)&YJ1jiG~mjR~W2Sa!FdDl{Gm8ccbGBtOP27Qs<8aW73 zgi|veK30MusC-u1Bj6K(<*8O&8cSkHl`MN4E^A3yTb#u^THuQ+mtpibN9$P#xfCG! zpKy&(NCFoJl$QU6ia@$Up!gR0TdOxQHL{foz9Fk9$I!#_ji;*nOf}%-iRFdm@j_j8 zPMcmcE;SXX7hc)}r@ljv7AL%^ zStFrg>(cz57#^!}qZD#8asYQ_!(u9Kvb-hq;$wxC7#fFzB-Q|Y__XDzSgYY*yQlEo zF7{VRnxd9pN>X<8Hgy1>N2wCco5p-!|?h#1#IjExy?M$ESVHMY`d zI<}ymyB|6%{+yxcebj5e_3mOtEYjAGOR4tp78KFxkV^)WeL|}|xXR_YxeTY*s2Gk>Fx*LwL@q696tr%85=D z*vvElrhu1GKN5K40fq|t<>*Y<{CjwrL&F%iyo*@v?FdO#`h?^pNnoc1c?50@O5k*N zM^QKnKXz2KEEK_{>nX1WcW>t0sizc*@82&pMNZgTJfG=(4ydcpLxJh<1ay&DM@!GY z{RIPy$~>a2tNw>2ISxZd+-s|17dRhY*Qkz=RZ9g&)hD87+fyp)cGMK#28OFayveJT z6?-jtIHQ5Qy}A3O0$Xe6CaUq9%ACig_jM?o2+(eWhAuA2PA~E<&c~d|Ej94!`j5cU z>$$tSb#gaC9t0o?cvou=j6#0q*3{nwiYs?tdQ~-VbD!s06dd8!+tdv7B{*t%BqysS z3fy_CJq}w{9|J}6S?Y^Z^`y&iil}a z&Bnn~580opX))#tNY6t8g-8VP7gv+5-!g(*0BMG2$*9#K{op!p0$!_*x%STqeWVq+ zcV9!3j=nfw&Nsb~HF|kI#=r?GSk)n2dyo$s48sLyV*IKx&eQ^039R-55lTrg;oGr#S!^M=J+@?cTl#oA8tf== zp7ra2jF~h)(a`6cuUkJbJYwB3T*c46w%kFXq*uxf;nBl!U#GMKa)$TBUT{)iHW)?# zoFAQVh`SwPF`>N$5W`iO^=@OR{BXA?!?w1!{&tMI4hi?emPgn=TY7-c)6vhagmvj} zV*kvG@m^ZzA2P_1tb=0w#C9#KU{`nE(4?|_Hoz@pGX~zFnL1wBl7OF#izw|z#S%(h}#w@y}UITDE4!t+W zZP2sXHE==wN2=~=ms)9yy8Ap{;=D`wjqS$#ZaOQ8*!+0GyOIrr=Ypy`zA-m!WyqmN z^m@~0TrVz9ShXIw3Lbae%rP zNCCvxaZlf10VW=1w(J7O-%weF?zJzC*Ed#DDMh3lB0^eJ=G#)zN{ z_qpg3wS)SKoFahZ#o(f#rm-;eh~&Qe=W zzYk$;{6MKkv=Eca?0k4GYJy=6YeT>uRCsaNM;g!;Ax}S_)P`?2$VQr_L{0}^5wPN9 z7*#!tM05PE0bwX-%^K%hddB7SkgY-F-Myj7xMWKwhTIiy1#Wa4!=)G5zAdjET7=tQ z02)7qArEa6m@+a=@ueK;bmRu?N0kHPXS~%tb?N@K^>nCINvT`~dgy`a;p9pe;Tb5~ zY)-;{Z3JM)QR3Mt*C0I$$fY>Umod}Md;hCz79!HCk(iCPHw>1LeV8Z;( z=+}35(IyW-V&@D}N{Z-V(P;Tlx9&|gSgtSYt!e8)dT|wGQ(SW|i1D~0(R6KU+iQ`W7`z5rY zV01Qa-%0NVNcFcIUcFqow>7dEU)@R0wfT)k&=43xl z*{5Tj;b||Au1zy3Ym%u?J9Da0B0Y8&#cR+IomLs)Eo8GC+y|w3V;5ynD2vqxEES-{ zwW2aZhmu})e_sz{anI%3H!%6S9c$dO2QO!*pVKOik;l6jzo>Olw*YW0GvPm;8GVW@ zbFdx0Qh0$umF@yjw%6|sM^jtfO4wJIocbyAg<7~evb0$?dijVt95$$5tg<}bs(oQD z5}3lBqbp)?oI(dSMC4L_P#p%OGvMOaaNHVy84C|yehcv&&>+z)kE{3ti9KtWGK)Q7+aNHBpI1u-iW6tt?IZM^&v=;obPo@9!~ zvAr(la+giICl4kNaz_#ZR5ZGLngTy&5#KCdsl zOnFK9xT`E~wwO@)ATs+(^X5My^ELLopegv*ZVA*y83_H?;gzGm+IVko^rE8C%_igC z&$L;%CHWK-*aa=X#67`Y{gCT{obpYr2-2j9O>zQ|i2QD60e!j$FMx0ddMDJ+r zbFzKMClv+Zn+duh2a#u7Qf)YHon$~qqEPN_S9%tGcM%Hj7u+-1upK16>Xr@Irucx# zq(g}~yd?hVH#rwN7TN??qUE{ds0>tyM5pWAES7A%fwrSjM&-rLI-#|?FxLrH^`qq9 zVmp&o!%ReOVd1qP=5)?Vgrs4VB>i5TI8LOC8$okgUhGIv-YZ5CSa`18%>#?b3mpR` z0eFlXq!JDEYwYnq)1T~tn=IVouH8B*R6YLd!;4FNYBm<@JmZk7$Upv>q)yETUfw{I zlu4&kl~-@KUB=P2!M zy)6oN;(2dmVT+e8lg^=9$jI44$z%+RWZFZ2aht&j+qd!;7Bs3Ebh)*qEcs^JS^KrW z1{kYiZ)r3IPY5Kb-FNjaO^J~ZkK5)AvgI~yMeKVcSHS$t|Fkp9i&k^9%Kp?9E}@!^ z(#TVRDFOe<@uu6)O=-HN?II>R=QUe!Uwnexeielk5im&FIO@**%-R2yoorVv(WR2x zsznqm{lF*1ezhZ8ME50bFw0J4^oFLgnNh$Z~%pisy%zO_+UH1NwaXnD91c0?m# zU++DU6TcVQ<-H@D>MzqK?|Rb=yqg=yL4h@ znO>Wej66xd4Ar{X!6uIprtX#m^GoqU%WxdvzlB)j=!_GTS`%F+_<3ZkkH55QG+`i} z@c}vmnmI!$F|2RkQ{R73v7hl0(MA7U(9&v9R%r@(Mq9hb`e=R~;B#O+m!Sjq;D#vi z)dxvw!bAw0_OKB+P`xasp1*0H6}P?-`$mpUDIIQA@&}D)$v_qt0jESVAc8N9mJro5P3?jjsyd;9}j#V&<~xJO6w*U<>>=&t+_#6@&2w zk>fqS%llQR=OJj^p$gL&<^(4mOU!2;N&8lOoTNf31%(XfBdNFm{aoO7xPtk!Auzx9`k$6lS1t5@A;mHrPi2Y5uT(hCyZm& zi1cOZt0RxzS&O0dIQ#v@P0}g@$W4Gmzfq=+t&9@7r_CHwyY+?E(D@mT;VmI!jgJS@ zfh3OxP>N1LiE%^xjnk_(9h#71uT0wD6>BifFKb3&uV*3O6xyZ7&0J_PDLhxi7#T4W zSKRz48)EN#k>#LF{>dOCQTx@1^Cf_@3=vLfs6IfN+?CZ74`JE7`;pmo53RcO2_63h zQQqQYMo9IY5kXtfm=5atW;R)FA}Y&4Thk>8LSPbO4G*Q)h)~UXpHLMXvs>u2Q?Ahd zpgka|AALsybIsv@Q$Y+eqCA0~xg)Pq{a^E=1cP4c1>~{_2IS_S%c26ZKvalz8X$i% zA=%JI8IKorxm*Upq^k?r5TH_%i5;INoq%c#V#w2R_aHl6Odh;%PYbDE&vqS zM$EhDJ4!zS*CY!V3x#tA4i-BnSNZMI`Mv}0;FGv7+kMb25D^vmb-IZA5$H!YO}%f~ zWS>-?ya5T^9GC&`@g){r_dAcm7)OewM^F4)@9Y>Yf*e3-{$d9y$$Pmp^L%ytj~clX z_ZL9tyYoj$lZxC^OM7;%u1Fb(c;lbQ00Z1=r*zU)c5sZejR@Iwgh*%^MrZFmi0Wz^ zU|6hLD=%a9fHs@NzS&Iw+5gg-bUaKpV#WMRkJGkhO$rt~fntZtlAamskc(SsoM@g8 zj>!Ml?+-(yf_!z+Dyck(&L^X#Zp=|H1Kw}VEtxBM2Y6CnWLV)nLE-@K1!e`*9RrUz zWsd?@kXf;+hFC>w=8Wyci{NI6@RODC6ovt3WkfyV9+!KcrT+d=(v)q$(#+yfrfU=%3obbsY zK{auhY7{~VjFYku9aO3*+5=Q*JMOiJx7o$P-~Co?f$x29VSkHzHTBf>#wGKqeU%yb zJBgAVgFAmgjgoM!nvtstiljVAF8>gRT?LPf3gJz}V26|8n-}g}HC|9eDW7T*)J=<% z!(o9L1g8;p`V{k+tubelBC=ZMB;aO(UsxQ%TjZmHyQZy44GJjfib7mhvr-D7=}?Rj z{XLC`(%Ylfug=fp0{6;^PIMzmrtx;l8RBp;##Bj8zzBR zVTO9Q)Etnow_Bf;?4*`f`gIyF_@K492uc}3=RaL!(3IUTtEG~o5lo|Eg&f4gJQ-h; zX1arX&A;#!M?yzxEJUP3!b>Vz_=(p9(Lkh`n&@H#iCpC8ZgI#k7=vEkU3oz`fg<{=v$Isz)(p zK|f|%D&(v4VW#ocxu(`=HCn}%`!NoV1@aPOLNT^W3xG|MdHs zsjh$+)Lhl+s8>rsO!(H)ue{7g!hSZeji!6I4mqfxCrdwm^scy7h(O@;DC4gN58rRl zWz3SjJE{}Rf7=zKk@b}t>}Y?KmC*({?5eHOx?zklq@x{P0t_13*x9|}ZsAd<->>*r zzk=UZb}hmI~fW|&1KcNAI1qY3CqkLf{O|8>*g zo>0y9oEF)iOAVtu2@q*G)R5D=o&u7;btd8~jq?N_K1FMoP}!{Y9iYcAhNAh8GfeY{ z-cpQDlN3v*Jw!08ckfmal0B3fJ_#G?qPLFs|Bi1Tm4b*4<&HI|Bt?rfc3mh>by6WC zf(bD$hX-kRwVkDv)1C{|UGlQ+WFY+3c=8Eg_jn!X1-FwDGWy489>#zv5ihTeiqrb{ zt&2y!Eh!yi6Mg|a7+nT z8uFb>d||H5n=6<6I<~mW;Q+`S{`vVO=4ZuR;hXx!mWR{G_rml&(pA!M*cF(}06*)w zcX%;f<*gTAOF*`wS1C%MhUentth+Vy6m(D5bnV{N(Mp-HC?iUb?6P;COQnCs^H~3s zQneb?(HDX8gnhO?^~ZvW^eHt*7+yX8$#O(UxzqVuKDKcmP#0t#PGycE3Y?RYIe(Tl zi*y2`8UilKD>~1f++=>c7t{ia!K|eUxu7ccF4T*HLH}AP-CYSp08KQM*j1M^PgX`F zIg)ZnaaAvh%>{hFF!m<%CTC|Z2U6m)$$3qTl%REZ&d51lujvPuVd_UB zJ*0g=xZsLPIHb1^*2ImWT~k~KrZBIFI~;W+ZYy;o10W`x45L$nwv#Gubj7-J6_fK` zrlA;~Nk|r2EWc4odcprI$d60O)Vx*48-qD=TykBa<5XV|;Z~k9g!%VBM^B0SlgiBW zM7ib?6du-TQaDL95_i~yq4+a+vQl0$y3biVmL3AB@aokxy#V{CCx@RC)5GF9qMF=O zdcz~eS%P@Rl6+fF4WV`-qXu@(yG3vUH;zvCkse}L(%UL81@TZUw8! z5DHagmA(fqLQ$2kh{0R)yBz#-RvY*r4a-66wXr=|-#Wd4=Kf)rB^JFtSn~(DJnfTX zK8~QtFlc>fquBsaXZF%T=f2L!R@jvuZCud4-=0G+Y7*YB@?CwZB_qxLN zr~YplFFx#)RBRt-RtOhptCwcYq#iSXeU^-&@EYQMFHftUU#C6@&8=&WOZWPnF>eYnpM7@a~F@3{90 zqx7cZ%;eI|RDmoS`qzJ>J-mfB<=A7S@}K`UtcRgmcR{Y=zhPzf`}S-XNwj+trC(yR zcnWk_q18Qf0lOWzlF~aAtLYsf)l_7Xmq3(phsEIQKs}J%ukzvQG zIb*QWlq?kWaP0<73tE9%E@l_-94YsU^DrU`$e*fiElS^@w#@g&NZ+vn%IV0Yyr{6; zi-2WEZImmcge=SeUO0qu(9;X-2nNqit5NSWb-*cb-Ke0wNHo)S5cx5DPjnKMK>@56rvNsK0L-61zkM?`iOx z2}sES6_5tyTI^wR@@}Pg>nTCvJB&+if{WcGmR2U8gG)xOb2-Gu$?0K{fB6Qq8`uFG z?Rq4tBOXxv4dbPcOIv)Cjwy)4O(ls>Wa<>*gyo@K^bp(xUq(3R&%Z5TBWh%G@|kkT ztx>LyvSkP}ww>D}9U5RH)Em@b> zBUIT6Saq|ls&d0gMNTrPqGGOf+MHMMXN+TN=hGU(T&1hr7R1UByva3v-t*I)in&#O zy_L!9UXVb_(5)KlG>s^pSJ@=)T38GXHfK=U4ULYEeT!>;;ar^w#EL3_c0^d*m8$Gv zWrp7{0{4hj=fqdR&oxZL>>Xcjx_4NbD|jqI040L))yqOsY+gHa0q;fuet1mUyGFF_ zrRh02kCz4#PH0QP<+YwuQwL&?1f9=4S4O-+58W~f(Eam|&7C|JUt(fJmDC%@m7rjL zYE0q*%NW}R4MluOTHZhwo4 zw{Cw+^0)9ottXymH%C22@8&VemJ6TX=Y%eeddRx~YpD6Y7Dqa#hp?F}_;t?s})p*giXT(@*F&bks74 z*F5(BR)SH9)}8#ma%3!ms*J({w&QPvp94;NTVn(bn4BKx?X!Sk1iRlik^eA*&9eTI7f6jZx;&A*r=Ezg)bdJWCH4w0AG)O8K63a&ced=XHx98^6lk3iT z0g-fxu;n*XCjX8l57^s}J)ib`DU6cHSU^OBdhK_y1n-vUOyeg3_{)}Q;7lR3>!;Sb zA5unk@{x$Fh7X$jqCn3PDV|t9>6%e21X*2kVORU-+3UeL{nSFHI3e$dIK5rv1=|mI zRj{e{W--tf-{!}f8H&KOLcf35^XkPIE( zB~raO$8rywC02<3rv5*5`g)?@zu9R03XbQ_&JA2v0ZCYm+vq@%<3|3W-or-R&_QJy zaw#&VVaLD-t=zrcC!j=ofyK4PqH6`fN3gQk@ynG9>S~nWpqd49iVY(y0fiZ*z8vZO zcl2mlYgVPv^;;t$qHL0pVtz@FCiRg4vw}&gXLX!)RerA8;g@+gA(s{uBf?UUWvr`_ zTpGG$B{NCi0!%=8vE-S(x4< zP3bG?j8}7|N`r8H$5ZQ@YimauVM(x8WXn`b`Wr)}g+CMepn(sodkSU0dJ-R$LSSp5 zx?g2dd*gH5cPJ4uJH?sC1#leJ1%owq+)pWrId*VSXG0d~B#AS%#k2trKeX_*5tpO< zP>#PgLVbGV7aTDdUQ#&#|Nhm6FKeCNL(6?>#x#=GVkC1A)G*b=ai+_C? z<56D}pqpibjas{(I`ee2eRg{2j>SDMPf!MiZzj&c#L|mIKToIH_3F*6J{VET*4007 zs;yS939M-im=QnELk(#{=h+^BV_9|=#vYrFW=Ea5XLXtxH{cnz*!K7=REAv|CwEL; z=w0Y!R$>$x=vee5a>Vkrk+>uwdx1pl$g37{oOh1}SM7y3r+t%?y**~ZFok5oM8uaX z$-Cxab6Jt|df2WCp!eE!m`JqKToM)wo!G-kwl~*po`+NiHP%HT`$mMydOJA9&HXT11YxOA`*(9mstx+0KD0r6TMpk1 zWA6B9ARy5rkJJ<5-aq(nhiyY7NA`ce*!$O-fcYw#@DHbXbQ~_?&_P8W55F}uF~-~+ z3-ESe^JYGh?|AU!f52SHJjd(Xaa6sEe3Diuw(PkX|-WVL%l1ct< z@jOqcy%B=CLgY#I3IzLmv3Ne$IG8ADOmJavhgtdpbvAk96iMD$E5e)fr6k_^(umcf zDMAw?eUzdW4tgEn&lcgCow0`qh7doBe~iot&d7bkqx?OwqwdrqZ1~imef)D}o5C0d zSYQ`K+PHuqL75y1P^GG%ZXE15N?oQuG5oA*|=zh-fC_*@AZpA>=LTWVitVNlHJ%*)3Dy+FG zq`&H?lvyF+ODc^P8+dR&Z6@x;Rk>@sJ9wq1Z_pQe$r!hS+6Fd>F)Fv`K1dz6(`t3? zu$Y1}RBDyPaD8sAhE(58TOV67+1Xt67-~c3UGYmYQxi&G^`YsXwgh-wD5i70-l$ia zqJEIAw34rR=w(#fWr6vx|F#W(flW<;IPa<=4>`7>UAXVlx)RpandI@86v!e zMh)VCpG}y4^)Li-5zfm*3T4ro)};GD>e8{=s2%Q)+Aef?r`4O>NNNZCouX6}MS`~- z)A$+q@-vA_AC?U=S_fpwFGf)vDpsI}v-T8FA-9&WZS753rC-{39~%=T&<&cJ_(NLCfFA1sqkTi z4gHHJGq|t@kRF1O^1&dRD3}>340BE%IUQD9%;00m1nWO`9u`5+;^GCQII#(5N>Uyc zu)gM*6uomycpAS>dt?o7axcm&5x=l9sIV zcLKB2tpR`rK|0dN*DoP^w^;ks_~b`E^faLI&`C>il3I>xkwcojH1|H2K@FU-r;-?j zhDcX<$Ld-?dk!3Whah39v$T3GKIHW=3;T&%BcZN;%f$^!rI8$;QT2EGcDlXYXZnP5i-)lssr zDt?2FQhCVHo{*n5Buip~fOgY@w05NilJHP!L-OAkh1e&oDcA~edNb97OzibghzM5?Iy6m!(`Mb?_(qfdr}D2SX85fXs$ zMrqk3M!wXVIjor8(#nK}#H*_9LIQ|W!yVYfB>kF`-@oCd^eza8j z%_ov!Bycgeh`Sm<5e7L4$mzU_HR0C_Y7pa~N+Sbv_q8f%`kns{m;{PRj1dj#A{hWe zwR6>xcHWA^7b_{J9O_vsRRoU}NGx3kNy>4|5eohhAbXu1z?VT?nltHu299-k`&6m) zbL}QT%uPBd#}p>D*|ZTaI6m-d{v24ZHXZbVHnAI*2qL+PfiFrin&e&sE(->Fs>C*FfFNF^s6`TKG0ph*x$v(aVyla|-r2ae6 zkTpVe^k$S^UtY>(PZ+tZ9EDnhYqLq}WhhArlpS;fSRYrLHnl+P>{iIKTQ|0$cZsmb z$C#wQM3(de=yLXIzi5S3awJW+88}0$45GZFyLP}X%`viXG29pu6o5~+W$EOBZ^Q%c z#rklR=PIluHG@L?QxA@sO zvE5oy)@f6Xk>)cejeRPDV;ji}G7j}uH(&n$s>)kjzPaUuNJuI*T$s3{uFbq_JwiHM3ZV;j=XjmIg?U%e+R zA(ZMr=8t>Y@j^f{tDD+8TcpT95=Te&(y*{1#FBM$v))XFNBpQ9a+T9SHAYjN!vOEK zk^k>r0Z2=Fh0PWNuH1)a-ouF?_gI4!bms#Ikkmp19y_n6n;hRX;>S-^ed4@Lq1=x^#$4(Tg!D zVc7_1skLtbC@vh&rN*+Zj6N)w+t(S}R&0w2cOYXY)+)LYKZ>BH4C_$282hk1C=&^E zI)L^JwXS`8`N6_>leN_bePK;Gcj>=;e74UmEGLwqtMp#EfAb$Ub$wxj(=xQUG!VJ z0Bu1suE@Lc=&Ty&m?kOV%8NL1K)?Uh2T^IHlKPRD{DbgD{v`NYL}W-QIW-7;(eNb&f0&??k|*4${OCmSP{`Yf6P=x)xZtDS3*v zben7f|8(Qtf>c1jI`GjmIhaunp&`Hrz&l$mZYS?Nj)Xq_CT8RMT~xlswyvap7RpQ> zs{ar*yv*CUS+TZDuS6pgOF2)Ww+tEPT8Hf$MEkj04=zd>=acrdoMja&1&W*aCsSTo zRZG9W3dkt51#7>y`!|2o$u+$Z5SWd4Kj}EkpPaK%z`X3D2e0;L%nM} zUR{8_IPws~SI2G*=nMAG1u%yF_>HO%F=IDC)`#OFCbW&Hdo)p(Dc|^%Nm0L4jK&OYPrpVB)?48lPmnFiCOIS z^mq7AkfoW#UD=kEiFYvM0gb;9*h}E?cYYj&7YO1;Z*tAib<1h-A|THeQX`Q-9vi!Z z6DNNCg(4RQ(m?*;X0-LJ;%OV8hs;2rF{1AY;udV%caU5mFi--nw>XTbspV^GWbz_| z%S}MD*l?zQVDY)x9&*%9X&b8);3A?kqf*`@izd8`>foEb)@x($ek13D`*Z)s46j&3 zD~buG>a65*l==RS5Aq6fOOcUUIrCsniSZMN>y17SKVs_ z%HhwxvD?c1^dEeJaQ%n*ZG2h4MVd-NHHbj!+Yg*U5%&YPTF#CGmY1aFwd|J(KngTdl(c#j z=Appo4ln0|hT=}(LYsh07V{wPkFrjf`d@@SG}L>c@)vXFeHrUX<8V+l`vPryg$Xb` zS378+4X|WG6>m*k6_a{c+VQ?7&Gt%em=-8X#caJB;aKd!?l|=lVK0nV-7{dkqN(oU zuZZZqXDjpH<@KEQ7e!{Ujkk+9)_(2|n)u@7n(WhdE2V=nygJg%U%I<;Ps%$zibhQ@ zi_JYJ9*`(t!107I?g4MW3Ix7iJ0tR&rrxeFxy;?5wJhP>vkTWmEg6nRtKLtCFxiAD zdwv+^d1mE=&0fG$GLcsk4~qFcF46j{3n`ID@mv`CEmCaIJUf8173(WE!-bDI0)OhnOG)LRzr#QKoIC^tCsRbi6) zhjDGBA5jSI%r|N1u#l68fyb7?h$Ur%RX3?VnPeb)9u{dHh7{B&9q4)TUw^fZB$106 zCHAU*5|rN4!*pU{^S((olZsKd=osxFj6An)wR3y1xpmXyYX zRSHfE2_j?Lx9wnj=lTc4h5aO9%*n!IcV(NLefrWfYKsawT>nM8p<}L?gTA2ZB2pAg zVJ9Soe3MkRa~<(xLN~@8lCS0+{qWr=^s#Y6Wu73^-T*_=&TMC>$K=jV?X<9iGXbf= zKKQG$CLUSQU;Wy3#yk9q5Kh+OrA>8v$KJ-Vc9f_3S)=8f-!)cb22pz(e980X> zoD^~j*|E@1^Ewt2xe#~8ER6~KCGSbIS*ti7{JV#)42{l@yNSZ$Z>!Ib#g^qu55}|$ z*eEaB3R9pMS~jC-{mDbSB`7o*@+H@Cm8M)K590dGm-9(Y)qAm*)Y;I0VE!%~q1xkl zXpgPqdg8B80p3QL3hC~d(7;=rT1B{P;!3Bs^Ck61$|T&2Q5qq$fTmy34#x*i`nQ0n z>*bbqU9>UbMR${1fKfOE_%bGX0hBao3JWNm=rmNTs&dB5zoeht#Ls3$nNMv;jN9w- z3Uf;v8EvZJ;4tx9Zjp%-g-LES%Dm|CCpyWwP%vlsZta6CG*aa-J+WiTwF$hF*3xE+ z+&-*h+{CXf2P`F%f^{U#k)YP zh6<)E4&;d~Fbkxgf<$(kJNPUUi|O)EyOJ`9uCwu~!yWo3EN_L+9b#n~D&!lHq#E*= zru)b>$g<~bArc)7f>8)HjhJ&(bIUpPbXBg{iVeoRv&3?AeqZ^JV#8lbsuQ(2Bb@BA z_j|%Ai&70CZOHujbyhh^Ej_BTMYS$j!VrWuPYoW`sy36@Z@TPc%E!7|h<%^)GSa9x zBmO$t5Dt9RQIfwV}u<t|v%6iG%{nMVF4gRAgEjG_*S;IEy|NF&Pz(OmbWN@H|{hP^v55 zRlfYe>M1XFbr8aINsDda@B=`y?ue^VLjh??7{F~K&2|$qA>JVD zICf6xK)@z!Yl9Lk9!n@8K6>v?ghNxp>ZCX@;0qtBBiwL;0yriuE)$gG{ULbwt2L2n zUv`(|@q!dCb|0Vvt9K$-Lo;!Xm_qu{tg=gv9|Z2*;~?mjs1T!mx^9hZ=bcb<1i*{C zuYJl87^=5?pHY}3FGVS!q(dNwN!I$fpb6!JGkJv;d{kPNUzQoNQZC5VY3>Cze2VtR z!3XacVPdN6K*XNBN#4x^urBxNqFJffQ&%59k`c1zseH56dOdp`+<0|m|5WPa6PUQj zX16VsR&Mxp@Ybg1R^ZT#7rWI_4cuuT$)lia$1bzr05fu%Iv1Y2d<-r!~X(U(Kdl-0bTE?#!G%nJhTktH}f_C1j4$Uk*U3EYlJ@PD$dkdSAwdP=W>3A@3t z>tM?qIyNfsY)`1~r#pjS00ey|5{#TQ7cj{3yQfLJ%w8smKKymYE=9C+ai#NkCzj=P z)>~8(NWc3?wShJSP0$Rv8%&b90_Zpi6eZun05 ze4Dhr=sM~{?*PhS23(3vDWN^{GN`)WF^kdUP`sd`N2_n53Q`F$DIWx`!KNGDC1G4k zhIbj_sK|!_6y>nx&cB;f57?OUKC>UlBA(98jxqUzfdb(Nra>Lh>I5UHdW9k%F)V$m zXGM4n#WC?IMQ^8^wy>|vN*bYyWqaz3-M(mTQyrZ7jJPQL6vMWg9&~3mVnpn+0w1PT zKEBysi4WNJN1UHc_G??$w{Dpo;*kS%-w#%t|pSOchbEC(cgnc|H(Tp<~Od%Bv z)SBzu_oE%S@30?}`)p2A24Fv*(Ui1=4v$5nx_{88 zKMWZLhd*HhPPs~5d&(7#YH?ECM)WLJG=;qQ%xvJX+)P=mBs=h4 z;50Ajk#{7y_{Pl!*=u>v_UsmCZ~^QS^-ihQM`kx~nB*TbJBkTtq8@48#hzQL3>upx zff9&t<*_1f>$Wn8@>J$VJ@{(Bl2Cu6qyy%|L|91I@golv3XatbBi(nf{E3iy#v133 z>~;r?e+j%#{k%dtnvjcR&g?itI}1O)4`a&#IL8$ce+dPv6wWSBTPmd$>MoE;x zcm1Mb3$|K)1~gYzO$f=?#~d;=#q^g@WR>x#ay$L|&RU@*eo@h#^Muh5#h6Hj)WwVt zppxU~0mN9g$c&c9wb6hhK9xZe$67gitSA=mupfPTu8gy|=RL7$hl(LsLr5{PMK+c< zpgt=Tr{-5NO&{csh4ve?T7=OR5Kw<$FF#%{5C#H+pF4#2^|3o-9R8xycw!K70 z+M}x02kpu_S9u=y0b@IYM)x^D-3XfIIMRnu2$Mce>Wwnlsz|vYql~nF<{OL{z%sU$D>(c<_qw$-5Ekc* z5-Xx9$mJ2rH{)_M(iiiJI#z&oCaf#XD=7QSHK+p%*&Y{88BiGIq_fU>ZIWl+-<51e zY{+ZZZxn@dEX&x>C6!NK16EtUaUV$_vC9Yb^@nMA3jIKlh@%oy?R@6R5pRSFTTn^@ z!RzIJt1x}lzX~;l-eT{eykZ(oj`aO8vhtPX=&rYO}D5!lR|6TUUHWZ|PZCS2|i6)5#|=L2m>|6vOkm2@w}VZlk{%*J7^! zBGNbXm@?E{l`m(@ZlX)Gq@9GWqZGn3qH~BpCc0!{^FVr90{_~Gzv8TADFeW`-GbB} z5`;dS%!ikQKx+)=_{4nq*3Ft*`stLEoYJp9m&m+u&qmql1W8reVbvC|32gxFV;;3| zs+=~b)-6TK8NAt?qbBh~Fi3EiBpzq@U-5p^%mFBqwpwY;<%4$3{l@Jxhnk;00U+Hi zwbDmr?rnOn4ZfszLo+0>HF3auKZkZw!oV!UjZw$h&y-sbB{1w}nm(d~^S9CdI={uZ z$95kNaUIa5;)MKX=GDRV-H6gO`e9?NPrpXF>e&IGg;{~#>!`H%bOUb+pe?f*!yojm6YqH+CVuI>txty7y_#nHruaVxfaWbP=l03MEfv znGv7aaqqKz?r7ta_D|+(Ht7yVcOUc@3`@W$2Ro3^$^|sH?Q(G%5Op0qdvcuc$K3>v z*eY>BIMDr)!J;WHK_{+Qq!Pp8b=uT;i~U5@PB7a99id5^51Nas1;-$v0zqGGQpwB0 zY#G!x?jh+V6cwh$?*rc{s5!2H%LVdruEbdbwuV7SYxD%1U=v%b6>5gT{Uy?XT6yra zXqsNYLunkoLN4KD#yBYc$jeN52I|B2n}IN#@PS!K;I*{oUVcn@aG?ctw-l}4H~1|G zhKrggInO)0&>h>978HcmO~xlyw+>LWTo{IS_nq8`AdU)C#RexTO$*2MA0R^dBIvs( zp@ZCSU4fIkKxa|30}(a$VnWN4Yh;r=53%*Z@=rQp!G;=&rz|dHX^78AL1$HBsu@a< zmR}qVT)%jHR~yAXQM_n@+ogg*jXu%PPT%WzsD{P#kAv^jZ6hHKfYD?OsqWIn9xAR> z>CE7_`UR;tNg|9?S=x&F-pR8yw1k}%HSLH!g>h;xSQ?)=bbM^R*GaYT1hd_|A=N-% zf5N;RsQu*P`WWAiR+l`9I{SOmf1eLGZf zKY(KeEpx{4GwA>9_&SK-bFg@ha*};nkW`rDQbw&QcN}9R-=-0{aVg`Y&HScaBO%5(}=~gsC3jB#5{N|CUNz&`CiFp~C=l9`MZT8n4IOwsJ0^+0wmGH|u zZV?8u?_PB%EA5a4o(O=$_`1@@zF#}`hBk{=R&_f=tue)z5=VuL_RW}3f| z&_E*Uwb*sr5!p8i7O$g-hYJ7WdGNY8+1uCd?&|I_TWQh6+~6wc(xEg9@%Gb^jUQc= z(0C_5Z;OzFljm5|iGZ=MGC~|DQ~fjaqMg{u+@H9|JiIeM2^{?|BwD?ys94+RIL%DK zR*<))S8}=<#64wboN2^>u%G0OM6D3(O{f&(^fDkoD4Du*ZX?HObz+_cq+D;-$&?Eg zKv0?wh-07luOt7Z23j*VKiKpzS+`g*yugbSrxbNF59?;ZrfUVjiL=0&?xUE^%#+&~ zF@rYSCpV6Tz-yWMulf&(YCdbAKs_fY>l_rS>~OthxO^7~+!K|+gC%H-883Nm4!7uG zdpDwGLVwd}i(>C&oT_5Vfu5VCYluE=scKMHBe->3Y51(*A>^@V!Gx;UuF_eso4MB` zE*&4H61CgCMPO;@2qy5IClvScJ6G{aC`n1(`XwatenQi#BbngvVhwqRK!7Sce6(0t z79x6%=Ec(Rl~X1km`_pwnqO>cgNXf^ip{sJ>E=|%=AxDiv-bJ>*dxLnAA&Ciu?}M| z7DO`6?&(FrQN+`wNHbG}Gr}WeIs69A`E`be@2%p&M`l3bU?$8^`FpWmvU8pl-Imq$ zVrny7J&``~AuOTbm6IQX(HCil%xJ!3gL^G1Qm~5&@#~OlNs?XN!hdyL53da!YK@JL zT#uG3sy0=RT9G?U!1XwqJEhDux=Jk%V8VbGxVtjp6AF7jwf@?{x0kLRsEyqcy_2vj zdO;r=u1(K+Oou8%X;&x)7w3;hJAhjQeVLBFJ26l-BZh&KEmuHJXBZb38phb;%`2o- z%EwB;dx5mu*L%&gRCt8A#ou{cf^odN4c~D{M(Y6^*Bf-XC5dHV0v$X8VCg%*C$H#D zod#O?XU^>fWmWA*geJYuC{ zMg7p*$!&5lpL^v(`*Ur~(nof@O-gRec`;u>qt=;Ba{H> zB5(l#@QdBkh z0>Hb%9nb3FoRgk$k2(gM&JzH}V1NED8Qcu^6hqX@K*KwnZ<3YBL5+a3aw(%x#nl>2 z={ti#@vp-w%d%_h0p@Lhjih;UWZa7tJK#>%ESK(#NY0XVFlFz7$g?bg=kS`&1t4Mr zwNk)>XazfRgb*s*l4!2?&ApVynOn?? zac9t)aSjq(;HnGGYBCMiVI>I{qH+OeF?Dm1@O+Sp&}7T|8KVqgZ8|q;nzy}97Eyx2Ob`h~HC3)7il#!k3<_?-6J_!m#TX0|?I8@`_F*=EvaeziPT@qoAG)!y85>y73-S+qfKCQP0p@RobL=3|G% z3oZWcDL!)=&(m|~1OKS?|9uR=2#nDxIoC^XA!=iVt1t4j{Sc$g{mzxDnvmSo#})S% zJVSG_gC`S~5}4U>_*WuI_Bk@d|E;Y_&COe=Fcn1vv%CFNOF*3$@AKu#bI`g$`ie2% ze9vzD1&i*6Lexi@C|M81NK$$1)_n&M8@~AzT9fD>fvxNC`UjD#LjhuqhB9DJj#CBc7x8y_8x~RpM5uQ?Q(}EE6VGoKA*^YbSg3)&-St zc!T0*)D6D4l$`E&)Hh(UtWrGl>q!w z(YHYZlvVakHfS!!BLQ2*Sk0VM#=X@Cv*2i>hTKB@c;0B+3z+z;I>7AdeF&`ylrX&+ z19QrJ(*TFJhS*mk_F%6)X!buvdh?6CqO5OktP$m4niOV(SeR54)RRft;!rpkO zA!M?=hAD_PzQ8pFP z(;+#itcFL`xijBIfe$v6Pq$oaqZE1&n?v;C{4)-oJB5^sp@8+P+TLXUYma@Cjflr z`gdtf0|uLhvtvrWQ}`;zRF1q0^XNnC$fYQN)<*ULNM(jvx4BPjoYxpdth()jO@R=_ zUglkuWjq*hkopgyn||X+YvrhSREqtav<`!bKLJ>jF zaEip&n!Z!dXw1fPZacu05T!7O*Z(b^Wrd7bYIw zCX5~6R}Or17~W5=#Jszqy-B-ZyppiAI0Z=aJEdjBH6J$oL;i%R^Nco3@vyf&_iJkY zrqt;*K^m67zdC#=%jAoZBgum-Q!36qIk11r^}zG@7ZJg})bv6i)3CIiyL2LuOBFx3 z^7qDiX2F2>zXBR^MxR3w-mc;o5_>19Q<~Tvn|A}^H|5Y)(|_W&o@TI-H$>^|hsP>B z2z^=t+oYhMbVwj4CpuNqp%;8Ro7b4`k^05}h@fQ*5`M zes9}rdv`m(Udxlutn}8Gg+~PyQcj3VS)DD@p z!XTs(Hxv8{kS#6h-Bfl1nhrz-wXxPjAjt-l+`(0o5cD7;Q40v$f9%YH>;DP&07J#8jocm&u8jX`vC(ljljKc-re}n^wM0O29NWo zOBv=^TQ7TAF{U7*)BRgkbb@!)RKmhvj75e=S;eNSC<>&9VeIL$UR0T|vV-6uMldN=xbI1-k)9M*^QD^wN=$X8(_;T0P0u@GLY$%iEp-NtYIXiIREU z{zi$Ln&lSI$Y<{b4sW+@D>bWC z<7IZMAUL2zCw?4p8;Gf;#Fw zA%h6cM-+X{scbBqAd;HVO}{bNMT8>gHD$F_blFTJN2M*o=KE>=0h`%pWSbcUw{nDL zv#k83a)vB!MFzaazi#`Z-nG01>Vq^4nO0Wjq>c7Ov}GP5rb$KGWkln|4^DX{Dz+^1YR* z4{`LVj~B*2+4MDyRHmdI95d5Z+QAR+i59RY5Ye}^dv?~8i~W@$pFpq>SG4kJN3*mk zbBoUwFAbITwy*K7qs)af31o;ET#a?cK)8R=c+Z~)#38%lmhn(Ua+}#c+5O``W%A|2 zvG}lb2}pt(b#7;k+YH6PWwrf*_=Q*&A}T^mzjKU4-Cv#6J0*!8Ei57Om}R_yfPQlB ze~{LRDm%KNg2eefDZaMo6ro=R6l>~XoKNg6A21bHxGsK2%NX&06o-kPRC`dgpM7_5 zsAaC@*?RI4&ZTmMY-ra^DQz^rYC3!9SjJ3=t|&mjRFtO&3Te%B*pg{&jf8*&m*HLkLB5qsPD z5!u2-p{lms)10v%Qtt=|Lu_D)|KoB!^o_zm0M$V$|2tibT-JBTW`R-x1k1Qw!7=2m zsS{~ai-W>Zl=!a_9SjRKy3pTU#iTki;tu^th0huXN?Jr)eK=5R30*m+;3`HGRg1iA zm{1K13|2isgFO_@|0}yvehGTOUX;NI1Y0yFK+^O87Nn2==JihbaF1hZS_)w#mAx99_q@gA(05kcs2AMlcBYfy%oXeRUV9StU++sJ3@ z{s~Sk-t%NG&)K^5`bb!X#)YTsnRfOMvK7=yORIJ8yjd}B!1-eEJ20p_AYqb#tRo9I z^hoGtlc?sOAbE9`4>$_a8Vb-kIcGz_7)OT$`SyRK!oJn395R`rPw$nE6({#H>CjG{ zTYuF1SG=W$0)DK*oQm+oa1QjOTFwE3jJLtN;2F3g|px||Ok?O27_ zcgZroh4O5#c9vzmB?Hb0y;=~vZX59j=w78yD?vTsUCW8x7${U%ydU=rq0x3AT`msT zk-C};cH?r1b)x-S1{h<#@62R|n*ayQ8+R%_@+$d3r;0wIeM(d!lpta`i2UXcJzvBf zP{{pWs-jgNu8Qb6>qm^E%eZ0-T#ClYjvoD`f z^f56$2C_%V#7FCHLOsNXK}`_8IU%W%bFAI)6DHY;MZ*}cNEU_YzTRTfJ^lj1>z|PB zt5*?5kEr>Td9We5XbmfbPzWhHt}DqV1Z2Q%QXrf2jx-($MN&cy;3vC9b5#D(Lwp)* zH-8Op>ll#<;!Jtaj}{RcO~y`I7{fyqTPCuPNRDhS&}%yLB$#(;T^h3fLvDQA*be*s z-1lH_fi7xPeAmp-If49xw#p$(N}XaB&htU>NTQgN9X6$QTz&>M>jZoNq|jqJz_B$L z@Bw#@`E}5`OO1ocZ3u|Ax@-0#4#U;8+W!)-y1@TIA+Vy!5sp(5dwIm$yJOAFKe}#F z+6J^W3Oa+DR_5nDD30WzgT^Z2nzkN=0wzlcUASRw@&!y&13xBUve5x`FNd9xr=hmBBd7g-aLA9WqA zdA{>~w>eYb<(JVuNkb*>u88a)>7L_>h03wSFe@6QUv4$0%b@ok+SRk+@XQ}T=8C&9 z4G9>{*@}CJ*@&5fCr*St7Tf@FtXe%|R#j+{o4v z9lEmly0Xq3v&#YZL{l(@`ym31i}``(QB{~5oiK_e)i5%i%|O<%fhAno>K6LXhSPkP z_LjgYi7r^7HU1|&eElH_0F)SNF-Vnh6G?~d`!tbIFb`T8xNs|}i0Kg4h+fXBJ5As# zlLZHRWFH|C<_aV9CAR@U3)KHiu{uslQ>_4-S7K`j#@X1#Dw3sCf#u}FOyjaHZK&!!+;fIu~o9i>H#H*Ojm`FTTglu)guDLkgq5GZh|J^`q!v&nj{gE^OGfD0N-8-b(PDX?R+1yw z{#+H5e7UBVE6?Y9Cp_g&inWkV2evxH|hk{xZXwEGF4w9g839)A=U-$ZdyJI?rUeAOd)T7W0%sJM zgGy&QtoT4wXz+7uZedesz!(Yhgpx(E!Y(WBOoW{m>bHI{j%HHRo6Mj1*lL5X21^tf()rU1+Y z+H82}r4fU>jM26$DI+#{ekRK_F$z^hC$EN9cl#oCq7yXDLexce`*7T~{a5Y?nJlJk z@_gTW4w&wvZx@Q}y-^K=(a2M9Xj&qeogZu0WT8=X`F}MM*U{G`u*Sy@S+-g8z9AlT zZO3hMlQ^}|0rIlA_%d<^^h*@aPawLbKsu|3M?(s zefCL)nv9mQ_=(y(4mk>-)Bm%;Vh`RXcTh=l z7Gx^^+cka-DC>wQtvs5xbNlPy-W((1E6!=L)Q9(4T!nx0vnqO4fnUy+b%7b2Yyn4e zo{dg_g0=#d^ODO2v-u}_8z`NntVdJ|L3(1kcG`#qz_Q#XK>As+XM$h;osS!>R0==B zQ!)AYaI7}I{CNI+>@A+?XAlVnH^%ZPD=&*Xr7`T~xmv@)HD# z4;iNED4VLgYZq+m=I#h5I2{!tg8l(>kRL2Xe=eWjL8p1 z8EVI>-u|7~g}4Qey_e9mnuQm|F+0Is;w-g0Q|DkZUCVQaM{HSUER9qQSyA6HCyPhK zPFVXI$T~|_khXxl5Iz?X^G?r-=G8)py}y*|N(l)W)3F%g+kvP^fFY5qKWDzerTKTW z2I(XPQ&~MG?z8nK6@;h#nHHZN)#dyJ_CS>$AM;BHWw(5D#(jSI0VO1fO0qX)1Bi)U z0fG!Cmt8EAP0zW}Y;VJ!bKE(6u$teqPjwx-8e{!$0&FokmS;jYk~MjhfeDF6n^H^N zwRtt!4gC-XOee63L|;hjQY^W74}O6XtJ|)T74IIIRP>*#Zw5#6sL2*U93Lt;>4O2? zGMH99H`2xC!SrXc19`yzM7rX(ceZ!i+}-~Xnl|J}stEuDm*&k2sNg@2pBS~@u@5eb z=|*|T#^S>%S{2+yO$QDAmTV;D73F1ATm!Qt<95e~=cfx6K$UXw3G*#&&}NW9h%|of zx_%gRhqj`!2j2$2_g&zbkrdhZs{<^G=(NM$t=l(=xlicK35A=}Xz^N7r_pL+U=J;KExUuiUD{k3L2)O-q1SM}ggfPUU- z?|QkXekL1YDDEix1b(BXAX9?!EijRmSNJ~b`+dqm7oQVhU!MTFbMmepTb4v-50$4H zk-QS&tnDVGhL!Kw6^;O$7-bRH7h{jmU_G05=0rMe8TD7>Iba`|=+E;sU1~mN!cqKt z_~`jb2(^Bx&c>W10d#()DP{hXdNcq;8Ryu!O~0?SbTS~EN~70;eu$PS5pltNb9+a z_8#j!c@l=fk-NN;Q5>8Xt=}v{v0@f_b?AP$5F6Z5n%!(JXq+@zT)8zs%OCeP^;p_O z)7>Qn9SUC}i@BUy2V)b>{&I+foojCeve&U)!AtHWIDN4An!mo zck<~hEkHr}xsn>>hfJ%7?!@3LAL`99pnB5oyU#&r=PEMq02mz^^E8H8wC(mfr_UD| ziCR1VSR8#mCfprMJZo<`G-+tDrG6%gJuukgH zES!0d@-g6{I8F5$3F?;OTa6G6^L*eh{7dSOnaMYS;P6W0<8k-^iQBQJOYz0I>WwVW zx8C)Euvdc4DdBsCD5JaHsXnkCs-cw2ztLR*7}zF(y{CZL z797cYg>Ptw(p**u+uZM(ym(2vBN~VCJCU~e*A>*B%};ok(^o`bPpJ8qU*27OKGBzc z9aWM!_nC$`wrO} ziQ_0j!ZI7BsYY4FZ^>=+R`xsTdKowURvhW4q1K(M38x}ivf2z8EK@|m$~*2BP_bz4 zL@+98{e7j%+7c7z<(!%%(l#ETwiKRN7)x25bbNG{IqW7x95AU z(2Zk?Z#-*vIf5i`!IS#W;i_?f8s~aVT+tjw2|A<^#XL^z@AO(Q?hSrl>z3C#tPCp> z5}q6ZQziU}_>-8SuTd!<%X|Y?tiX8sfptDJ#vLIM+e1iMd6mty0lH+x^JbCCryGf4 zJxXTROa#40rNiJpgT1h;+hHgs;e2Udrkjpv;E^RXF&<$`!lTay`h%`45LU%xd3k39jZy61%I)h?=oSb^)lQVVjqF!j2 ziez0(Dhbv76L8f}6H-1XTuUs|;E39hu z0eHv{wE?9O{4dM4KCzT(ArrUmEY6W$lNV9t0?KreI^ya6%<>oe7Zdi+Pbaz|j1dta zb3R>V(lp_$KUWI-oyhWFP3lDE7>hNuTJC#46tDi!Jt4B)^Ie_1Ikqag3vg#~u9NY2K$XP`)u4YY+ z42W~wly22Ca?Kl&%`3IRmayFLj;Cm^YK&V4pN-zJ}w^ZW= z@Wh|_x%H~d3_NhDygG4X9w6euK!7z)$W^s(ak9)QxZe901Vz>&CMYy^DKgigkk5=s zb$6RAO{ZPTx2B6^zvmS+m=PuK5@=oAad~jW2e?%EqLy$Of=6^wVAYYv26H(4jJnnvP^Z1 z!!D)V%GeAJ>Vd$8)tls|bvKv5B}2sjajaa9GY=>2it2M^U&CLeG5^*?!$0Ih(1!@; zzRD3;skooj7;&Cwi#weM1OX^l(ynZbaNf;WVa%g4wbw==9628IUGeE@EINr3IrO!G zEtW0nk0~7>;2QHGK3OtPXnQ>%YTJdEHs<9?`%`Pj<#8B1emg5vi0T9@53-oI)j<*I zra2_iVOJ_tCE$A77URQO))dZgn5JWEG?p|vIJRH5Qop=q=oJ%419pq`K@&S61zkqE z=M=c2n<5*Z6BRsV*r=nQg3A5L)W{f`K5t3|6XFkC=QShd8h#QIgm1rCiY2l8PdBG+M{; zNM|8?>2a3=N7PBfePt=3r*vYhaf)E-6z)?Fq%5D!=;viK^pb_M`RN{6t}?LNP`Y8T ztIBQ?V5;>F?`#u4mT2j3)>v{)cy)}ydzx#U^-JUwpm1c_?ywc~9x6s;{)*}Vl&WMS z`3eSOZEwc`D8-}Kv2L&$?jiQIX~x-P9yYx4S+e5U z=#Do&aZ##nNcy!+!+n}eH*niN+?(D_^9;8zpf{8AY~|^Yx&izo!vLsGYNmQ^951VK zI(9Ubes@P^;R-F@Ti@swvwofmB`~_S1m8Er6~@*!)mYEcU(-d7#rtq(CH*M``^62U z-RC2Q+!2LGu63cSeF*}>)cxXbD;H6`p;!7%VIzf&Nb`*Bp(7LJcvR)e{VHXC1eJ3D zITYDS+sqF*=zLTXzlA(+ewTlKU9PEhE9tB;>W=vWJ;yIHf+;<`vz_m*0FvQ z-546##d(642*89fjXsoR9N0c_?f-kFRMg>vfm$B&)IACIkk?tQzEUO`oep{z-#m!1 zEJ>|;u=@q6yFrcb2fimyzF7EgQJv_ac;WDhRkpVepJRG$;Xs$QwhjI^ry(@KI|toC zK@uDV?%3t*_o`3zE|%r)Qm1ML4ZB$}C+^FQY-U!zC86kr{9DiH;Q47QUa5Ze>+i^J*2{%*D_-3G1jnM4~M+opyCpZRNhllFD(fG{q9KPywVnwXrIGcib+9Y>Ld^P+HsYF5<$N5ScscxzyHkhP6IaFE zDNfM0I>>9Y?=ZdkAnFZPIsIgmz!*$$$}9(dp*iB9 zA8`UiqBK3(w48mmBvsxGJ>KrX{~P`GB&Tv@E^ttipOYoCGgPw;8-x$)n%wM7|C87% z)_ofoZN6Ib%iVafJ&u5H4qS z=}NBlt!17@kYGY&H15P?A5w1Fh(@{mTQdx70VY1A04q5kwwQBC;8GAqyyK5hWqnG2 zK%Ac;x8zK-_wAi4ul`We?av&1S%q8Su;M%|;<|s} zzRVtP3VFE}GSX*{UB|)_b$m-$dcA2F9FHse*n6Y>-_B~dK-K)ze4D1QHq*rNA^Bvh zES}}%NSI!&3hTlo$|Y_cbaI2Ayio#%*?;{2rACpz-Zp)&yTP2!b^9foRPR@>fTiRW zq)S+ZueYlBUeKax%!K?>0QG2d3|jiIdnAc@c{%m)OwOGDY=F6=kU=zVv+j6Jx_;jG zbv!>HfEj_$6$Cj=BAV}0Rfk`}TW`66#T}UqsigF4RSG>Z9U^RZdHPN2#slk~Izw?g z(1LF_*TqY%y^meLOPqrj)uE@Gm2-Pe=yLvBo{Oq=*bX)ec(L|a`F749wx2PsFTNQs z3~nlM=_ZKFI_lNr?&K@^k!Pz*6HAzWny3%M3@OCj&7v(m70k0o<$&^f8J+nCs9>DP zjwd2N2Z7yaVvL~-Sm{j8}1 z@r|qini`13J^U^D_Qu`0WdgherlFXf!lVIS5?AK>5~zJAD%^O>rjXZK33h)l0dcPt zAU(PvOe>+>;bT+CPyk+#)@YxF9}y^VOVj*|7Tc&{HN1Bm2@AdZp4bdBSPulv>@_5UFz@Qmtk_cqZ` zNMr}|W2d`b{fZ8r=WIJCjN_g%PNq|Vl9G{7S&$p^-^zL?`&*a*GWOI(BxCxI01Ymc{+m!(w^S)Ia@;K&hfb?~7=&_bifqRbAR#CReg z%ihv55X zfcu#T*V&ud)I_?kXDE1OLf16>!%_RMiTtJLJfA^E_L;+5&1y6+DPn&040=b_roKef zVNyBOO}vECRs$L3Wh}CxQWxKNNxgEyG3F60#Vd1EO$~oZjiyz7YbzCVcux}=8}6i` z`}=15@Q{fo_xdzgh+bD)8i%)EJ)9o}HQFoSs=r@N(p=5TUGw4rgst5ev&8TUG7&3P z2^vz(1lGEftdCZtoG+ixx9tiU@J~!%g$l?EsvyE3Fa~#jY)9nK6WpfR>5Zup&PnT< zZSBX%buQ#79KL0cCJ~^6qO%7^%GfBXU$y=FI&VlR$UKes=VV-wSD8TC%Fr!zU*z^x zajb-;9aw<&+HCM{tkibb2>K4RCX2?{h*Y^#u9S%pDSy3x&HHxwYt@#uZ-mSM)heQ%6(!lZpadI(`#!g^LKSFHMRVruh5 zQ^$_d1B!HPP>%h)yVriPE59Mx6vzMw7B&a=VZfJHUP}VlCT}1T8{g)-kkLTFRw7)v zSo++A6VI7z)^HiZtF6#3J=Kyir~k17kkYnwJsYpWV6#*>!kuqJ_3XVaf9!*N*p^Q@5* zRqN`KevHB0nrAq?|E}C8++l8~s#nyQgJxYG+ePLV4C0ATd^1a~7Y(bsc*=(E{fTs01k2>?OU&;7K$`Cge~o zv!e^7XvDuF1$>Z(X z#+?VFsCv$PebOK0d&Jam)oZC^TJZkNp8u9BIpr2WXlP2dqFy!o>Wqtk{S7pp!1PTq z&gUMN4*3(JPt#d5+w7uCIRRW`qYrU)X9f1zPk%ut<g4RRk6hGQG#k>%V;P+#_y zxm&q6wLx4mET*)E3MOq^z=iW>B=4THXH;}t^je?T{rZuOC)#mAIoK0-97h=RcbplF9Lh}7~kMcAGI@!q40` z1LTWcyqF!{1<0prokn1{pNY&11DI4TT49_(=^i?awQJ6ck3eL?RYob61{ls z^DfB~iXz|9!4nHR+8I;mY|Jz@yJ}aNRt0(N z;@MaQ{PG@%LaR|c4x)y_?(vvF=Yv2>pa*Z@7wfTo~{!q;U1%Q#}a$x*7q&_th0L1@sh6LCn7(qcZm zo_sa8+C_DjqljxH-ex2+v~VLi+~;AY)TRnxCl*PzxKD&O-qo$iXuxu2Z`Vq9gT{D_ z%;qGbXbKniKk1@kC3Xv-D+Vz?0OWRm?a&<)=hP z5#IqFi&vG|E~~>9gjQ=){HWs|!gp-?TtjY?NAB`P1_Y@zU_?{<{!ezlFNZRDsOSpy z!Yra@PgG!g@)UbQVr)V9PBv$`DXTo#T{CkxXDhgagyVA@=;6y;>yAKDWn@PWn>G{< z0VzJ#geJ#ce%Rf6nX4@;N6r2N?Uxi7B%ym?ckuZhoM5T(PA{aA9YY0L1t}Wenv*Wo zJqo0C##NtUP7>rEv^JIGCE|dkV&&jRKF7y5;1=-oxP9*cZew1oqiFt&hQm}aG?q** zY-y8%1!SG@`;>Jatgn^a@D{-{G>=HoE6NlJe;N6amuIkZS(==)#E9s(#)&y}`JDXP zPCG?I=Sxm$SZn$6uuOjZ&8wde3wZ4=WV{A7qc)*$DnC0|;zB@Y^j@)*6(s;%wp>KO zM)eHb?ryDNL+sNm+VxKdlNcdKe7dU53}@pF9qvXeex|w>GK|g|Nd_-X*o>wf2^CS3 zYdMP1uuwW@zv;{$2oU;vF&^@k1f*qo;SvUED+<@IY3y41f4B}Lu?Y$QQ11SGmimL6 z41oG04>THLFXwiiAENu`gh&UMn7gTP+aEE3y7RIFP3~x}j-?#1FiTd2y^v$96_oY< z0I6R-pnuobVM}zO!KKz!ft)f zN?R;2Ki5Vbtv@v4T|puMbIGIES$55op3fL9!ohwATqq^O0ChUYydH~NYvsF@be zaCvzRy2s-0hp%hH?102d7$PG!hvHTs*T>C;j33TAJfW6K3|O03`0BGro_h z8o@#PU-6}^Z8Q>*<6M1ooX^$+m!pBwdJ=X6i-G~(GfwG<@V4a)tf=eAp;sZ6$lZkO zQv*nr`#jit7&{6gG=%wDznYf^%wBBMuIf(oT0kSk0FPUxQl?F@&j>fCG=TvMgu#Tt zCeZ7fKEa-r=FP^$R>C?e>8sMa7C#;RoJH`jN~|k6 zguNCvS(f-&w$GJ*I?H9ZnlANoJhjUF#S$j^C0}OIij_%7-=%sk{rv3FRx>z$^_nk* z0kOZ5m76hSfzWrF!m)Cv7ckH{i3mo7(c>R#&Az}{shNNVtjFOH|0LD&;|C!XRlT&S z*(gWJ&-#WA9P>tlbrpjl8(Rm%aRCY~FqAAD&L0|Tr1Et3#GLJbp8p5yKM$1h9Y#=F zxpX<1C!lUNmVCw(~p$4Np0%Tb{ zi(FP5ESiQO-)r@g#41i7^XLe^q_FS0oZ>+p1n!+z5V8tb(KOY%!CtrG`qK*u#j{LB zpf_~cCOAVKp^n#DD%KV?W?ya(_hi+yo}tYpeIR#m+z`QLQrZYfT!zdCJ2cIK+UuNL z1is0F7lPetQjot4CFtzA(@k&^WQ=cHh~n^N6pJ4g=lBWP%LYea)Dr#8m6!#F{}BVa`J}a{aSHJ~ zl<;?waKiqI)f~@(n%RBuH(2&OCG@yRBbc+yW&6sRla=U(s;%e23Ni!Vw9&sK?|7f zA4B!oOr4Gw?Qklkx{P6aXHm5hqwM&vq_HKhH*vPBvT$~{-P`HV+!)p*$vjbQ_$cCK z0UNAQ6uyB@5Zi+KkHzVVcc_h7j8A*dDDfI2A;H}ID}#IEhH^fE`&~kKlV(#2D~GU! z>5<1INj5L%+4Q+ralwa6IV%{=PLek%UWu2D99D)N9A#@!zQcq$@Yl-!=zxw;E(MJ| zdzxB}hs0>KfC^=O@ph=(S0i`(->#rUJ+ZQl435*p7rt9Vd3gSc*@jnJt6|vrkPaJ7 z_at62k85vz_~Nt!-<>dW51Gth+d=8R+RxdSR`1d%BU!Z*9D-L;(s)EbQ#ZSjll=x@ zi4AFnZgEI&RkV&$&RPJWE7QfNWd(hLUpj>63Lu6JqorvO$)NxR=m1T9bc*3|HG3F6 z35(e4`Q3TD{mWgZF{`;x7tgaIx8kt{q%<-I`EexNHtKHjA3s?C?JN+1tD82(Q4Yz2 z{a3W0l@jMI@Q;;t5!%rX=gj88AQ>#s@iBT5w*z#-v(~)`K&6q!@x~hUb{>{`epUOx zT&LbpK~4-DsNj2pVP$K7eUzzX6<)I5wlio8-CKlc0Y%9)-gtUjsYUwVu9d;NGv9+@ z)K9@-2$BN$4wmO8-J2|_m;T(xioC5H3-wSqPKZ}f2QVv+dYh%#vVXp*gl#o@8UxA< zGl-p0<-1quSkCL-qIIGWq3Ah{`LM@hRj2YX_veUWGC{wm=Cn)p)z+LLZqY7RtVn1L!`%ZB~uo-LsSc+FaU*O1(c|5I1Foiu?F|j}BAaTQUQ4x8+-4sIvMl zBfPO`f}4k79i^r562xX>dqS*kr$?rqI*bsac5aX>g6v31M_RLO`PWFoG7l+Rebm&a z_`pzeoj5Sm?sK!2jd<(E zWMM(jY@+5v?D4Ipozz^1F~3_HWZR49WnU;L1X73h7iJ!!sLNE9H{eMsIV}1O_kTh^ zX{@sjCp@f>d#Bt!t;}0?s6`lJKppEW-MlF2I7K7 zk0wjkH=ojk`Hwl9Sz)|gMiL0oRCtr@{KznU;HabMY8lg(Ygehjw8H8E-;fctk3F(S z;N@vxE`K}lzsi0Hee5b(D!!w?qd{GC%{#lcl}Xm@&_&&8R4I&H57rigRHhhCV(;*K zh^;);Y?LH-zQ5{?irTxVByFxPNT&e%2N+VxH32`?G0lC~+*X}g9TqxD z>jw~lkX~dZ&b0ic3v8+4lKoZ{U!-Q{H_0<+0%(`7L zRaBz&?2H-N1DF)X1<&0j<(ZFz7Sz? zXCF@F*IrQL820u5cTB zcAfqxOIV`9VYS?Y;-b~_B>Y%5$zT9fKE+(dB8jEz5se4GOV#{RTI0`It1LnF`O>&m z#)hwm#rP;%1tgbGf)^9WHb7npv?oG$pM19vd^le?+-{F|!Dm4uRx<$2VERw&oc)DY z0YAh#@5M~xMHP6jj%2}Yoy0NkEqW8x?5f1@pdg5~lT#2?piOwAIKo;;CuqL_V_OXU zot+dkGuFLa{m6bXhi;VFeWs;;KIS6a!7Ga> zXG0okYeWWeA#g4|a&^Ln+Rf-P&Pd)>l-HI>d0GQs5B480euV5yj6u2!h@(tY29Hx6 z(#`H7qbv3pNusef23&H_9GAP}3m8TT;7Ic%Mctxx8QCP&3?dc3NsWjDC=hruQVqD$ z&uAo`+X!-f7l(lLu;HfB9D<*5vh4hfw>lH0xo_ZJk!==<9^o8WgA7}%bYNrYs7E<0 z;ZvdG-ja!P>jTE{-M1!L*p(f|VRo0MVN_0|w5P zT`1}NbSxYe45Zj6dZ(4RW|UXBX8A21U4-soGl+>UOzGl{6f`zX2D4+Pox%0Dq>N6W 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

&h=fvoH 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 literal 0 HcmV?d00001 diff --git a/uploads/8cc77d3d-f28a-4b9c-80b6-adc6ec672214.enc b/uploads/8cc77d3d-f28a-4b9c-80b6-adc6ec672214.enc new file mode 100644 index 0000000000000000000000000000000000000000..1900437c804de14e37bd846f247c888071857f03 GIT binary patch literal 8833 zcmV-{B7WT=bcT#TIBNy@s^Q$wCVd^? zdY+8ST|z6Zn=92hdhHQ90NTr}i|!TlIF^r6Oio0rLU{2|h|#A7JJp41?E9vxoG{D` zD40~-S>Pmqdmv+ETK2kRwHDVrvZ}3`&ZhI62R*EZH}iClc)E>|Mm#2%G_PeQTE{xv z0qe}mpyeT#SCyV3Z82pw3ZV@1GVve99WXwY*+Zu%{88ujuAJsJe>aHxi6APRvm2Wv zL5q>BSBTCxqhlsQ^k0pM{hiNc+{bUxxy{wF7m zDa&wJIe!S`4|(Xc+uyfgdlLhyzRtsy$Zcx)BDKdN%4aOg7_!(nN zvA6C?+C#Lts3mcUg04N5q#p7UaMK=^c{CADr1?An-;olq0eccg;!>&K8XzOcXa5nA zz!|6(f&rI0Iqvsw-S-YU$J5&m%+fWs*8pJHd55B(9na4vGaWpLU>0HKZ!KV?)Bdag zrJ^+ANVLZs49L6Ev%X;te4GAN%{nqc3>Q_FHorcYHnx9BqitsCPEAHQ3{2V2@-E^) zLBcseCZeUtU0P4lPF=6Ax@efH%coq))%&C5FeJe|1tg_sl=vHO7U7w3(2~?hh4X4` z%>682U#9FvbEYo6xVylUL0Cih1d5o;CMdIpC9AiCv)P_T zlfav;c1f5+hdAxq@qppaJ6#+ZLVwT|kYv}0Pb7c-k{7#?2SwF8NQ~&E1A!)8V5}>}=6Rrdq z9(+)jG~3#s%)iI@YCrM_26#aP1Q@iGe@bPufGbdZ=2}bL^6|HlHR8maox(*O?$UjC z1U zttD?ICuA;LhL8f3!Ri2M^_UHVbO-wkpZOAxypT^p)BVz_zy+$0qfbG zTrT|w6Dw_fvy>mK6z8QRgi=M$R$~x*-dGegV|`4Q;B>0i=)gWGmGm;;Nb(Eq4-TNz zKCu9!2j5lOnMcJv28>~Wl{62x=_}8?a8SfA9_zrXSh!kFl5L!Ie{(-_OdO}!m>>f5 zI2PtG(mRtBnaUAXMk4NO@e=ADy|wlA01riDPHR-AC3-EVy0!|^5|GP5_tv9dUgAX2 zNmJFi6o|&Xw^}C!=RzmbxDGcw1N~|`)7U@Y@;3#;(uGY7jxuNwJUGt1_BgcWCr2Bt zG4{esh}L11SK_~caq{~LPJeaw)4oU2hmK>GQXCgwgg~SXzxm`~xo&(y*485fQ1i-H z_OIzI4bYn!f06#tBCPZOg|M;v4m)Xw2Qks2F}-In&@SSkdbl;+w$<1-sV`{3s;f#G zoS4wSRS6X%kR8K>`bi2~TcCFbr{b?RcG3dTGac`mG>KuiT|T*M(8!a>ve#8*7v}=< zvmgut0di7M&HEOKMG|pU5$7q6S~Wm_cDndNktfbMzvvqz6_>)VuRRK>%Yjb;?zJZF zK(uji#+8iu1lr{YBuq%B8~$=n&*E=(mrr*W$_vSeUy_zK1T14fpjHWIIFBVVT@V-v;|UK(2A_L#S7(W!Dhz zX4|h!+#VUgp9Bpas`Qn|Lz6gUq&%v~_uA&nOK7C@TL3b}J_$2f$r(!0@njq^aHq zIyEv@u=UE980kjhYkb2Q71(e_oxx&p`o3Z1>AQC zJ!+vXlQJW%GY1zv;obID8#N%#7`*C+*;`ClOC4{ii++3_RU-`#a;{fQP-uU!-)(w-+Dm1QM0&|O}br)X!`F^)w~9| zYTuj*s8Tuq>xuD{Fg~;@C+1IIfzJ#5Qev=50i{4XNd9y&&=hdr>i^Gh00xGDyiRVU zrUwojG{9Jxqyo7?)#j5&g-+o*g+E zek#J_UqFJfBQ}`p3ouz7tM{)rVXjMfw=Oy3QPqNh$-XG;OWbm<4^46j{phEko9-&+ zlEo@~Znp{8@QYh;yGgLV5Mg5H!f^$FI_@eEaq;Kj#akQ0}F;|EDn-JfWPixLpf(uJU zAXVv}!Db4|qhZ1C9VrdQIn)&|@|(~*_x$<{ivKwwpcNPjRu1gd?j(cqnJE`azOF_M zQsq&VOumZXr_+Oq$$6o|&jo{sDfls2$dNIIym!O&2|Ez_d_>gMLXu)3K3emGrsfun z5KQ#KTAc%Boz35+QYR~O)!-rhtrC{F3g)Y4yi9?pxLj2bLJ7UVu3CdZ6jev-m-#N1 z!>Ec;oP?m`Vn>d%A?U%qJcX@#Ip) z0T~XDy973ukL@Pi5_-R*-y;DWG;(8CW|ZsMvlJOpV%fgQ<8E{fTc(Ny3Mgr`CW;eq zBG9`bUgm_-o|?{;@|WsqhinI1LP7Yf7Xg`Cfqd06SE_Xd+JO_9{_rWyw{v_ZX}L`eHR+$d8l{k27vio;Dn@7^t9k5OeCt&qp#b18?-C*sarEuXu< zTko8ieFlRgAuHLhuDhY;t+$jVZ8+QPzqGmkZ2xYn-@IN}otX?ATnb;pteFbw(oki z(c>^sGC4`?qBCu<(q1Y(#OS>fz=zu%D8jHiq%y$#<`D3WMy=`W)Iq7pBxvCvxw+oo z8a`*@AHD&=C}Kh<#}_!D-dydIN=f?zP-`_2G1hxU2XOV21Ho*IwKT2MU~o@xJAZ}j zH;Uqk*^=ul(A6~$eL;MeKIPGS;jXD3-}}h2wFx^&06R({_;ut4$R^~F$kk{M^o-^D zi~Hf_N-2s{ja`rK!tRPzwg+wsbz$PB1LCS!Nx2y%7m`)+N&hwLG?jADRZZ;HHD1p0 z3l8&`H#uw0kz?}8-?&*_YQ+yZ7%mi6$ZNQq7le{m0F3@jLBZ$5QFX|3hqpAT1@<1= zp%bX$-btOx-E^B7WgR2n0qKn-;H16=q4%H#u#^1WEgGpj*sqE~o;zaQ$S_F43!W2j zltu2&K(A>%kBVUK_q!N;XvKRjL?E^E02a72AU72qAomTQR_w2U!#wDn4IdVT5gCwK ziKgMkF-Sn{qble?iD;HKB-dV^jVa8{@59{MQfi=7JO`(H1=)ZanBN*V&F;$M`lE@R z9ybCByo6_0G)$cQ6uFg}#j_=7=i6tu!j@|5&Z#XF8LVbtHj9BL`a9df#3YNZDxF zG>3bCYLXqYSsqg-u7w_`o9bgyHtB1RymHP&i=jB=9BmuP*jgUI_n^ukzjv&-DlfZu zyrAJ%I1dBT#7E`)y`C*cNcy#h+KU5Rse65Wen8@a8OC=yC)MWS7V?37r*LALRLiPDHmWTK57$FB@2qi)_q#vGYznM2 z2*E)T3r@1_Kbv|ooiq4E%fz*Lo^aIB^?rT7wRr-i{}pgBih8oui8O?Wl_h4+G7KPm z)>17CJDhb!RvRcAxjl&pR{lu!09c^R1~bTg=VuZybO8^W!(%{o88=M=;0N#DF54`- z3E3ftO)%QGDz*6TlX019Dz$aMh8~Y`Eb|p=5Zu7A|1o8wWw1{;zDKVwiqRW|85eMH z0!9N1JD}N+(2p|lV}UoWg<&CuNCq?F?EGtpf8Whm2eV?wg{guC>2E%&wtjNc`%#1W zv$uH4PsHlkWeoK)MZ!XuB^Y!PdYSm15t*|x7pNd8DA(78s4 zuFz{(w0L7BcivG_Bk9$5ijLN^NRhQXOXAK0@dz7TaTRLfGA6eIe9BQ6$0c`);A*IV zl4rcZRE1J3OOUo;ZTiYBk8E)>4wufd)IR_&Bo3u1a2bFXJq`#n!=6B<^R@8gT_ za4Ae&0mi{(NZ(Qv>oyn1)9D1zyqx|Hob|J+kM-l}Lr^R4MqXmAOPp4hr&nCB=9Wk~T98ljWW^MTZ*UCd~R#z|;vU_H%|F z6$IRUI=iZ8hO zkAe8L@mKsYXd3$dBtoEeP^KMv?9dtwV8{0nKuqm#lZ89tPRnz@$BMd8ZqyW{YUR?2 zJ?tKL^wX(&Z6r^g$NzPg5X3`8EvFOet?wW9QJ^yTSO2eH?XybuIk<801Tasj9Xp^2 zYbJsV-8aS6%0pruUDsG&0nz^O)Ig6_p+I;{++tW?4tAvtm;==6E8-ThRmE;ndHR8{ z@{rEcuB^eH8){qHF-n*ITocn>{ya zjGP7Z2T^VBEh78aeD5;Yavm~ogR8kt!kquGMMdI2L|8NE8yugl)*?rbwdQIVDJU~p z7M|Sv{tH{eZUxr?=;QN)5Rf>uyTf@zx`iBPWB#7Px)wdymwNV>M}Bbj!d0HOze^!` zo1Bix9jvWAAk+0S{L)mtP!8ZF{P(`h`mUunlPdOeqe)x0n!N2Q%|kL88gp2LNNN^6 zJ=opi;qm;9IL&I0M&MyxgZdBNgnP;LKVSm|4M`xdX4m7uY0!6orP7WP_bCPV=^>Sc zgm#?mPL%VS62oLRq7MjMJ#!N)kx{7)i~{*RG;Q^gveF4l@w%mZPToW z)pA2F`m$(Z2ktHNi=@r6yvu<=+-gshholv**D>K+7)Hu9)ulWs&8f~i?FV$I1)>dD zhGW0WW6D3pJaD7QXm64&w-7!^@LdhCBfL8 zssMn!f?3Pbp9Y$aYLnNawcivc-x!^VnRshL>~68RJ(w%#9p;J$o0VSn7o<~HQe&OV ziqQM|=%%4mX#93A;Gn(znSI>;q%yHko9coyBcbFv1$O27!joq1YyQl#O|Bo{^uFaS zRe>tgp85rplOs@#1YY!>bZe`H(MepQcFOY4ey zys{W)IQNpc0fjv?2tu%hd1uC5TI0y(rhjR)9AI*^-lt|io<$6$!9*M!tlE50vr{-Q z*Vb9U774l_9(d_LU$xMgWgZYZ;nsAZHu_==!-ohfZ#v=9dXY#v8-YawnIgLb8&OKi zLo`h1E#2ghVwSOv%NL_F`Tn>|vOmZab3;}H;QKqGKLo^nCpBv5;U`?GUn$~(rn;R( zAg5@)rG}I9;8_EFQzGf-`QaV6n%69%Krzl;VgTnva! zjVu=!a|j2qt@5mvqzS0p`b@&(dDe*qiK>=gZV+c$nZVX8&iPm|qkFA*eh0D_5lG6e zN<+vl=}q@DQif4he*7B!i1DD#r5+;|DxJ>&(^yJCv`)P~&XAk=q6;BBqDe)e8lck& zNR4T-3=+r3UrqP}BPO_i8%h3F{$TFsKf6~@lvWwIvvcFx_S!3q`7nm=4W6y1WaP*g zh@4^;C4VoKyj;cOK>EOrQT`LV@%qkm4p_D)Ft-014F#t}+`6|T$YM0sU@6A2M`b}) z(Zyez7&lsW^0EO_sT|`!`55WnHJQf1?F#P3YKZ+H_SokXKZG>PJJO zohf98rzzx1GNWQ4(-0ONGvZlE?3wVh0YYb52;dDfx&r(Gu&O=Esv zO;puRzJF6>&{I|^o;S*8j}EB3IriL3&Me3%XkG7T1xTx@lxYMoQC zwEIy4SqyU4n739yH;x5IS%or&X$i;^=ZE&+@%NNH{IH<+I4#PIU~m!&FDYt zk`>P{QN$s;ej24xaOzkNaIfW2w4N8q-`YRu%$p(rV#&|0g2;DD91)euWAc^KI|U5` z5L77{KjvW&rq8Qd2@h)z0bB%FGrgSOdOPV|u)mHbV^pXMJO_Ou*w+Ex%vzKXuC@GP zbrA#Fbk4JUT*j$FQE;$(-8msBy)c*q?kI#9ES3&Zu1i!}IGR(A1ibtREqCu(yKm}5 zYY%suhfSrFT=6Mqi6 zUFDkKIV=hq5-cxM2j!~8om8FbMTH}>W(NQAax363nwAmVX^;wHH;-ow9_L9O%8^+w$+8vFlWz@&! z8E`aRO91e@Z8+4%#3621T` zmbsm_%{)egcZ7>nJjQ7l`->X*S@0sn6nq$w(?z^1rgeFN*diP&c#8TZ?#z>)H}71S zKhLU5h@nUUH4Ba4n|iCSv!Bgk#hkLfXv;Xg7YCgle>WIyYzB6Fs_$Vq=;fXd)<4=Q z0sgignf}+%gM{^ATMrY+@HM0ibb_LrfEj;c`J#S8$x?@__dJ!~m4BS%&Be1tF0J`c zsfs>#+Ap3V+!Fq9H<@)sNJWuNo(3!cx^67UF(Q>t*w!NkkzKV6+N3i<q!x|>UjLiZ`RHPrE~)6{-DX@?%H z+CQxK5J3!mx|`%DCrL_M#aF8S8OpdR*w0*%Z8tjZ3RP|NCln*PnNMPmYr$>Nl44nW z=^pC3WQI$~|30-hU2JK7K(X(1H zVL8>1n;GtE?`~9pxfKtCwDp9Eg`@+2^nofUDLk6VNw-4tJe~i6j^$icpK2v^#+Cq!%$=O^cA#A^FM##MkNzH*5a8;ingwZ=uR@A*^NlH6+1^E* zL+2>8c^@AbCCJ6H$~a8C0-Y1bzMmgeN39iW6{te7jEoJnIQANRmfVC<+tTT)$A(_z zdw=Xu3q=(*2`$Vr_aa24ya8XRiI=yLoY%f4PYy9kPupUbeiW`)ePPx``7GT+uT|YO zi;25gKj{TLLm2sRZ`ftOj@UXmzRw9E7Rj|G2`n!7boa)3Bq(XFoyO>-zeTp*5>9%5 z_N~VCW0*7;L92QHjzBmt@&bQjO~@=9bBKa~4Pn%3t!PPwsUNOh^lAO4EL0O0^ZG}3 z|8p>=)+nt;Y*d2qrs@$#B4pFT`r~117d$roIC)P?zOg{zeS%ATdlK47N4PDrN$(Be zavySl6%ijLeBXT}_*VDI8o@k_`ji@1SNFViQQbV=Q9`v8=R;J_@#?uCQS^qU(XnjI znfSyTuvpm~^lIva=lq2y~+Q$RCsGWaNmvD&({{cJK-`BSO{Wz$A40t^0Y^27ECIuN_9N-rXmoM3k&ETf{UNuP-<+6xPa&O6n5HcIgd@+x)O ze@mNSgksn#RaVOjmr5YGlY{QJa&&VAFH!H1EF>PK4@&?MzOk}hD^fAb53i`JEdkIQ zq_>N$X+}Lj6}YT*Ia^s<88Bgv1j>D11)90wdC^jCkcUSakMLHZD$wPK8!3Ph=S?Wl5rN&SITAY({?Lg*f$p3z~-IC|6Y$?4h^{; zaQqG(J(n_s3FQ{B!~D8*#-#sQs~xFHtuBBbGUhyFK)wMya}TX6yO8Zpbbz5s>f?- z9r;EY{*gedW$Z8#&B`6)zH*jDY(~piD{(T{<%62+UDlLuV0VyVJeYVjHzIym}dq@}jM5r8fn^lkdPAOvCk9V6Z!4Q2pS=RW+SV?v1e_NbLSUUdl)8@Tf6$N^|V}h`%tZt|JXcG;Ezo)Lf-{WDS7h zjx=>o=4k2&`Nq+)k2wzut_t}1h9DD&G)^6PSLJd`Z6a33s#EHTX-Vj~*QyZUEbVy> zf+cLZNH{}M^Y0a<8f)K)S%4>Z$ef+FzIAdmbd@R-g{S?u+pPbBCaa7BJ5%OzwP)Q- z_;9a^YuNAF3i~-2S$dLcaM`YYR1}0iQ@wE)er|dm&oJu3+k9#v=S~>i_*IzHcDm`- z>bquFzStRSUiw+f_FPWDV)#~x+!EGYM-rSUw$qJhA6}GOJiRF6!9u{Z#{N20e9DD$ zDF?%#rn*Wc-u5BFsgI*@*a4*w6^Fx+f-rfXyGqVdTO*Ne948jIY z%;374P9nh1s(>~EB4y+@J36>`RSbM)8kd|Ifuzj~=_%;De$lfk0ogaz74EXFvlZE0 zX)EQdTe7zuALuD;E^7$-!d$?Yfsmhk8Z{j%>=?xe_P++fkAOshg)fZ;KhVZyXWN|P zDf@SAY8?oB)vJ(<9UNw`_-Fd3=#<=HqkJfIm5sSK)WlsEKBA;@5C2m&RA+Hxa{#S_ z_)O)4IWg0D3Yqe)EK+;!E2AW2B;R}B1tS(;E3;!A-kYyycA=RwJg^3$9V_NuldE_+wE3Bd z#ElB?h^^QxH;P`ZxEJ1;%~*LcEq=4si#bfLr3llIklzV7bc&{5!w6QJZu=Ms-EEiQ zfSi>%QpDj8VFxMkp@_1Dhj*Rz8ek&C-7JjT1a=$Fri5L7IjTAjuCrS|rHjw?omPfY zUP@2le*jrS=}GgrW?h_(?yp@Ctbn^I7p(LUT1ZJE1#XW~MVfcb>j?BbC``~kx4E+Z(CSpQt=UP*wyBBl~-7pI$aeZ+(Ou1ZGZ`e!Y5VX6#w!bu5Ej1+t@*U;28Ux z?(j@c>QAGy@X2)|yg{;9q z)Dl@J&M3q`Ccj)r4>^(@#QM(Ib%*RencS3R5qy5HRx*SV>C=+gq`|ZvCm&n;A{!b_hqV~)AKH=*X zjX!m9xM1py7`2@+d0gLXx%wP(naRkKf6IXWT#kS_N(-!Jqs4t#0rZ2p$m7I(2K-nn zD#O|=t>wzVIDyN1F1S-N+i5YQtZ-y7IU>w8@F6Nfa{o&Ttnm{4;)$Gttz1 z-$?99@+}hUXKs`Ux=w{U%K)Q=Ndv^T8&^d)5Q3pE%%{Gdit=tRwDFyhmLshg#JuC~l7Y0d z07fBOLWT+Gb3hMSk5cig5Q~PkrsMH!U@89i*96{&)nD z%amzevjgdzRxZ#2s6R@pY~3tG0oU@eaYh07N!ktpDs)Q4K^+OM3<{!xH?(#T1`!Nh zp!d_}9Y=?mZIJ}W+#pWS-slZ)>PYY|F&op@u-a!raP#8-@q-TzT2g_^uCy0@FsDj% zmiaT?Z}mOqhfR${D_7g~BMhb87d%l%>EJ6EZ0k0R=p+9;4B z1NnO|j#6ptLag2pPi8L_CZ0fC%NQ5VZo%F!r7Y==g?K^IhvzH6uG#UPG1lh5lJ6@t zE;^jOR$lMh5ov zGmdhMvPsIEs4-p^<}?h1j`jP!5Fx5ro)s^S0{j{r`IM%`*Q+z=+THsAc(wx7go2}mlgzM!Mj5>VERuLCA1ktIR$A2IsUECMi`C< zTj@abea0aT`@y>1Lr_R^lAo!0yCpsQ)5{_ja*`w#nY8Idd$e?E5vp_xw#8-R>d{!% zE$qV}G?fr0Q)Uj74P6X)qqZ+@WZO_1o!Y(@n|PNuN1bWk!t&PjG7pb7;iq@S((#lL zDF)MaNpI!;<$gSRem7UMrg=6&Hs#U$pMR}4J5x&4h- zN#p`-DIbDkt(;573sY7bVhwPsGx0>O*f8$Ac#M^^oqdhl(l$TmAr6UR6T;D}83=53 zyP`xs$LNj!PVc_ceq(oajo97&Gi@CYCQ@Il;c2lx~W1gTYfBf(slp{xh=|2wmVqTGkYo?&+)D_L;qn zac&(`gik8q4Ae1h?;-o)pgETdRqtxyFNS`sCKTL9R@LnVsw*y=zxOOET=@Tn&!V+% z?$Mh79*zughR7wDVM4eJ#@d_6P@#9}lCAQUvy@h_~pTnbnkj^==4zu zp3@fY-rqF<8_#l`Bd>99O=?S?rG9LE`FIhpOMPPof=(IbT%2*u>zTv67(fEp|NR>e zrunALbF5SUBq%Gg+}+I!=lssRp!-dpT=au^=xlr@uXet}SNhh{9vC601Dn zZ1{6+jbe(1gWr1g195i;Sya#+64L>rqlr~FwtMQ;WXE^N%ZLOI-&nui6>&}D3`(HD zZ0f&@(Ry_bWvfw8eA>tc1#1m6%q{%x#XT1+0#8*ZBcedcSxr|M#bP`oq{6T3kE67D zGDb;tUinoFk%Xmp`T3_Nd z4x1m`*;N}36Y;HD_;F1di-fKV0vs%PZNNmtyN^W|F?(Ml@0S`s*8FQpog0y-erei4 z6?FqHMCh;>!f9LMXzyXuvo*$c(+}?*g2E3Iofn4f``B!Q{u4G?J1TuT&fSr;=w%VlTL9PmHLx4A`zbgTb8$}7>gxk=i=u zBxg0moz|H=2CouLQdpDPzSz(1?&XWbY4=LDp?9II9_Pq7@Lq1tZCjzb-QX36p2JX{ zqhlDV%H(I`U1*Ur9vf#Vf_El8jNWcCm!BCSsfJdv>Lm=6cN`oP-XN1h!_$>$-45f& zI0%UwFAeCu4q5Yhcu0s3oAMg5%QkRJj*(hKoZo@-J+1G|~ z?PZ>sr7U5_A$F1uF*h| zt$#j%Hh&>zNj$^+Y!+=sK2jAnW{DQ~&>Q z!E0%EN^&7+mo}^aY|NYhDbKx*lIqOcntIG%afH#zV(e5)jM6`$p0y5#MkJKf3rX1( zRG_ShHn_&2S!S_6TOgvzuY#%c)A-fJ=FhI$j|Lj$F>!+j`yz}iJpFO70^1>O{ADW_ zwU`9Rn|m+dvZ&>S!5Vw8G9jTd2Hme={f>&5zh9kMNJ_By7=Rcj81V^7YhHIMy<+lJ zftF>?H(;G)mQyRVn5aRaBtD$v4EbE#!_`-0+0GX;{RFZ*aGv64h;l*N*u+y0pj3ApfT$i|8=1t$3BuT$65I*naDMyG@$L7vk0kpxws zHxRUf36o6IYJOgm_{j}iS~F&zP>QP$3f`m!F=kR(`$Za35g^PUcuFV!o!nm+Nmo7P z<*MlnSb1=Oq$ylOT{0B$vB7KJFRCMsTHth>YbGE!)whf0AFIb%8hzRyA55eS=&>!*8UOqz+*NB%O2ic(^X@&&!2@Wtr$ zUktl|)i>#T+4Z*HGCUw$nQs;4?l zL-j62Kau?jeysH1}%LIWbjK7{#%TGlySU?;Pmq^x{@4v!?Lkvr3Zzkg?(|MptYR zYK_`Yz^@wU-~B;>(OrtAL0AJaNv{oZPF6=aOvVE}E&!_@cwTxZMTEo>0iNd_v6k!W zs^w5mb(_=Lg`{nXGdN=5#|qU_U^;IP3lKIGhE2zvC|$#iA5>$5)(|u72)R?)wjWT# zSIQ59LEJ2LmL#^BiyU&Ok^y2Xwom1SU|ph^wm;p8;?3g8cV*)9anVYb8vX`Mx1&lp zMNb|7?z8d=d2SYutex1_diFVTq7kRYH4RLwr?ocDq< zuC9YrBgX zb3}3FMMbP)YB8k&W(j%(87IPVTc8gM_s^zgLu;y4s|+bdCguzDL_8D18x)Iskpe`c z-S2Zc2+&DF$U?} zStRHMU1rf-=CgZSlK!9LvhK*^PhybB0=5E+eq$cue=la;mm$VYzJ&sAM8`f<7HK`? zaetO-ungBhknu9*@V5kJc;Ps@g=x#blO@ZgHm1|QG$#MP6hO0!eWx3Apdlq`d6^1H z*GmNG*^jSNV_?dw0%MJfN@aExqUJNS=OE!dfaw$3zjGU{eAo9J0M7MiYtSH_7*D zC!QYwGVJtc(yjci;QhkUOX-Q&-!}*?p8?tQs*LzL8tn>W}c(EQnLz z3N}E?Awb*VQcl0wQ6-cO)pW!r4|av3Xeufis+d~t&k-*k9sG?G%1jOMG(NR@a%||txPeznVFrph`K%PD}4Jj zD-HY8H!=TM9Ab>R`z#EiA<~9S;W5ZY07wPF3F{mSev*HPwlqUe8VGE*0{k1;S2<6S zy&!^XP@gU^0hIaQmV0gO-^kRhWI3hsRMrl3X z&Uauj_KroT&0C>1_YZTLI?1#+#yO&=7dg4qz3eqMS9%y_1`?E+LO5Ix`ZR~&n>)t^DtbBj#utm;A&|{x7x+vJGiKYdKi52fanAY7UVv{B~q&=-#@x-r!ep3tx*uS#L}+5J%LEf z-x6H{QaVis`^&k}1sg*rRmIAOO@0iaYcNr#LB1^D-2{1MV8O3uO0(k8v>shgJSc{D zW4+PO7yicdLa&jRdmR=cYh=1p=9+}3;tZ8$j5}m8P&m22DJp#{ViNb?*#u--Jvs2O zn_R8<(_wRh9Xr>TWyv|y+?Tq|;~8%rI_&M!BU8ug)kX%bJ0M^mr_Y#ZH89Y};b&QH=e#?lOEm#IKFE+8I|UN|_;FR@oWviRwC1Zv z)QN!9Sntqc_s8#PnWg~AB|lq=*JYCsp(b`<{Ct!iLZC1TF7$Hg5Qv+JD~ao?IKr?L zHv>UNcv(g=D%p9I-@0~)E+O5_lG{Jz-g}G*O5HlUv&x7KQ1iuM+fDEZ^lVsAi~i=( zb;VI=TD8T2bxo3GQb>j!i(ts}2yoTa+1gq-Y9Q4|-0{*%%c1cnowIGKP=-i6jn960 z21p>+u~`z_Ns(S{4S&((xZOluJwvcOJIxG|WRUku6R7`!O;pInAZHadC#X%KHmRl! z;3+%|i2wxk_sKOH0@PWLRKSIiLa$nO43VQCKmtZ?8flBI>`i_G!k^b_eMNH0vHm-R zpUKy&Y|xCcC$M2FRkb^2Wy}I2{pg7Nu~-6p_Ka7p6CY@F=_AdJJtI94$#-9gIcXpE z9uKvzWHe;~qgX#bu7+Gkpgfbf>i);@!PH$J4NKGkD_gDwDc{P&<5ZSp!UZgKNsvEC z!Ply3?Pqs4D;57ML+)EUL2^hp3F?n}tui*qT2=3m)84ki-cEQsL29AjG1gB&@t+7l z()y&}c3QMuwP^IiH$a)Y-DdvEoK@qLGAK^{+gn6TRYCl%^a-kW;6F?{4WHKwdHE=B zN+o{C?b{CUW1EEwD>}&L;KK)Bw;TVJZ<{N@%-{T`(t`;|js06~H2v}{hza>O^R{&WH8i^gWsx$;xRt;9q|1yc*UnnG*Ze~^P+ z0?3!tWm`Z=Dz#NMdu_NIv)RoP8CT=vJZ~X}6Y!?3i`SdLaTSaG?p|d4?@HpQ<@tOK zwryk&em8arhLM_r`|_M^sA#7O8KxoGRnYAw;nu8ez7L+s!a*(>_j4SyMu0>i4vAP`%i4w%EI5MkRuLkTz z@L5Mh$LB=rn+@$)wLk(9g#OV$ii>f`!s`_^0hMaaU|SY!UP<68ow;3nif@0*xgPu_ zg9e*{M5`1ZF?G(Q)xcQNa%w0oORK;OlYe=MJ}Gx7_94mZK`J3R`@PfAULm1NSo?gd z&kRGc1w9PV<2O#af5)+c5UQC2+f;-*bgN*GV-@(-y7{wDuA z*NLDYo3p7=*xbR~9whH`zUWBWVh5unjo<|Q)$xmWUann!Gl-uvw5sbh$%sVQijcQ$ znjiZ%)9)Zt)nem`cQsBoz1DsiruUiu)t0!Z&_@V2UDs9jofG3yh#2auqU!XB3W*(g zXj#uo?cB(o9;?qlwT|LbvCfob7vI$eA71c;ri$&y9FixMZSBBMCxD&7n*56bB4_sb zsn~08O#W^L$DF|5suFRtc05L$+_%$Ww0PWRM|D9m5-4?zC2}22G$XO5;Aw}CCr-$m zzQ~0G$wzDk#+OdqN9sC47i;mLM|H^GTMA4|)Gw8wV!gI4D)6d2yT5vK9s`1VR1m{$ zAt@GOZ{8)$YtsJ$DLbEEjsYhdZ9;V$;NixK91!u6e89rH(gMbdDG;?xWdd2k?e4cA zRYR?E(z{uphFZ3DHS{wV%~FVQhDnrcaSg<2tD$?%wOnIp-GbkSq49<)@`0QD!yC1%JmpcK|Iw&0i7hEK++NgD6B%ZIff7>%10=-?mvI!lI#bgxI*0Ef(*D z3qma3N!>9d2dcC`BeCUBQA@N?U%T!_*g3aZeROPqT-;s`b>PX0W7lm*c+c%$tb!4$ zjUM~9=WR0u3M%v0lTP5ql$vaso`B>$k-=~l_iM+AzPcNPO*QgRcQabhy3b@NBgNp} zzR+qP0Nk0kYDziPg0#ywqXzEg=Pj##%j-jyL($a1Gr0XB<2PZ9?M0h|n4`3y^y0sy zhXoc0-;zfmo0(5E^Wmk_T|ormX9s&CCRn8vt$148JUbdi-Mi=-6_hp21**#)n0^^U zvg$6ZK_EsH*Cf3j2tOJ^!vbSkE+Py(+#F;hE(aeKnfLquICrrc5N+W1chtZJtVs_h zIvcuSKzWX^NEL$~%wXkf73V z#ILg&6oii^VjRMRS-J ziy@pE=Ps}aD8kd3-|hcsUVv)_A@W+`<68ji#D1n4TjEgv4 z7+KFRY;LygrkIIjc6n4;_11csoY}fkVp0T{p2az?jyre$q`D$~Qr!QU_hQWoV%H(H z1R(^J$5y>_^0>^S^Uj7;BjR$Gj-TsU&~9Bxr%}F<@6q}@`1us#?|kRTfKk7NU|J)a z(InV#P*yHeu%RZwKz*mwdB~M2G3wp<%>i6v-$4mI7(@P$1r54rA<-*+9um3LtL@FXo(1xbSLl@6v#m93a9}L^Ledamr`4Rjn6o>3 z|Hdq@Aenzk=5Vk(?AU z7D~M^X3Y%vbvSavC89F_2|_@rUPZlxaohmYr}T=d;qtYSfFdk+%P%XL4qC_!@EB%{ z$XF4&3i?2Z7Uk_;U68!F5Brg%8mLKH-tOOcg-CQZY2&Uf@ehY?JfSdwzXItW3~%wY=P*iXI=OUTy0J!*${ ze=Y|9so04P#hIvIf@GnPFQ*fs=NPd>-Y*gFRDEf22In!%3NS-dPlhR~bFwJP?Z(9} z=y9P3GL3jEc5YaakJ-x3hTudPoKNI2k}lZta+Uh+H93c7`VZOXEqXNm^Jg+QgKd%* zU^`?qwA47+fe?AZJjhRH?ob(6XxYfg6b)HMuwj)pg-D& zJA2WZcKp)~KPjD|W9&9fDvv;5i}$B>r-ufT42f+w#0ZGeq-(9I@!vzi9iOzB+C?H3 z{To!X1dhVyuDmYo$_fAamn}dk`W}_KmW01>+;GGxmuJG^IBm%op7X9cYUk#^2|8{i zXEroeM;B^67oFftxo|i+`xRAAOHRyei8D^lQtD^6k$D&x3d4R9-m(7KA}EhOg5X5b z0k-*3Ugix`xtO!WPdVatxL)|7)ip|sTWmHpX<9Yze29w)&9D83K@j<5heP?|j&ZGn z_8l~(@?)?x_M9^@mPc5^7V-m_Y$zL}@OW1FHs z`?II($1`Z^WwYuNWKYHzA|A7MSJF9u5dok{pRUbC-ov|bI23uc2j)Q@SiHrK$@bzu z&$S|4rpgybM@dhVX1I{K%g<5wJ!b2ruSZxQ?sMVtZwd%Y1OCtv1v+f^uZQvwbs7rI zMjW2g`EKzy!zdvaCP|d|gQ!rDV%#^Z-RvBbKfdyuPYW%STPoGnl55upxi-Zzccs23 zQ_vX^Mig1r+svTW+3%@Zj-3DiWiAL5W?$^Ws{6xN>hYyX0v0nNO8q&{ls@)dZgN4H zb!KD5Zl9(=b{jI@!E40I?(kW3BA_Kx9S?H3V~Y8i{I*%asNR}|mg}9#RvpWR6H2KF zE&qX9gAbP9y)}Zgq}C$gQml%VN7b+7HTpH2KE(@II84$wzU-ngC!lV{n)YpHwWzz_ zRuDbk7I4R>ST@p%Mszigyby7T3Xh)sswYjzG$E0^P&!YpZ{-dY3#nVAgnLGpL_?Oj zX$8W1k@79G`u?lOL)$ww2e_h060weO2CirjUrerj;p?8gL<1+Ks1dI8|H_IDN=V=V zYdH8^nzmfIauE&>4_v~)j|{DeVy$hq&`K4elxcQ{41$Xy#}^_d*>_XB@9>hfncv9*=?8N zx7~a!8%ds15a>CRkDp21+~vM7+Xkj{$VV-HiPK(bRoHb{kuRB4M}8jE z!gKfN?~lyWrYFn4@JDo@->OPC4JO}0@8idPRB!+biCp0(4xP0Wzm}kV?n#ulGn=lw z4(s@u-|_gYMTcqfwuFh73~{54l?N|WZ#i__E_Yc}M@N&qR6`YD^um-jB)Od=b6&H zg`AF}7)*<#ej5(a*gw3L!$5r-yi~vmu;c>39ciUkrj zvb|gz#(UY*ip8w!T4_5@WT)&mNJ5bnBCmbWy27jU%OVnWtEdZGuB`@_w^?4pVJ#Md z`o-cf#Gzr*q|{!0NzjKCD)F3n_Kjl8CtedmmIHg+*--&H8dGBJv#XA?rxMnHYEGC! z98iCzeb*TJn0vq-NM39t!l$s8?M^J#Pp*l`f>xH73f&05Zq$R%NL&z6He9E2C!yyy ze;x^DN@4gZ+02h7cuhCYLx|F;4!CO#RXZn6NNZeBCuq?N(E_V8A6r`?003&TFd5}i z<~7Y+_X^SOK_ehif$WGEylX7iJsC79@#@vVD@2G_R36|Y!-VwBNsge zOIg!@o0ipd1X$jvzqEZoqqjJ)(anr{U9TAs{d|-(H7)US`%!T({SZ4Weq$=G(=B~RbnW+QZ0bIg6biUN`Pm1?wr(`{nyRY~ zZVxcwAIrz!N3O8$&%E9&M)_%1qG zejbQ(n9ti%zGUaZ!@r;+U~`EJDcyWPtXn0GejNKm^bys^gRB?{cji?GVD+pVpw)2L ztnLWjkV84K+$meVE~epPVDpf|8gzgg-WgwyUX))Ra9zHHdgc~CeGz|tDDDYTODo4$ zJ;D+NhkNJqRKYj0$Lcd%-Skg+x+~#y*L^ANT9%*D{o=uwDq^i8L$F#e^T}VM%5e*c z-Cg&@=L5AcfST~*;?;iR{`w$5y35mP!`~z(~dV1 zFRi*I0PP}~t&#AWx3d*6&%bh<7rAJI;s!>xw0;-F;6AU)+g8ZXVLJVUkb7*{jUSEP|T zQ9Z^UvGjm#fV}D>o?580WOq92e|u6d_`G7His9O&UTp1x-hIQ*FQr{wX&f{)sQ$J&pRnE@G*>lqfFOk#iaKRV6weJUnG3k|cEB0Iu2d z5YYRFuU+uBO8h3QyO!dQvKE~Ty3_T?zeI*#nH3afCiw*7b;YfY&wm*;9T;A-mP0a? zt`;%41@GrAmx+P7Hf;ML`1u++=bK>avdqR&!(cK$_ABrD@p zW{vH({z=EE+bxuLm?ZClX<(R(EktviSZ>S^fb?4qCo!r{Zre5OQ}hYmeU{ zh9-R1w5mZd^x{!*#|hhK*W0^BSUw?k%~YR(pXc=IzJj)f;otC|ET9S^yBnBhG6Bwwt-hPU+i25n*I@2d3y|vwOB&s zSnLxTcf6r2P|!!nT0vR}>8Otk+nYg}Uecz`vQp6=E=FK`sa&XXloPh=n^X4yU{S82 zo;I`KdEFoQ|3t{_$|Lc;WI=_y{vZt+%5ekrxVms*_F9D%O$A|XL);*%$ZbLP%Js$| zRIyDeMWteF`^&OMYSN!{mNHkpl5&dnk&IA>wo==pGA@D+n0Q4Y&C9qc-Y|{U3|ZN=>`-}%Pi$kh2Zc6spde;O9;BUk!7pE0e!}W6&uE-3iU|czl9^aL-IBE zvK>_9Dre)$TeWVc-*AhnB>t+En2NJxF;M}gc8j)z>*ung9MD58Fk(U!HFUW@QYlKO z3VtlWsHrc)mw>4=XdO!q6W6d%us9EM^K(SNoj5S%bO6D*hbGEo0@Ezo*07 z9oZvv9dv15)|AJ%Ld?oCTZ`e{Hd}B88G$7@IUq-*+4;YQujMjC$9&`o${JB=@kXnaa1sxK)V)E@M#P3q0 z=Gb+?19F$ePfmDA0QX^`t14-xwwSPNAWbJSTUJHci`xAn@2Mb9n|Qd$k5SzpuDAc> zD=?X!PA^uE?WzS(pn-J-EqZOfbu2lh(}h536iuX~EUzYKWj2_8O4Y|hB2Rq{wse@R z_%1>1KGd8W%4uCxy3LTuzLself9V2p`#jmRihzA-gAURK=Sa~es&|*^a>B};&HD}^ zT&$zm!DN`L? zHxjL`{x>r z;%dzA4g5bK6{!3G7rx~6;$T73Fe<6Ou1oPzI!QNJr60AbSUx|BdD#stS z&x`^1yOD-qRvHCO*1-jx(?*P>;R+#MmTZ4$4D?`HpG{W@i&h9SypQn>&SC4N#8?rl z-so+E&K&s4!qn^Gj0Ur?YSgVi1%@>=wk|ivGMjIA6e06OI^z?=n$L{K`^3m`;s*US zb(ZshnL=Jr7vyP;43emV2$bH(?O=$i%c8caw(!>#7;IdB zOIocl0T-7AI>FrDRyW;~otOQg0jr8NRbds=bzjgHR!sH(-rXa?$KvNa^IV2 zuVGQ0rcvF(oN0#;qehLyH};Y1%DvgCO+H^ReY+nm3zR7H#NtgtcftPy3}0kr_9L*m z#lHW%p?$A&3>s)(?p?~W?=)I7Q5F5|Wz*nI&?NN#wKDNtl4o(JKSDEj-!`%3CV*Sk zdsdUUm=2colwa^BU15`M7N#h%!4Uj_`k#awqVK1vxy^qvUORN^(CAg&S~j?~0Xzzetz; zD>A)sgCCr8=}WcmyMPQ>K2!*0ncq<9A$2vj49l1# zv`y1wLXSHLrVD@f`pLe@J)=fT8*kfrKv#P8uX&%DxY@>pji5OU@HY2LL`ZA)xZ_)r z))zf@GLvhx6|B^vah3*{WI^yeR5ND?HJgn7?&giM77}gWgrhMMW#*Q;ty*7Ix`kU3 zK!!Z9wTp_{?zzpLq^Xq{Ts8y>Xn@o@1`{yi13O+r^0;>zTJlykF@Z;)>tFaMcO`$P zZ_}gDC%Gf*W;O796wa6rlcLSgt!5w(h)6sXDVuDVl&30PQVMo+qQ~$Kt*`iex z59spog-C%IDV`5qouHa=t~;p=2=O3)a`pNkSvWK`!O9sS-V;ieo>-Ssxxx@EWvNg! z1X4U)$2LOSW}WqPRJv>}u|+evcY!8nR~CtvMJ{wE-xJt%vxpU2qo&ZZo?qITEUN>N z;aLo+D=C?9yp}{GC9ieOtd-U2%idR3R&O}*g$Qg8zCIPB*7yIJXtKr$!*K}BO{g)X zm38$zgO-A(S3tJ@?IJ@&D($jv#BJww{d&U(f4=OJ#eIF$!G%vJG6)fHn^~sWv=pKM zG7dY}!KROdfWRmjibha%M|Bf~p_g4suTu3*SWd@;fxOmXe@hggAz4ecSNH$^XQt*G z{qqF(nuAcTCQJqIMdqkviDzyU;a1?7^9k(ZjRl*(P!XJ=J7-JnIryQm?#%vua+IQq zw67hdUd2X2AH+eZL4P17Mi)tYYYriPu;k7ABC5!8MBYPk!#xX65S)XphQkxH8z`Y^ z2R4fhH1nl~?7&j2A#LoH`Svi)V1lE0i^hm;SR&T4@30^x;xzYBBB`I|qE)XJ@<`aj zK*^>Xik-WRnmT7jboB(P&cZ#Pn1k7a{BQP4Mu8(#ONKu09O^}N8o}$X8@q)}Oy~M| z%qw225b|!N3_<$supI?&`=`~CSETht72^{esCbSY7p|SALc|Lx8zG4e;aUeO+p}Pt z@@%aziGzpBg`4eioiAi=!j~_EpUbf7__9n+TYbI#6;=`~G2SJtiV!`iS$05byVtUB zTd+Bf?k-r2gMx5Bj^cyVKgLIX=iSr;r81na$bA6oY(>(17~{=+~WOE|$-8E6TlzCuD}Pi+Lea4cQckW-`1%{b^s|B#QP!*AB`y z47>-(B|MmB82=!-yGUWG^OeWvYBAw_Ae0{4ua2`(q3yVWL|u{Prn;JFUPYOOiLlH) z36Cj9M0HD7S)##p7{Pn>(>TsTkRZ@xeF<_|qr$B@?YJx{Q7y`w%AiuiBM;0I)doBk^98c~BPVMM$kJp}_t_>-2yQhlO<5L~V_aHmNt& zcC-Mx!DkuWE5**HDgG7&0$*Qo>ivI5aq9ISEz!b>Lu_O2BM>UFuy8}(ift10R_Mx_ z&elvoikAY6V3qn@F`N`$%tn(fhqKjPuMs9W;>dsD7C&gMq(0So`q!F*;gdehWN9gm zMp2iwH+~uh!g`kJk=@O&56JN0;&8VaWtWc0V}A|{XRsolEDR({#{?p0ik3yD$2wka z_IQ|Wp|IrU5GK==3?roI$leQCOKowHYUco1qwQJ~c-8Y*iGIOji)FxRh8NBV%z)FR z?j(_}j@&2oWN>*(<6SjdX~Xs23mo-kkXpc1_@0XJql8$ACY72OWunaA-#l<%Su3Hf zeKQNY$f&$}pZ*AzC+EM!M-#`4;#IBy`vN&{&mq%qOfp&_&3ncwxpB})hL5}TeqmE?p<#*EqnYgmbCy3LKl zq7Okql_A@fZ{|T%#pL%?2B!so*FhR#3YJ+jd#rXc zncv;OqsBbTFK7#L?5d`l9cQH?UI!z11tG{lyJF)gKqw9rf>CXDjvX7d92LJYj)CQqR?!Ae6=NlpNfK9C2aj0Y#O)Bb;moUO zr|A=f+r-`I1!b6)PAM;dwhg3uu~hPrREeQicP2VXym z#&G8}sG<1?YXyeI0c^fuZ%PCv1qb^w#!|TQ$#mJ6CxSjiWX{o;s&#ttm4)ox3Pqn& z1t7M4dV5DyUx(w8M3`n#CU-X6XWs)Xo7L&=G1=Nw-}V4&eEI%IIdwW@^7C z@o-0{Wz11k`l4nZC=j<_Uex|+VMXQ8f-3cAvTuvC%cO=5FU>A0miRfD4Suol9B!?| z{;tGv=4pS+^b(gL-bOj)HR8Y0-;O+F0`RH~$~i~a6bksExIDSCo;qkeuHx}PhMdE*+3h;U-B%cFxU{=skG!mP*$1#* zN$cnp$x_*H^mmIA=ke=QRfJ$$qPSYatxf(+g=vigB}KZJ=}z_zHPJ&cta)pilzBT7?=FP2i0;^&CLM{X@ z_EkE!Tl>+_lbvgGwTafg)j@WK`+gSPkl4iT=$3(bEddr@;|A<%UK@!q0fIg!KEmY=;B_~~ zSC*qh`!<`6LOMCKonCywGn^z@fH=2cD(yE|C4|3m)5V@i{NJ`Xsx)Q_Njaym8mNl? zXypwfUzfpq_B>XQG$?wH1!9=#G28&`$#)3_lQ_@TF>dh2;Or=_7F4EyNFn!Ev6u0; znqeSPRxCY8()Bxz_AMb-gD(1aKgivjdwR73GbWCK_FwRTDdk6MO9{o{yndZvyNKAi zMZpmgJG9Qj;lKddv*nR11rUyp_evgemS@~habca?QIHRbwO)@u*3>gv$r5cqRS{Oo zfs}6?V(qW1bZmo<%iVwg`SY`p88oWq4JtKEc+$$&1Syu`n@ZBKYBR5!eGMzv)7B_* z)r+%X>g(K2?9q(k9g4IH@dG^sq{?ci^4tM45Uej7@Yl@Z>X`8eQ zP(+NF8O^9Njr^zxsQfmilq9+$besV6p%Vu9PSCX#UJLfTy+E?9N=k+diBaU*12K_D zH{*(|(kHO+L_r0iHj%^$B#5}ntdpqwbh=BJ`NC{Pln6IHFx2_FVBiIw;)=$TNzs#R znbebIvOS*x?jqUKJ&6%dF*yh{Xht^FaQj!b#T-%xK>tDR*3@VVaq$eZ#j>i0Q`Y7R= zxS0_r8WLkgJMyG>3M3&tPtE+FHkJSQXU45XN~7LgmZMNjkUcj5#U+Op5hQw-E6p4L z4JMtwHn(2Bkv{qp{l8aG9L%rm!?mEgjhI?VL}hNdh~pVc$Q&WDP5x8)udWi!sr2el ztl-G_;7=ih-{Rw?5+j7-7kD~u-Jb%D#jwY8mqvtHET7XIVnMRbxLSqL4P9_-t&ABA z)**&gX?UEzg4hYH*8r?bI>W_bpg*a3lkylg_-axB7>CC;^2#OK3_39na&sIKbLnq~ zj=@_uF%JOxs`@Nx?6xB4%2Gj33HtIOceJTeK356y5)Cm;Ix_D+5~G=-57@?%+b^bw ztv53Al5?xE_Gm22eZNG?L5Q~it@yEx@t zMe(NtN{XLj3EApDbB4n{B1sGpB8Rl@EFL^0D zN3r4F?k_Kwn-ddC_sx*@pvFn_O7pc7reK#W`dfI{ z{Kxt@xUxx~1PVGB8&RIRvR=Djp=b@7(pauvD7XBUV)jIq#zQ}W6`^)|l?#$mbY6j< zX2w%1hy(GN7p5_seRK+9Zf{vqsa_p}g3xk=+(j+04Cxn^mb?1OC{_1q;7s}&+fIQ_ zfsxgTK;RuZvNb|_DffSR=5ryYD-H177$9XA!gaPKcB zrXQ?Jb{kpJRUO~n9MvJf?Qeh7ecGO?ES{DJ60d?e_iNSq23orO)#2Wy@W+$~S8*ai z+#lMZZ(sjqfj>I&0cuxeh1=A>n+7Nu31mutBQx%H7P25N>weP~;zv$Dx zmKo;Ri`#DMsmp0Qzqm!5HwktC(7XI{a~tVw%@L`($9O{Iz`4cjf4eVCIC9)P3ECF{ zj$?#6-jGrmPIF&utZu=IiY|5QJ4+{L-UL(#&poi)RfYMO$RW zXKvm?pvhkG%oSQW0X|ONRF<;`3s1iJ&d@e?M~_E0zHHCiOZX`v*XNttF@|SN>oUvT zfQ38o)IV`B1b73ihk3&q;V7`BD(W(i9Fey}jPlM6QqzUG?P8zk7+csC8?RIW5tusA zxmtKQq8r`;Pqi<>W!bf7{07G@L0oju@a;o;lr}uC)EtV@Rv>~9>N41=-X?}j^)ikb z(@CL{;J90k<@pbSn=oqcq{G-%7ZP2oZS{f4=oBzuNRw@Kjjiq#&_Sy)wqzuI5JMZd= zKSF7m*rWtw{S}1fUSU&kom=EsFgPB54DWK^y?cvbJJDIb131AhpqM)foji15HjZH? zG0GKg6QU7=uya|pLj`gwjq2&|1iuPb;Ha$Hy1Ek!?9~Rx!;+>Q$j6c^`jB|H{-%N>d%t#O6-BB{MhP7=3ETxZ zJj@AE_yLAiu2JrPXeOrVuf)!-^%CzuV4WlyvO$jo<@3k*DM_At{5p^s_W%QWsT9wCK;rE`-3qvO$x5@Zwk86ALugj__L}d1Enn zgPVDJfk6xh(!*#&pe*L!iXJo6JKdY*C9(ut{!t<-g7Xct6~2P$(t=9f5WtP-{ruoyGnqy0`xd& zjjV?Ue9mg>UAu}577FyLjQB{+Bmz$IHg^Fg8Y{|GCt^VcP8W7pMwYcOfZedRS?Cd) zz9CH2N{SE&eAZQ<@tR~MoPQR3)GFf76h7}msV0|>t-SEfpbP@RCfYhHJ~u6rhv= z8@$B}eppXAWSaS>vb7eSh0z35ek8QRu8axRBTdDKbxD3u0h{_P`atZg<{)h%W)~-q znc?vM;GopN_zJKQL5S0jvWL zo22W5Vc$rRp|Dy)>SMuI7$}UPm4*Tyv&>%*30pJ3QM96M_qZhp&FdxmCO)1HB#79u z>LtB!uussFh>Ub1rex+qK6E2A!&K6&|Dd(iQ{&kfqQ?9$%2z+Av&GJqtbFM}ut!O2 zEmlr0V<&PutUAs+|Iwu4e<$2MI~bnH>)m3?Mnb+>TPfnEew&Tn z1v!g*so&K+vh1j;_irAb?B-s|;kceprsKT?249@^FnC3;DQ#yF@(xT2a zHGU)%{6M4dhL-at7wb$FT|p$cJ3UiWViq>$ZxN_q*j(cMfQ@ZbwgqDi?2d%&P-xmR zmrpz{0n85Cmgz2??OI^im^759lRW7@$zCyQ$Z$}OwGk6#B@?@{AvtWFsS%IVc$G}XO7MIxUxN%go+26!;%a#->J}-0@3!V{Y?)YQ%UNIM^<4iJB`;DR7DJN|1PFaAkBv^I<9>lK;6L6ua6E#QMY39S_EJ>I zGfrk@v>G&XIt>N?RFSf=-n+#xA?8M$W5+LMq<&MH@%)$-%b=~dT09wGj{` z*VLv6JVYvOeu^+Kz5U@*n7VLw+K zm*P>r?#+ulSv9>9!m`cZTyWh|4{!zNdG|v+W;tkf0vS&pW%N$htlxMH{nPFNccA?9 zOZk|RpcxmVB#O$NU)BCE%=H++Bl>2)M^pnNvY*LR)#a14KgR1j54c{tCqtvcU`Ru) z90l}_pY&P>FswMq04e`oE$Hb-d7*kvsJye%l)+7767E0hv`nX&u=ZdLuYN(S1qVSM z%9pBx3tjZDOjE>kJtq1dUwj(J)?-5N4CJJzrtH5SS8Y{v>g4_yj*CIrx84wHuIFlJ z(%r1&_+3K<4eK8ZR`HOjyTkO0vwY(Z;vo|3`+x$!PlyYeRnWc}T4o!b_hybDx~sKh zi>X@e-tR;^I)&nPJ{A#zLH;}3!v|1?s&7(&u?QuevKZ=Br>kdmfq|eW<0!WaDi)VT@>U{yPh^cgaxq&bxxJTK}g3oF9%HGM1b zw4%+nKDb}v?zLN+zD(TUhnWL@9rb{t3dTDYS@URr-Zffp;c$if)XqSH%{$-uuU|F+ zy6i>U4a<-muNNEig|qv*^3JhNUXV&j*`fmMRoo`9^hw8BYzzX%jku}#2^g`oe87JN zE1C5(Pnd0gG2-?rzj~)Vn{I~6lT5XxMWkT!^&thVnWS=QkRgmI{fuv6R+5}TN2$$T zTcKt%V}q$k4cKPq%3?@dbshyyu6;!mhDxpHhBHwMjs4IrHXmQq$?+n6$Yr{}p)4>u zo}v2$IWpGZm6$LAAT4?Hg^iF>3WJ{Jz|9E#mhz!3JmwguY&~>Fid|J0eUO%VBiVjD zbCj#1u`U@WJfTX=jtT{TjNZ72uOG;@EVof@pt3B;C|MyBDBnSTGhqe(}zzG0#rBT+6GG}bFoIYNs!erM$ zGsl3wy&3t~UdvQ}(3KVnSB*34H=14EU0ZzY{oiZMH&^ZA-4e@8+66 z?0v@9AfyRRLL`${#t;p4^G6}4-mb+F0c1%2NNDvW=0uJxw=lC!ZyN{d;f77Tx90;42X{e}Ue2n-&`~~A9EeYJ~U&ojuC*V^-mmQqVzD{Nd^JS9B&`n9mY3OL#L_U1g zAfn|AE*Dg1SR zZxWWgD7VhTWDNwr!p;lDj#AQL8;EQS4wIW~0CO_I(=aK9Z!ZZV!ET|`OEo`+8n z9QTNM4EtqqSe7j*b1U@O?e^+FrAK6|w)T8DcDR#%D1lJ)qu4iG(O*9x&uMRatBbbbcU00x<}GE;E>4wb)VBWARZh0}=8PHw!>mZ7lH zS49vdsRQNeEFC0;A-TK=$O)@FUnPpNV&pUkAmj2MiTt{RJ4URnTU#`L4354`?JaE` zKE2!>*@lVA@`ehCP=@D3@I^;{M;*6O>4+ReQt}bTKB9{@$V?x?iy+LdW$_4X9}LzY|7%ZY#p-+x4vsMG z0)r@)+q`qvfc0Zj^QhCaB|6usBl^SS%0f*Havenc9>w!T)K z(VBFVNg7c_X?|ouU8beKJViC~gjVBcBs7`a+j$R@?YO3Fn9@8aZnfiCNzCY$O_v6D zJJ$j5p5{;pUKy;5c{{`IICue3b?7S74mD1uvf>XlfDXmQoT-b{XN@W;#RlKMT6Geo zeSM{xy==3J4{SKlbh}!dAg{(1dY8<{vbU}L1ucfLqd}0LbZ->_f~Z{J-p$9uSw9`B z*0>U*T_M3HSdAWmGR^N2$@a%PI`om79Jqo`)s>!{93AO6mI`#Jk&mzR^ZfQ{Hf|E? z8C<~9gNA==>*8S4dQqt!k76OHO!&~b!CFKgmd)}iDbBb)R4^m9vrFhG+d1!q z1vN|jdJ3;NcvZ;RQ0hkbZ4w)Z4{gn35(^+ITUwlYkV;OxbRJzBnzxf{acIEgMqczy zb3ppybjo*$4@wxPGSl|D{0N?2H7OVcFh#;yt0&(tj11v{axV@B z*S+~Z{+z+F7?>;ekA_!eK39Syxw3`;dq{%%dEu#?p*iLI5rcT2O)U(BZkJAiL`Xyb zu|jBIT_FZ3*Hz^~+t*R+h7?SZV-@uDJ<#zraY=mk&un7L1xC5co#%%78IKzxMjEDE z#>mIX5b`qbqdC5TLfrrHPB^|~j!$+O-%}%)@z)I|IBK&DJ%)rss~?)=z-;>&{!E_( zAUzqJ;Q%G>lA_FycrZ6ZYbhA(oyxb@vYO$!L$c00MUCvy*=~P%Q}~mN(zSF4W2evb z2hadFm|bJlLXM#56ddaF0?cX7KMmpp;RQe6#BM#y;;w_=z$IEPaCKY7sQP6lOJuVX zY*_#n))yE}ZL+COY<|(=4rsWxJJ2V?wQZLHFIaqjg^|i0kf_lHo5@We%L7{M_cAHb zsy5hj?*LxD4eaeKb8fs>v_}~CC+z#ZR*_8oQmmEztIz%x`l}!Ii(im;MqI<;oOWJ*1#bAkSRZjqBSEmRs)e|+DxM>_{LqupTlUfcj9#Z` zSX|w`dJI=+Uo;_h;8`iqQ-sFH?Sy=FqCf~T0J`Y@zVb$^XduuVjvwk6Y!(I{Ltf(2 z6|_S9^AJF{%aoXDi`A4&1AnWYMcd~@VK^wqS- z)3~kOYw&0;6>?MtQ0d(9z{TJ22D)hOt#QL;UUtxGuo*L2)L(J*=`#OHeaPGS#BKvf z*X{2wc1;fq$yd!ABd~SXM*=XRf|Fk&m0{1jGg$BSpfayn{&j}gH~l$HzMnk%94%X> z6X|dzpg=9X$}I;eVs^yGAXNq#Cv)fELX%cI{ld=U(VMRJPcCSC{xrWT(r6;29Mz=iU?VY#5!|C+bwc11nOi~tJ`8~Y-`Pvc z$hjs)NZatiyKn81d^|bwN$>M=Lm~`qk%&!R@Gku5DG+6H6=?a}V}h0!VqGTfSR*)I z>Ft!NYIZ(SPlHZ2O|H~qYV}U;K!`l;6Upy+R!{8QT{Gn*2>$|FO#we>%3xFEku~Iw zgb-(IY1^TrTqD@E?li2FxL-o=U3A4sOSKE<;Kaj0|AVy@V3$!uP2Dh5cMFg|X zQqX~P(}7~v79t@@eOwvt}90R&_kLA1i5!b7z^((x^%W{7y>)HeK% zmWx2|(aLAzv{3IQ)vRB}l2Mj55K-C7=CaV=6b=-Bn z2Gjd&kw25(UtMoGV#bdHgiIx>b<=Xf+PExGrg2ZavoA{hKIf$=tfqopS7rvMcB&sx z*dIu4XyKPD7!d2##rG%Z14CnhIhCS}lTVj{9UUyKuq|H<19F9*=bTFfZ1MR2@SOmy z99n9269jNhCPy_#X?zzv%6lfb&{J}+%(o^P|6qdDzaBbl@PG|v-C6^~rLJAKY|WB5 zy8XB&lq5(vxj3DBRF2Mnvj%6sjVCQ7dWP=f{hnSd0AOloP7<}B)3_N=u2WGD+8tFs zAb2I4ayf#C%;wETS3_1~p zwEyWJeaEytRmfjLDfduKkObm-jG27ngO9(+EIZnk530JI;xC#E}S$IzAf#s5A zJoY((`M?@JiElsV!uaU{H#!?a*9pvmAu|f)h2k5Qxj>xXwoLtU@%j zxiw|}k+5CWSp=2ekZ{F_y#=EsawS6(9D&{ZC+}})=sbRx65Y7|Y)t~l(5g8-+pzL1 zc=M5G^W0L#d;nXyF$jWJ)ZRTCBn4q7N5S{6EEQy!S0!JyBd9MR)O0px1%HzCd zDFK_{8(b*KxfN|%o%N_{rYUM?K?l}9?GndEdE3)5{pJkRu>n~xw~d5 zRH>75G&gX;=XdhD@O9{Q#yuYG280q_mR?C}XKFyWj460If;)KeDVE+Z-Btf~QPV;= zY5xS0=q=w^UHHVLj^vTPg#`_?O#{r zMRUh(>H$hT{%ei4+X?W<%vKZfA=3d!)x=K1(ztA+EWy1`b=aIfg0w=hafymB%FnqH zdIKloJ@IuNA;Bj)|23m01c>_$9-g&ixGa$&^pGtn1QBNx_XB{`G% zh>0f`@m6$F!g_iR5AJLdwaY{Q9&zmrTkz=da z&+5~g3j@EnMAp7y2BAo;i>&W~Y2ZZ~M8L978Eh3(WA9Pf(he)+ys6beYk3#xl&yD$ z>OFgGa?;`Oz5s4=q6TR>>^uMz3J#)Amr{FN&Vc*mq#`2F5*2 zlo`Q=xHzR9lBP3ae^f`7uZAQ=jUih_(JOgH2X!3>)qLJX{h2eDoo7cx3js@DBBbi4-rvjzvDmbqF>Kt&vs z58ZQ%_7nWx0-=iJ+SCl>uQ|K);~2c>o9^+?pUDE0i*#^}^iXv<+ ztAlz_cTUH6)?nZ|w%OpOP5$82WGBks_J3%ly;?@KEhWJE_$rd0o^NnpSs;^Cx@&HN zxUbu9q3C}FWX99@7MY&1(f(H*s|vb1(s5IP^itJ5R*b!DZx~w8>QyQg^}DQ(rtLj z%MsOBy*HW6R&rc?3!!SJmPp5*Zt!w@ssTng_L}4gQW5_VOA(*fWPXjt<<>h?VY+|e z$FH?JB2Ec`Io-eU`juF`3$}`@y*X|iI+x{!9f8?KH@}olNplfp;)BH;f+!0esj&b~ z9#8b}AwFXU3iI*~wDtJL*G1%FB;Gy|Hy0mF$-$?t#&w5W`WnetHUBfey4qo`w6=}Q8xfT`;GqY1|IE>Rcmbm4|Bx&|2=s%k!)V4`3Ut{>!>wNX(fg-z!HwT|_VVkP z5vP7go#IO0C#0Bnz_22PZ2o=m+aK0_dLvKiGmjboSIW(Pa1G^%0ZD#Lu&A;|8rwpc z$WSW_Jx664tr*Ztr?i{Z@CMhRI+_l}60_1{L9{M%G02XzA|hXt$T?QvDAlHGH_6NwERcvB^2`qnx~t1^(ukUIKPZaHn&PpPsU zct(?ERiM(9*c9EhvpiI>Kt~IeGwOe@4MZ;a4G`0JIXSM!#7VXKWM;s}Kf@%{yjgi8 z*AvN!DuBcYX(s?SDdoNY^&W_C_`%sVXBG_2v0L>bH@%>1VNP*kawP7?ZmKek8VSrB zcLF6`*|zS`I#aR$jN6Yx?kh{4i(lD#TEjgth9HRuB?*M79kKUl$T}Vlr#+nc$~FqL zUJN1?jUu+DQ==;2m^lW9VvQ~8gWG$He`7_<1V^GW*v6t1-MRqNi+x)2R@Jqgy9SU1 z2t|vgIGsM9ev?0sW$MCsRd4oZ?1z2M=tbUOVvBG?RF~6#YT8sm z=i$LG+HT0AyF5)sdSmPvF%9>UcY7 zVf+!JhzV5+peU0?_1du>wTlnu5J%Sxe3V7~ys%kY;z^uoB%n)DY^=J_ zBsrF%>E%k^i;L_w`Rl_DN`Ejrj5~#$wY_M^uF1*Yt{bYU9=M~+&k)Q*VXV#LH2B~I zRZljptITD7PW*!sU{qLY+FJ*s_gclcUVsmJ3?{XKA0oGK=s5a{CZHu?2Pm4*o{a=!Faga|iHi_)zIo4<@ zTabB>(0CLAK!&f6^8;T_)-R_X#Gx5hFsFSuN)hg$@*rTXPdX&w{_uHN(dkl^n;Y$ zxHhiAq@te6B*AoBz3SI4$uI?g=yAOL8@M=T1E*mqPciU@FSOeSF)e%U)utEC#JGxW z+N$GuR!o1$wDm?9jksxyl~9xM72!cS95-Yabugpgoc=pz52y*)DJfs822mBLy;*t3 z##jDbUDf>`*QBHMhr|)chg@2zCgtdb%29 z)#T(REmQ*Pj8S|5p1X0{X=V9QI*uhBn}3<~I)_v@;Iy2VAf%(Q8a6Hi#;0>>i_L^f zEd)#8@P`A4*mStpux8khK7NR_WOfD`dBPH2P3lJ;6t1*{k8PRc4foxK{V8p?z_Pgu z{dfO9(mAYwHOcuoXRE5!TF3(a)DL7%cGk6s7QU>E0w1Nr9yAcM)=6FVw{*YC3IE#b z|I_|L8clip7NH;W@Y`Bxd=_2{48q-!Vt&X3#`L&BV*XV1U5}V2gvhmzhfPp%6&B? zV-hLaz-up3FdekE2Ir{?7V1*`ug7=AHcu_T93I`~+POn$9fJ^Xcbj;cMCOR@%vHtE zYEw42VN^i0l7TbdMn~h2j#y;SNN5sZ?@5mx-vh%}GdOvW<O--aIWS^Q4ObJ2}~OZb%@b&W!D z$Mt5K7bf9y0Bp4E;X+_m*pfgJu#_&r=s7PuAS%2asOQFf&wl~`S58N3O2Pem8%fJ8 z9`5}zm~^p=aHpFxJ^5%`Ei_B7?ck?ifR9<|5+Q^ZB~W+j5&mnL=joFY;@Kl6n>R)F zFz?i9TpB^yrwfmkNUMN>ir-%MyHam#fzHUsEHQ7>G~-MUtixV84M{(Qkrr|MnP0fcYXd2geUhANxddxMe7p2J?#ox z&fuhBYyb}=2`hDMu)7hyHo`Y%IkgZFr`evK&nB$vnSxK_xtb+jG;&peFtybMc%}*9vOn ztVOf~7=NQRKZZK-i)pP|hz8zEuhvA?NDvO)hdawyWj6Q`BQgAy&c9%N>v4m}N$ zWlD*N)y-B7U!amf(t2$1O=jY8NfG1Gp_a=ytN}ZG;azY=VXQo(&gI5>oB(!KI9S~74Y>x9jk zIW_-@fFttGbSk|1QK2^IT~|VqSc&g~sBc9HtJo%y>|OjXTyswF!3e2vU?*T6m{)RA z5Oa~dGF<{d03QTZ#wUP8)@8l*Xe`iR-I>&Ml_&Kvq@m}rFZJi|dGSL*!Xj;JBWof# z{7iO}$Zj&9HnNy&>o~9G$x}-4i7=m*pRI02taY=3w!2pzhm4&J=vlAp!%$wov7RgFFVt47mTm1Y zbaLJOa0nYa``aK~MQXHRc})n3GbL$!w%b)jGRi*duQP!Fh5D#xB0vPqS*YsQ^7ss6 zHEPVvcp?#7V$;)w2CtJl=EDk2o5cE(^wP|#EflUMUOD2itY@Aso_jPD_Z7LGu^q^G zCziqx`Lg~(h!3PkUR9wG)hC3-O~}Km-}#W}%uUFX=!uzMH%nA9R87SnI)Ecte4v(V;Vb9DZkJth@aQ`y6Q3f zp66@fwynPZNE$ZrwYOGfCtWCRaw4;W+=1`m0&4e`qvQ+qcoiAT37BHS)tm=+FQ6lR ziH`am8qOYX{mnvLI7Q2W&lBGl^=4U;)hfGVx?y;CdCn!*JRKUqDQZHij2IC#-=ck^ zVO!Z5y9QJLV9-|(FNZo3PDhFJWV}CG)u?~vYykLpgE^85)n-5yz)-=pk{q^OhNu@% z6Zn(pGNcCNI4>CB7j8>@V#*6O`n%bnab`vgi5SQ*4)T(rt9A_hnUtg@$7N#7`Bk2j zJIBOT9bYJ2dnWTCrL7^@mAX%a#~)c;_jU8YJc)Y{ke?WF5$dTA zo!+Q$vOs3X(ELcziz0eph>ngFmyZ6FHbx<@Tp+HZ!TgOZqZNn` zHo&~Uo{y#YP*`R~fe)rV4Xp;cU;IVREv@xTSdbK8WQWD%-y8)b-$Q+P^R`a`wXe|g z?&b_d`?DYmxc*%Wan{Hg>3iI_Obl4M={nh#bJ}?SIF(+}y~)dkD&Yd! zE&1Cv5>B)+=ygK{WsCA%46YhDs#4L*9b@a7(G2Cg?A!VKCx;G|PAU5k?sLOdZGNaQkx zobnx~(7RF_*B3I>`!`l9|F@+P;HI9F<=NTQ&uC4ilsKZ>xq0rEtBQ!p1iLOJ^9so8 z^9}v{1MNMPqojz#YQLH@b$j0I_(+jU^cNNRio?y=i28qYZhDVp76_Z;Qj*-U*1Gr!D(XogQ-cL#|C8vRm34I%> zWg5wUPTI(Hi)DxPL;^MkH4Rs=7YOE=K^=)#YDX3!O0WB4E`fJiN@P?OJp0I`jPj4S zA0srOG>7?KF}z!pGFh`v5zOU`C{l&kbkNEl@FC%guJ#?*@kDHZAmE(rUnNx`TDWy+>$D;%Auf_vPpTk zO%$$RrGIL;X?R9|9$8mcpw{#6sdI_<7i+T6wKWXAlTnu7^S|a1k9T5wF1w~p!&?mc zG*R#d-TRs9O?E zrM5Fh_Rxl5v=WfWgIj1b{>DQdScM%0s2!=;tuNvtZzc$G&rBv;h&-Ewr^{Oypm&EY zZcvc4g13WBC{wvUmX=QxyI77#d?_JuwlrJ9nz`L5!a9x)s!4H`A-Qs=6W?;lF1r9j zB{~6$1y(j)YdXw~_e+xT@rISg%%xPPvCa=%XEF0JZmAb#TPBT+0Q`QpKJKl9Jy-5w=(P=^LR| zCMSg2RuSh1v2DwNAj=;4Qyll3D{*NH*V)P$Lep@$A;}b34Y+nu&5U#hI@&Z`2O@rg z>PfC(F7wUvy&Jltj#>T0y2+B=G(MVk6sF2TRJ1U&k{-3fU@!>y58wNolA-7?R)E@@rRgdfjHjf!Yy^EoTYUJim=3#Bz#Fz3kDV-Eu?xb={6aK9!WSF zn(Ygc%No2o?t>d4P8d)GnPo)xie>eEMF4L80M6WllWXaAR1sK&=^jKulf9Yt8&r1X z(HwrXXK9^N_$=J-+}-~%W{`L@)^&p=*`1Wa3|aaAt0-Pd^53o?Q24ygD4nA?|BYqX z{F?E=k{$@a9y-zr1~jJEF)5ioxGnyMgBJIF-l!4PLKJ~I*1O!|-u2$Ny(O%iBStft zNCLb(d3l;JRBwk{Gch<_Oj9hwyZhGXJp~mls>-od4WP3e5+0bGvB!x|3cZU$t0>o^ zFaSR=)QeRP9*&FP-$`^wuKhXEf2ol_nj*v101CL?-OZv(8J5o4T=v+yoLQmC#X=>= z`n5DaAH}MaV3L#BmA|5YNmfh^3?r@f9cQiJI78dwg6wnXapb!}<5Ca{-QMj`?}ohd zJc|~AC(Iq!a`hw}L;qN4wV+Vfg=D!1X#8O#2S5XqzR%DO!M^P6364^~V^-@gv{fwA z8IT_?yVXfhCjxWl?Q6`dqVsK!cgabEvzQzatz>->PR)DrQk&CzMNf2#i4Lj6F5NL= z58@!(O7it5bek}8=3m4A>5UH9=vfo%MBK%jKn113g8k*Dl>Acb*n)Gk@_T-ET|32_ zQ?)g?(@+866;qT2My(!ETf8c1yZ|PM9I)9F|5F0ueUb!k(wWzr43(pH*PbDpYMv3V z03&)qU)&w6Ip6!_M3;+J-o3=x&TSFn7@an$6b&GE{f9AZ< z_6+iM8)}s$Yu{)J_xYG8>mD8qx<%q+JJSFdtQ)d?hWAb}5{k5{XY|W+b=8PbIVL5t z{(8X12;YGg_Z?h9Ny7X74bCOPoYYl|I%)xj73CrJD~l5{EI3_yI!lR$y9*$K1m3Hs zkad>Rx?y@?5H;rv?Lll)z&FGX4fR+ri{fxy#>g!fPF~28qLxsknR&*1Kx+exuJDud zVRU2qd{}Lvp-_MQ>^}rb+(MPXgQ<#bL-;$9!=@>jZZQ4`V4izX*KSP6irh5HNlVYg zWVxufUtrw)xz(bwfB{zMv`B^2h@QdM6KSpXg(l=mHmGxyTm}IHAwgK=pJZdXiM3xd zQ@UyOFvMTl04g)@4V;Owr{l{6#WKAxi2g>~^e$q3$lA1Y#W(`Dv5|@vW4#o5o2@KqHxa|thvs@j`{(mWwr7v#)5W0xKks_sqS$phs zzCXV03Q$K~i+NJbU2hDHK;>RlBZwbz@uGXHD(0# zW(xNUWiYT>JUAjMR*S{trg3PF9Zl_eyvUGDjTm4XY?LS82hoHPF9XZ){9@_mF5nUM zV{n}Kh?;m&U4GTQPH6)Bf7_cX0 zdW=&58tQ-)1p#?r(|2F~)X9-Z<*}vm)cZ%Y^<{ycQgpf@@J|qJ^Q`-=?STXp^0|q! z`KuOGFdtB@Mu<;-t@XI5o2Lt!2Zek(q{@hhgk`U>A(jac7KyHvCy81n`)|Jn+D|~O9Jji zB~lXT-$JYgPKIjGWWq>zoQDVJR9Ic_bar5Jq)4VwN0salM+!@806##$zvA>CYUws5 zG6XYu%dFQuk8(hiSo}!zR!#S3;dqRh0e~s_J^K*v=Hlfqz@NAZK%a3z{At<6XU0< z0;<$4+Zrwilm6T#Q^{^qo~!6+2AlUWTH##c?$bhW(Gx6L0X)_MIery$BKj7E$`8Gv z5C&qVP) z@xr&bGD94N?zox5N}^&N=8Ch_KT@=qy8!6-7S^!pHkz14Xo`K=%J7#8H|*H6hG z(QFY(GqQts+;<_B6t<-MATHLQ_djhPgxI9{r%ZH^5mJ(`tu zsoqV$PEVmqb?s{munTFG^dtt3eRg(E2N{puv6Qka;)ruj zzzoNtka?7XpBXyqScr$`|s4{Qp;t0Y#3lk$~jhjP-B<2QP)2unG8^~lq|4AA(;*< z<(y@R%)*&C31fRu00gC{2qbDvFc0f?n)@;hdZ#mXd5VSaKt3Yb#Gh(p zP4)f6l37l*Io_#H2V$iw-d$$jMhI#!nEE46BNq~AxR)nAK=ah7Tf)RQhytlXUV!B2 zE%xK+k7qC0%3fNR-q7erru9(E$C?y4Wr*enqi?R_ljjk-sxoaM6Fjt(p;*U3AK6l zf{2Rw@L5Tw>e1J%w$lhNBh@2}A@S+kMY6^kARTOPpN2_Ir|u*SL%uQ_U}wjXh8p#)0UiG2_eKawL1JNr3ZkP0#ZqglK>)Nl8KfG zTYVMa_^$GgMBv`{&>X)WI zd50zeI@evYQ_vgnr~1a?F?WfcQ-(5vOvW~?hY3985!?!MS{>IeJ$5BIsLcWRv7;Iq z)nlz}=>;e@qH@ydjMZ4F;>6w@xcoQO{mhu-mPnn6>kn)80n!sBa2y~c1Aq$Ye+OwS zl~&2jf(hN*5*`XP-P_R}v!NX~UR2PR9-cM=1!aK(;NHVL!ro8xq-cs{gNQ1zGUIT$ zD(0(Dv>?D$rYE1ny7_5hc2~JA7O;3%TQK3{2)DlVLEY$gm!QNgd+0kgZ4P9S;Y4BR zVj_t@I=v%`y~MLr9$fh%FXrivru`At&zCney|ZPVnEs(jR?HCQ;lip}l&&N3HE`!S zC~F|TF{_LNfGL7?ma1Pf&_-Cu*;WfCnHB&1jAx~qwkd+uu~$k&=xM%`LBF< zzWn_3Kp4vZLH3GR#N?tHC0G(w25(!M(a;fE&6C?gAel6bAo}aItAr!bvZ>YwEbUuW z@Yv5I)-`&>AEZY(Pmdf~)O3m_R^t=w-_}I2vd? zDaqpQ^vVb1(`wHrt~)#Mu?#5GbQg2P-4#EsAy$}0EjEMUtqC3FV8 zIX^bmB0BHBWxroTm3+1241}d8ljZ6M0CliLZs`_skP6$;Z7EaeIVG_ULRKIf2h@U{ zcEi?vK&xw*i2z$A)&tP!J)pKBqBPC-TU`ODJzI=*my8m7x)7tJs%U@@CV?Bv7p6Y$ zw1-$R7$0=cZaYIY$Im>j&iKX&mi=$M299|j-^?Koyd7c`)V%ZX!K#!oTPJdPwN!Ue z_|^jz@j_;|{YM6Y zPyQeiZOHk1sS)7{$%{F?HWu@@e2BxT8vM)2&+ww! zSa4M+JRVw=Wa%O=vfT&#-)~+HW7D4a%L)wB%;oPPp|OLB`3c;gA6+L7$7Pc9tHBuN zDT&i!VH&3_GkNZ=kaF_I2{&J{Dk*n7roXlZDZZm4@ZKk!)$ZT4`03T~=vOlkvtAP} z{cg?W?yv@+CDSi0KoJec@O1Vg#M&HiZqbksxUOnh%(4Fp2PQPM+kLb5NtS2(VP-ww zZjvryglS=8HOtTGVu}^?9A5BAs%yjb(yq9WpXMevUd2%4Ny3U5%=!G-8J1!27T1F- z8L*Gg#*yNk2jr?&W|pV`0AO;}L#xQfB1$CPn{Pszpt?g5yxB8n3!aDMBfEHUXUDzJsnCiOePqcd*%HNqNRjND=qeqkR%&?}5)%d1 zfjs2I@;pXYpHPm#;u$whV3MjolR62fwt4^-jGpihyC)NH5cz~0u6bs^mY1?0TT;msVIp!wP-7xX zLmobw9gQH?vZYJo%Im%2KxX7ViTWi%h9UK|*K;?LyDV!}G%kZdXW2`-#^$g+pZ&zjZmxmvcTS;U)i?$VoTw^yUk^NW46dVL@b!UwGJ;Uoef~i}|5FwvS z=-gaFRT|tHuS31NAPCQOt(xmOBL-_EXFo$p(mDks?{Z}9=oE%eO=Hyxr!7}Q*!iFh z%<7B9@?s1{vRKnWaAusa*pRyD)o76sjQ}4Q@R1T%>{xi(@A;tK*t(qt6%$i}CPFU% zJX`88&CH&?B;lCoVsG^bLD;0>fs1dqY%$E9nYRiSaBG0|Ot9 zE$z{}BW!2+I^Z{k7G^iT@Lqf~ylF5*((7EllT33UOzq1hQkT=qK!B_4CBJ=GJeLh5 z`-vj>E|UhXtmfaJ#rb{lZkU0+!!cz zm32y0z9l&waavVn4eu>s1}N&hBtVKL`@zpCM>uklOAmO+zLjM+SE7>S= zgyLi|jWMd3(K!lhi{DhhvoXXYtH&9B9$N5t!e0)P7l{G-iHdD->aKf#SD|9hq zR@RQG1g|ZEZZ_;Hrd>%AXR!cQJH%6)yDgsvhXdG4gIRjCljD2M5O&k$Wpm=Bytm?b>5CO zYTVgU$oM16h|8Y?dR9~7x6z*aBztPdE0V+YtL%-tysN5v(Ba~va_BrxLDd%6+I7Ip^ z+Q1NT0VZ_IdmY~G3h4Fg(Jr-G?b4{7Oza@6h?^isA+4tn*)v?ADHz4ANR^Lv!5sGV z1Jc9MTuWnvP|V31K=>-e~G!Z zWoUZ!ud7E?E57|D7mwbd#V|@8BQ3E#bl5|+zGvn%YZ1FI*G9`4D_){5^9OW9yb7Jz zqH6(G=G=4D1G@c8v9?eEkMmRHGFBPbISiY`rjxH3V-J1`RW)q{Ek4!cR#UZ0dj86 zBGEI+vD)cm4Xtf6CUi^1S-o|A5EE(Ovvdlq0k5Lh?C>Y z%vhRso`}QN7GEy0kjgRiAL}$id5eV01r|#4RrD&^9sfUIoIl91eck-`@|pdFm?^I; zjCD{NoqqCK`q0Ix3tKJ5hTU+zo;3xILcis9M~{#v#o7{-v!Cb z9ti`h8E}jcBX1w^u(?&bIt znM{&yC9L&yG^@&_;g_+G(rca?24Qiw!MoxT6{XS@n4{uA}KX*#6l66iPJkacA`4KUS+q?#wr0^@g(1|SR}W-f_L2MVwH_~*_JX2`KP*}@adJQo!{sgxt80Fs*Fv8t$Isl<@;^BvUc6g-o}gbG{Myouy4A>+F@ z`}*P4)k)o*?zCzBBT##JjIG|MSz+q2PU&kdN7M#`XBM7DKC}h-kQPs(P6-FVnR(w-jxc$6rI-nthcGZ= z2q)uLOC7AHsI%llD$ep_z>!*qnrXqGYv<^*s*9NPp(eTgPiQIwq})W#9K**Fr$!sV z>e;65+M;@l$SgzH#rP^_X=q-5UlMxwexXg_T1ojhU^Roc5|gT{Jr;S)t-knLU}vXE z*TryseE1$!3hReS(cThJ#XL)YWLei&*rMHmh46o3*(d`DR9|-*cp#bro!TUfo`cGN4F10fHAwQTeHT#x(kjy=36IN# z-&xYf(C%XC8{!84#>aA7KSc)DPgfs)V$$TbSGXj}4bh-eYxRTdmH*mdrWp)x*XoJz z*KY?Ihff?Tdnq{^%i_lECSOqgRzzFd1QiQbSi-{jp3}IqLyWL}>IG_lKN|^4mV!eq z!W}q3p`5K(=^xvMK0mU>`9)y5a9)@JTJg>uJwG{>6#K&OEV#A0`sqQ>muiuB5B2ID zIH69uW>vU?t|$gUGg(z5bsYqWP3$QeY1c4rBiOdI;ji3QrnA=tOJaCLa)0_W!;=gR z7n;SN=RQnk>6Z0o%7cd*ZG^LBT5m5fj$K^(0r5${DDuxN2#rg{!*<)yc%U7n#P6f5 zf12HU$*h(#Yt{ICLDvTE(yeb^DAIxsVUUqarW!-*MKJ9eZMZqKA)V($$;l=fN)c}Y zr)k>|L1m zO&lK(m406q`|(txH5JxQ*)e7chyQV+tOHyuT@g0W$bF$$MRWaaxh%;ntbiRYb@2Dw zJ?X^WqJb;SKxE&E_V}ih9d6EuuS1S)>gm=R>z4-*_ZP_q9tB9JPqvO1(N3;JPSk7t zXak9xn5kQCn1qGLqo{XF2B2 z?}GK3ddPq?ls*Y3DX+aVo9SY*aC4EeFDjWx@a_4-{_8|83NL<_jSqHBrbLagQ%g5I zRPOb)lfQdC(8Ny>SPgJrg%r`oJI~IhFzj&4wZ_%U z9?*=IiT0qC?kHwh84$<7lWJ}LvCm9x z5n!@Q%q^JjPd)Fnw`tVa@qe0RCRxGT>R7|s^_ExmG0V=aiRYJSMP=NZK@WMBeViAw zji!kDD52Vfkk0M?nN zh>@C7hvov6s1)vWdQGr94J46oUn z97fPzC>{n23)o#ZQ$PG8B#{9wppK6Vk-=gc6a==rBc^*k@|rH&+2I_m zU4D_&F?;KfT0!61!5GmHO(${{+STc+3oK_HOs%F`Gtmt#tF)tf=@(`5@7|@-JXo~C zu+C6i__tU;V!rNo69hn7CRwrE=ck&NjC!K7Y_^-WLry(Q%MnhxG0$FP$=KT*il-^w z*L@H|p(^SzmCu}zsziuwwGcM$`R_9{hq4<<-)|;N;eax-BIi4iwbMo)d4^BPN>F2oWgpOQBV->q-kc&goC8O z{#jh3y;>`fO9t|i@1nVw#{g^IK`}2YX*7M5JiiuS1dX3ye2ZfCB#%e@W$@7audY>B+R+|;K z?Ftz#QXLL?P@E~el~0;-c9RV;ptOH$Ck{}*bTq7MIH0HZ~XZPykj9mF5A@fugs zk})C0XN?(9h5Y(}Tf3RHXEH;9K$Lz||3BylShw;(_rIR6=Je|I4$QdkoObRU{ln}75@Km_cm;6T-8OnNP zv`B4e#NmwPdm=#__A{%#vOp3Be7;6F)|Mu&K2T1M(PT=KwusbZMr_Di^0{);Ws~Au zT2NTg?LNB0*`}N#w|G`P-pTMVar~f5=Hq}m#@#aB^4vBw|30iUrr6kB*f3p0Q}#Y0vH1!R6~n-sz@ImFtR*OhV@dUkHOi9Ha?;@ zHfu1??q}<37M=LxfV{`Taa4-WZyf_P%6jvgZ8-v8>9TALdM|rj4FS)o>#U=nyzm8N zzlq?ED1WZvHb1*f+Qr6+O_;XjEm8qqK!5ctSX{>zTL#a3OJu`*a)TCCsgALltrl&L z8BfZyaDdk1 z>S}*UW{@rlS2y!Tk*Wkf5f2sD7Ah*S0Lijf{511LH{LdfFS@UAq^GQTysBCJ?tVXN z)w}P4dzaDa=M6(biYtl&n>u;}X6IHV8_EX>_R}jpf#7mzHytXR#1BRACuP!2w_%*KK(poSXa}6!Pe6NU zIM!GGw6qmEm9J4_@uOP*^pUBjkpkKH5ku(c+7>*d2$Nl^%8u8K({V-0ls}D3O+-$5 zY=zel5R7d$DT22dzkf(L9W=&nJjzH3U9})WrYHz#RC|&9%;J0cM|LKutPzQIZb^t$ z=V>A{xpljUpDPF=QgZx86b4BMAu}>V2YvRRq$*<3uMPL239tZRN~0Sew6&L&m|eKFm}@f*sA#2wjqk#>dM5^9S%cPsPN+*^2PQ@k^yffI241m}SmL2u-TUU* zM?Byhf|(z@TRMgH!Qisz|vEc9=X@@bmaV&QRkL##r= z3Hck#vGGD&zFAUlNB8@F#NAJQ+;QH006R8fkHF78J9FDr?~11YZR5JAN$P;0o3V5T{Pf=)S|v%iFxfj0K<3E(RzFZ$S?$QxO@z5R zoi!2SJ*-9$nqpzyA32* zUwgIHvy8YgjC}9(Ij>kRTL)#9nrzb49*Orm`FObb+O|8vBQzz@-LVu_?L9pKR zqRvck^2tM@8jYxa+4cAck=JuwI&bfwt>oZqGlG)rmJ=8Uc64ag+S5ybTjj8dzfNx5 zHp5BmBc8mjgA`miXn>3bGg5Tj>coq=W<_5uw6THLtZJXJU2aCLwes#3S+x=TMU! zmMzkc#9f*=M})ve#(xQVF6rx&bIn!P%h1o$)oEG>5O7JGa5C1y7*LRvfaD^K^|fSB z`>%(6!UD7^))v;RgVC9Y73vV^z}bH6LCd#z@ivhY;lKgpciqb-Tl}2ou$zDyM{;z< z@i_$5`Qp{5c^UD+Hl7q9J?-^n_G{euy@E;)@3(pKMx63@COG!XIlU~~b+Bj*)h!*N zfKEjtWm==+jLW6u!5sdguEy{2ZYp1qoP{SkQ;mHGY^xUkK>A?MDcnKt-NnH?LL*pS z@EbzuKKL9Wb~KUmAJh6QubfUT(9Qlkxl4;js4|`Cr1rsolThZ>Fo3I@?7hpRF3h9@ zmTs=Z=ut6==yV2`CUr{8@5(0gkE>ywr=VhIDzqUgxKyw?czeLwrs@CT#MbiGc@C!p z+^tgi#c4;SceSE6c{mdQhSK?=5f{g5CMw&0zN z!U{cC?ZX%QGKa2I6-&j|3rP-jaWt!}Fj#{(8DjCfc)r<-RC>bt8=}Mz#y$EK<|Y9R zth5=~?PngfEkOX1Gf2#qRhWA?^L=9H#{dl+RGCc^3r-^bugzGz+>gT~#bY>U*MT zYy@a7B3W8qP_4O9`?&hzY}+1-An* zPK`5JeDBYvLNL(WnMRd&4WFzGb~pFX(sfs+XJ|fyTuDvHwu2 zq6R+X>B7c$n?vl7V;9RrV^29l1;Joq0e4}NpW!VfHL;kd`KJwlIj^f~$N7j< zLwUdRpIoVf2Zof~v(fSZdhJPo9i(2xJFcU&4^C)p&(T0W)-nnkCZ8V9mKwWZylHsO zk_1wkdFj~k$>4?1AHC2;5^^8^hI&u7B-6~F+`|Nu5VZ(Ra=-T!}$)keoSq% z(ZPOs6q6|~Jp7P=@8XJ~=OH;2G~2Vm(lr5MnO&b`PqT&>CxOPDyU17{n9Rh30u1O* zWXaR?Q?4?nj$`YswU9!)>ex>FHPnG;Ms>s$j(e5ip>f`AMQW4(ePAb?PG-4@)xg5? zd*({>m9_qaB6oelJzBLMGq;^wN$66ZqIe%Sx>y4QpA%>(W`H+!~FyFPXYvC#x-toi<6aKK1 zRoM!M-A*g#YdCmTtwuQO9LU9k-~J6rQym_Wvmf?J7kWdZhoVP0b!300doQZksEr97 zBpz>-anj<%6vEWL>jMtT2wc5&tTDD-%cp|FZ$O{Ha){pi1gFXQx^VEuT2VhGG>AB~ z`;~Z_H5H8q*TU0O+@k|VWIag6Rjc;hW>dz<6eTP}IKxM0+P@EIwU}^3G1p)Z;wej= z@YGQ6PjYIPvbBMc3_uz~LiOhJcX@i`&>yDLan=0FOd)ktyRcN5ctBiT%KciY&lK9QtIaSc4^wiOG% zrnR9$y?6+N7Z+9u;*6(ig{5fKUWmdV`b9`FRk75go^?=5EHo)X=B>!|mxNkh+x3OW z2q^*MoB&Ulj7B*2R|;uOR)dkwIYg?(m(zmt7nd>67voZMFum94LKV8Bg&Eq8j^B4q zEw==(zI8>jrbh|YUnvEoeno5DDVC;4-cwXB$iO@L>D~(!>RQ5!U@jg} zyiRQBt^!>MRl*Ptj*if1Ae6K}z$CntxVZb2FjmhX17m%tn$O}IT*e^W>Tw~MU@$0L zEXe=Q?aSLQ31iuW^Qq8*rR0PN_Eq}ttrTk&KZ9Fqg6V~mYi&!ZXVNB!Dqx;&oEzg= zwTjtSbJ6hn(8`4>Vz}(Os4Rw=d06SMkVdbp>pccsel<0_FUG;88O?H)a{1ENy)y6# z?KJp~kXK()zb>Uc@k$()$D`N`CTq~1dua>zhgk7C#>eF~bAfy-Vizv4o4_AVtK@t1 zN~qz{o0IA)wrQ90ZM7^2Q_LIy!kgtVe8JZ5g-R_KSXnZ-HJTTz;$Gx`X}`6i4kI!G zsi4Eg5u1^IOhx*5-3k2oX+u_62AZ%WKKm83iOQ8e4v8a0B_q2Qxot8hN=FV=wOfdQ zVcTBBMU4aV*V8}KGZJX8Y%!4ao#fZ$43HAbs*!?b8@a-fdR}eOtMg-gx(#6&fCy-& z?l#$*n2P$$P1c#sW<|T9(j}}~*T9cU+^D`91rw=YMEfCpX|q^sQ+b?`A*bS>chqwm z)656PcyCMwlH33xJhHrNWNTpRw(ToyP1+%SD(&5>X`$UaiV)=${PMo2y>D=pl2$pG zH`f?iwAH9X>4VDEj$I6lgJA>bp#~1^*iF`Q=n8J`2qbSPfaW1;V6K%H35nSGr4Q!skM2^>F_%wuQqbB4Blz9sb*n^{mqUNtvP;(LkK4Y%4{A+T;b>(~>Q zos}$g!W5d`H&kmlgbsz`$j0%36xkzY5|5f=l-cGv%{_EGL%5_x%SHu=y z_C*kGfc$`5<4p16!foU;Fh@nNb4HS}bTm|i9RzsQ<+9W`-YA@^uMw$_8(oGPhw2Fx zY#Q)Mub-WnV+KiXD6ErE+1xQb-e9Da$Tb$(@n3>;7`ed-MaGX4j=Blkz}#Y@AsDo~ zp^V#F^`+AAQd5siv3PrT#`}bvVKiFPuedQ<^w5PxC3FIEj*~S+$Jk`TK0bjrAFGSM(SvML%=|*dwr=6<=J=JPK*^_GcxV-T~D`ldsMm+iG0O~(iIkDN&BSMD>}Y+GY^6p@J`Lo z&hHx*-=T7^1P8xyDpHk?s+?hL$x5Jc<5CAI6cSAcL}GNV|5|s68zunu=XcYw~R5+Qg9C&&vzu3myhI!mWcUt`}=TJ7wQMGd!g1v-$5$C%^p&WqBx1vs=g4p zzO1Q6v#T1BpaUhK4gDa_*z80UP${;aQv`{m&&NJ6cW}mQ%XHDeAY?SCaE>Le& z(CPwjvhQR;&@S{0Gf?;Nnc*}b0M?-P{$!2Q7L$DMtjhWwUB*fqAJ znU8K1Oor*}UzMX;6tj%i6pR# z&BqzD{GmFy@p7RS2r-d+F{lhFTglB!=`tCxMxoq~J|Bq*^x*eWZi3Bvyl5P+JXHOu4iF z{s}qj*>nRJFUh}8oo3{}84rkSw+ttr-U0k9(o>5YhEVJ?!@?%sNDZeMuANbu#;uU8i!{rt35z8^+`Y)KJG!STO-x+EON_dXtY7 z28&K%*6YQb-M=1fEU=^MQ37n%?bq^)!smc!CB6zByPBP}ZXKzv2(#A{5}k1sK|go^ zqbxj90ECz&Hn%PEl1W?%SPhAP)n>fm2J2>90{!A;X<+_`dE*?K+UmJ>~Ye%J}V$ zM9sn6=UuU53oK0GD|}$O%kg{vLTgoPcewaq(q&_|b(tON6A*c{#iNy!f!rr33~8<> zT^~FOUs7Num6(f)U}@vbN(wM#ZS?7>r>0ncnVbmHLy>x$F0;r3yURpjxck=uxreL& z$U)Y?TSl371MlUj6bXI2;v(NrkZ}3)UWk4SbS2lPE>FIqzx~pzs z_Ak7j#u|EmVxyVx599n0ba=0!U|sbY+OY?RN?X(|)Br=U&O~Wb0X@KUHz%v_U3WyS zv=>Garu_#ldibZi!TuT ziKl#;D%8L#yVr1s3i^8D6SnVc<#F=y6g?mTgYSP-|g8Wlo7}=w&p=6U%D#8p?xyEeSzrc+JL7oQN{oBKh1r1Z?}K} z(ImSob=P-?M@04x#>c;ne*J%sSmK?tFdbLpo;V7P# z1(@t9?bg>Z{k)sXSy}%s-)hkfZa#@kf)*Jd$_b5HE>n0>L!dq2_fkAlwGL;g zXqcngAD){_0N1+1fF>FeKHRHMGW*gHYV}X5ctlC4%S|@X3QlxRvnnD(F2G{7KvH4F zb{iS6r23zFi$^_^WUsFj@;5ZIz#`~?vL1q*5-R<_ z`=z>`g=vLW%UjFHI0!C^%Aywf@_3k|;W=OmhB-ls8Z6-_y1LEWgA02E=eD*_ zGkET{I`DbHe(&4=%;?bD)@;@OGUU1TY~D?BoZH*W_A0!8RU_FU1Q>oB?xg$-@<`EuIukL!|s_svySkf{} z-Ot@@P!8pThSYQJvOW&XW0G+{OrdKd4kuP3An4bseS~l1(#J&E!d+I6%uJu(UL?xl zg#}dDz;dmKNJcVx(m;8yHs8RG2!nTGz60zp?Q6gZpP;p6mN`aCKpcRp=(-;2;=9lx zP>^?-B8+U3Thva*mHOM4G+L_cS67PTtnoMB$L;^#0FJ?0cDgCFB`5P#=u-IRDo(0i zds$xcMgSu7N+@QpA_|*=kRxEjZ#42ZmUI0GujMRtvWGZUO5ur|Ph-prSP4WqR`lbi zck3vto#x8^#VX45e+f^4r^NBplpHnwSz8bR==>N&oTP(XfP9M6hwb=ZGl}g9B~nc` zNy~ryLr{j?>e(V1Mo`%E4Ic2Ez1tevaz>WWC;&dj@x%sI&8&h9+P*=BtCvHI{>v!x zYLatv@JuOhy#~Q#kt--=@7_1O_5g3Y*o!K%>X~JG)H_pwqI%8AK|4nsR2^Qtr~n;u z(H8Q;@*}->nPTirQ`r!Ow>~4S=^2`)?z(}AhH0tlb(BwtTf3fiI`bnRMA8GEbJd#@ z;pE9^2KZq#FEzZEj%nDsGDBV_l?i=I6M7d_kLw-DF5VWRQt4CIW1lG=0PPK;MCCIa z<=HXe4ykm6_0n*8{P-(NAd;jztp}#j+ zy^^J~-a#A4LD>A2`_siHWc(MDlj(d-aKb6+u5e0%G}BY5CxI3>iYcWg5dtpaAqaI+mGPzD&8BqE27O$MGl)is}f zH77$-Es?R9mJZe=d6ciC!5hcbmNsfP*(spFBrnY~1wVi1M|#+{LL8L zS>r%1eXtNAL29;1-FQ>|Is@YlcEKsc1E)ZYN%fUCa#OhNIr8Nkh3kt!IW_Pienn#0 zWTjP#fow6F_o`q)x%Xlie;n9n(h}mPHyv_@K|mY4%}dWy@s7pr#EX$UmfA0y>9(gZ z@)il}H3U3C`RI$q(Y94n#Ig@*#WM?xcG#m7188D${7k&53japKn*P=!L7^)p#B?_% zYB*cD#urumW1Y%VZ1j^3i5(eDNGObs*FWAg2>t5KOh_VDVE4NG4kDm$B&-Tc5)To8 znQk>}IY`{GgmF5Vi#EYpH14)|xCm;@SZ>IQGUnrqM2@bh%}bFNEe5@90FpPs;Y?)-PVd6sDo%@;Lv z%}+bNXyC@^D6MYzfZp#brZX@&5BVm6A$n-n_XK-#_wNVfonX`CemgvMo9 ztmlcV$?I?)PC4rfP`?_HDF{t8Gl{~1`O$UP#;MscTN#>=3>gBZ@7Vln+GOp$HN<+t zV~~=k!$Kb>FLr&8fH>uPKE8L~9nIR@V)eKpm&vRjs}bI*O8l(dK!kUs!|m;DZealA z+BdNl*Or@#gYmM&`;VNNCS8Krv@HgzvX1qd_~5nLVtGyvNWU~FW?s|LHtn=QFsFT- z@iO-9)f{UXr0t&gn;kGFICJdP{ryzgc!d^W#u?*hQ^piu@oMgo-v`V!gD@$=-500i zcxB$U>{uYXzzqmK89;m~1wV|e7feHCU}%F?Z5%GT>1dc!?`8+f#$AoZ%g<3OZGvQ_ zM`AUb2HsQs9!~hkuvh=hm+R3(EVrpQlIsIcCsfO2DBwa#8F9`#q3|1 zyU^N?vB}QB#tPE6m%MD|5$q5H9lLoXg2wF%iQb}sK&0cW3asv{SXb5HdzEM3-6nvxPF4IR*u>|}P@PCpM#H7?(>&G&*DRzUfn2cgFUdhlYGx zFZx7lGKMiVC9BnKN()f_=nhamL=kc)AE&=b&$Aih+@!V7GtWYdoo_eT9vq{ZFuYbL zBUpCMM(F9-(-#_i+QJV}?Mp!YeB@sP?YwOf^}o>c$f&WwKXixiL~fJ=ghAD$+gr~V zNYw6g>BIbnUA-lhYYR+sny*JerO=x+KK1X77eoz)(n8YnPLFzxwl6$Zr@n#a80rBf<-NjU%zcE%_sU%9L!7HRttHv}ODzOs zkS3(ZBD=cIUo%#KwDlVp=WJ$X8jc@NDQqJo0K9YiOBbx47qN)v)eO&!$z(@n?}boZ zki7n(yvkJjP@ajggd4Ob1=~Fg!=u-TirKai5%D8*7lqCV;d^)?1<$>!ZSr8Z$37#g z+D?)`Fj?UEC_N*_%VYsDMF(W6qxQ*TrHq`s`+~iesBcxs$1@>Q-9CJLCHsxz*OL!R zXswt!%PX&`07*c$zu{;FsHBWiUWb9e0QNQS%8@KdNlP$F*rwR}s8>@`yo8Mj+BY$m z3+{x%GGnyq2s4j*)CSe}9Jil`Ah{7K2pLx0zwpBl9u;^dFhgWR23b`@LV^6>Z6ptW zD;)$#0;QwUxMmI91!lUBZHPh=u%N9qe^htI3*N%@@X$>V0rVi|WfAJ=vEds{)KW|q zwkclWuHmC@{CM`T%V^nn7CFKcTl^_3CRA@W17y!lXObEhmRD`4FpKZ^JQ|%JWS!m^ zQq64LY%JZt7^Iwm+z`G2I9bX<2XFlD}H<1mtAt%r&_Sa2?%bQkUF0_fpO zq@E`}3(0Y7#+-a~0h8i%<`YNk*kT6T$a!?Rj)!za5yuic*(Q+P&KocJ$6Sq=w=`gl zNR>fwLVaAxI$r2Va3>jAVvRns9wPpDbi(!?k0&0L=rI9(g=|_qjPXm?4sj3o6hnWW z;Rr^oX5wYspeO0TD1u2pEMQVw-D68x$<@Gl1@utfXxeKNml046lX|wxX_UBdq1mJ4T1iJ)7BW*CrdmZ}z6ismOGA?a3WuY;Va1NG_(PZMjGsD_GTSUZ zJj>=AwbB<@s}Z)4Prn|a;t=u+C^y2ZD?$^TC(L}bohVsH`oQ(Htj3sc&hWku7Dv0i z;FZk+lqu8XGR|v25*i@0i5@_;580Fv!9Df|lX%Hi#9>;!LhcVk@@8twdXp2+cI^?T z`#Hnw3D{jSM3B%QbFp8$z1U1RKIl6(`z>1WBqS&ycB-hkwvN@Tak5ka7+tkl(;GsX zSyYCgAU8Fg0RHA)QsbC{?(Lao7fCb8+Tr-){i_BQaCU@_QW}~be3JS)ph#Rt?y+*s z@gK!dym48>!Pt80RlcfQW2c>NA9&l9pX+{a;%B7vG*@8O$yC`VxZ1lZX&s!~+(DU( z*a+3tDfPwAcz{@JiX1wq@SV`#chgF+2t*G8mM)g(L696KjT_0s0nwDtyR7%Ug;x3E zu3C}|7_<)+QEidXJGg~y>4WxsD>JRhOy+rex$`)ZgEPg=;hMFgdw4eK+g>kbf)yq( z5YuHSw$jk({rVExZvu5==7Zn@tzf{pJL=rjo0hbOZNVhzXO!Z9{9tIupBoNz>~XUc z2YV%43(`tya)u&;393aCELya>e+R;TQ@rKEV5{kvYj&c0uqWA)rR z53Yx-ZE8YMk-IKUXY)!&2NtgrI7TG?@Xo`x5s|B(wk{P+X+NA)c%M+s#bEB_CRTVC zUrVh>(jv0YR^>hrWUl7hG`P8?8uWl>2;o`=`%Es3!CSG|F7Kj!vB=!(SB0(kSGbu< zFZ(>LUt`|**NR_-pi41P!8BXP2vT^Sv=4wmp-=LVW4zKEY31}kcuxoparo4p5&^1! zu_Ft-=LnJxm&99Bsz~KAxEO|g5~OED%~QnqMc!CH!{*??Zf=?=1J(3HeodO5%T>;0 zSZS3>@A5Ry2uR-uoDkYCK-Doq7=A>bOauyBS>Kk@-Kzc0UIj z56WYrWD5{CkJI;SFAIg^4_x)SYHImqgA#@TnraSa=p@ENZ^^eifVIaKCEVV3V%A@? zOSRfH^{5KvZo1lB?I>q~`J?KZflP|Fg30Vog@&gW0nuFrc2CnTN4Q+ZF{|ZJ`5yFpPI=UYIfxF zL9*Os)nt3VFF1m%7uB=GOpIyib%W3|lEL&Of%J|&>BqwcQsc8~lMCsU|GT?`vaTaD z#gdwXMRFrS3&^N3XgRkPm=~0~?{M=|r`olACbA=UMnIvv}Mi2&YO3cg; z8dLxt`TYMnhUyixiN8`=Z)O77B)Ckbq}RrNw_rR5FVZcU>d}I#41qpO8So52HE3X_ zLcB+Gm9SJvQGP$6LY=UVHZH=knRd(qbfv0F6NG&gE(HOfZGv8V^apOy^r*QyBbGjC zeHazo6zSC?Uy>M6$sGI<>q{ti46R#>Qnl{`XCD}GpC~WTSajg)&V0A7M|lZN8NZY$W*E-rF?*r~%Mh~g_6bo=Qu$E9bI8>y4t%@dvd;?$=@Jod zWemE!t;Wk^%tU@e^t#~`UDOIUD%DHj(HXX1(<3KsKg5XFNsX2dxVJZ1nwo3d|ks5oq`U?-aTj?q#*43SMO6Mwv5 zCfd7RMt(MRRQPTEAJ_4LU*ims$>a4m>UrdT*`2&2X;^!*5EHIaR|a#;xTp>tXeZ?j zNiygs60TthP*N~0eEXmt8z?|G7wjlWDI+8^$0brZ+0|M#xFg*tO7{?TPW6UFP8+7# zSraT(ZH>D#QQ&?^D!r?Wt|d<4Y{?X9I-Wo?cg`XFl3&)!=`#?l!_4yz1*_!IB-hAUxx=m;S9g36S9(OZKv`9`^O`ZW z+?W3I1`SyTk?*)d>N-&sFhv?y0NgDZezw{aJp`1vu_LhtX!Gb z4ta>d8gQ^1!BXy#ts=UJBg1tM<#IaoEhWvuOL@eD8GFQX%8sx&xzi4=S=aVhEp{Cv z03t0dcP=;!K!Ai%{@Kg3thCLsENk+q?8Nx@_7;*Z06C&h=_tP^?dlrW0PyGWa120I z(LKhr*0AxA99oyi`Vv;8-f5wk1{&`Z&b$-1ON%r&f1GvMfj3wR`0A=C9(~VK(gW+2 zzb~1z_31_tcGibc+ag2rjyg%}6voy-JYU%&wY(nd{_o$`IQkuDB}_)o>~#$%MC?)i zp!>Kv-ASZ7kTdpJE=4!E<+jz)0~%zA9B4Mz}+Fwl*o?DJq>C+34mM(yA<~whg&mnD9$AN~&9g9Zor~VZ;1PiggFZ#^6WZc{ z?f{RX09AXZ{+Apy-YLRrJ`VdV0C|ml^82-APpX%YRI&&_K|H@QG?`CA?PI4@{#py8 ztLoH~imY$UKpu-uJt@RX%si9;C+nP)X*czIE;UDMMd=fmVVm1IiBxdyIkl@Dm}&-RvI)hZt!H82A-Nq(bkCBUBgY1(@M z0M-9jNe5tIHt3U6(*J(Le?{^2V8j&yeVS&xg0L6KUd3YPDusIoTdaba=E#}06ZW)Lgzn4 zp9{mTHDzf7BRROXaHi4pk9RYo#Ox~3SC0UI^>{(mak!?`fH3xLt*;!x|MK$ADV;j; z4Bt*VZalh2X+NTIotk>YMmW+w_uI{xbGdMnA~@p<5koPDy72mEjA#>reIUyAhhh`m z?K0~)mAZle^xQ+PP4FoVTY)LR+JT32pAM_o^c=_}d3kY%vxh_;dIFE-q}C>V^s60H zC4n(3*D;F`XY7iN3zR|PlB$>9=y7e|CZanI^aH9bc8#;bO38i}La*U!fAoy^kth`0&OQab0 z38Zk$w^{iUshRlr8px)$+u>kz&Qf)NiWKbSesu>1A`+YN6$~-_EKIgBr}X9LQ#Xs4 z*ey4B8F`UfX|!4<;tpgrxlhSNnPb_+h(PSrbf7UQRe-#Z8}GK^6$mo=g~B&*a)&8? z-WTvrAU09cUp9E$$a9xZU2w}XPwx|-8{t#7;uZ57K$e@cH!hywhH%B}_p2DQO5&j-(e#X{M<@Og9X!Bg90=(n40>K- zCSz6lPH3;47I5oH%eYBdgc}JSy8Cgxk_lr}?$TA^K|O%hrhx#3Mzu_2CA~Mdc`_81 zSni`(#G^hyWX)=x0$y!sG?RLwGCT^OXb75arIkI|l6u^T-#eneRlYCiNbpyT&em;d z|8ZBUQD;R$T27F2dY77qCt;Bm5}7zQpFO@z?bg|SyRim8Zgq*;gC7CYRncqP!QwFt zZG^PBoAqrPUP2;rqdLs|(O)R@pTXHV`-7I>+b<1fCffPLgKGA7qG9}O8ljijk(Hc! zEbpPLd=@joZo@m4SH*IuTM0ehdp#|4*lk=~HeMtRATRhP0E)deCb{t4t{*>bbs@fA zIUV}Py<3K70U_V(t8gK6j!A-xQnsv5`w+eSIDi$(-we!x4Qr9w9}N(M<$SSR#uqMA z+SEL|4=s-0qzw*_;(r81Z$a{DIcqhQ#=aMRsduxJOp$u*I6=L6#sZxHu~>H+I%|Om zlQ`>EaBoh>{qS|>Xg3JoUONyR_!0B(;`Bvi?cxO|YQBU>2=TELoB zG)qp`qt96S!sDRY2Fymk-Gew*HPMMZ>N7V4ER@3p6>Z@E}e+J8g`7E!>q=9O=>R{?O*%rNxh4}1kyrMuM? z`M_&XkHvj1l$$>)%L*d*hjbgLl>1`K;@cB-j}&=`Q4LZh0Yv+(3zcV+R3#$4_TFGl z)M>m5Kgy!${h@#aeQ-0<5=gJx77a4w|@_eu}UIdnsC&9>ZS<5G&wYGp@w- zjmAPUi##@dCOxz{H!^$p2t}l}4@hZZBWa=GIxQ#xEQ4TdT~&M+2yz2p%y>x`a!w$Z z=w+RnJk7yUS}dwBBDsqVdfv`$xp*XNJ|f7#@~=o7ZX~}qPtEkhZodfZo$w7B3r@_f zyOAWRyU$z8xM=U631s|xEc;S2LMZ6}t{1UVlx(9^eO=LU#%k>)oP0h;jlxAjma+O; zobiwyQ1aQYNP0B;;tdTzN?j2ZC~S1s{XWKN7A)MbC2iiXJY<AtX5*R8@uYqODncws#6=`4*>O(e;G@WJ!RPrM$HnsBL25Vmp>0-;o{K9l7m z)HJYg*9Lm@HXm?wY{PsxC&~XwJWXQ?@}z@2k*JlIYaJea7=*whK-4`|i)(Z%SAcv2 zT&Bmviy5qXeakpS!Frmfen6NR_at$;r1pq~wiO+i6K>zOW2x2m$jJFopz1pp5QI6O)r{tc8#WPEgXl&Fq<~})dXD>=JB?-l=1R5i8I@k?`6}zgp*U zLmhRt!(tq;WwfGA0od@&RQcI4b++>po(ihlqPh0r$QFVp51C%Uw23nbFHNC!K4`?B zXMOPnVw^veivGZ>sZv`%2#zTV8O?B)xwcJ^Vt-N+6wW%w1X~LsIkhB$%0V;<^5d$a z1D*fUaAjLYupKr;D&YR1U}(kW&J*8miTJ;}r`fl?;K9xj129DGw8&A2mht)MCB|>b z*3QrpZ}k?-%_?LnVTYndHV_~6YWgvYzyg#1&G-36+A+%wk*adImGG33DDx)uE!)d` zOy?P+D-gGJeW54b^@84K+7Z_aWet^$xaEUxaIuPW4oBd|r!Ac`Z^vCGQHC_aq?RQP z7wA0&dWNPvWa(gp%wF1#^?~5u3W*FeUU^DG^x(q*0#C(eHm}oBJ>~C3rpPgBEHMtN zZ_+2TP4y=J@C@=qKjvo6=H)hmqK9~fPPdL(`o3GPopojJzWnwB%!35?r+y(CyANR? z;gAszl#rlb74rMBB)cV&5D?er)>M5(Aif{`EfWf!%n5DV}_$E&zpU%^Z zzS(ymcE9jQhe0jS7j4U>o)}VKzMEQbVrI5D_koyi3&)AuCtl%}2Q@BRHAwh6-ROIL zni6vJ5$kFXEN1%x%P-~F@t7QLkyY~Xwtu$$ayl;$Y&mOgZn#|c4)+;k%Kob-g$mq4 z?+h`c9gZ7`hv!joq33!J@!t?17O#P-Vx;(41jCV^o^R{i934k_*!~TXvrfqf?;d@{ z8IKvAZHvd^37MFE`fT8hhH!K!TcY(fJ+NEyCg=ba6_MLd;o-3Q5QzM`=rM4R*c1zG zhq&pkYCi@KtH!m5?bW_mZ#b8f-mPU@Su%Q zJx_t9r;_wo4i83qLp)A=dU7dBtjet}Ojni((W7%$Pb!->NsEB7+>gbOvlCxiM zVme&@G=HYx_c^&~DP3mDV!e*olnM$~2o5>?QZG{iWA=X8Y30$A)tjE?mM;2z4*WbH zS4mRR=XgyV5Bcf)4E$T7+|#4Ykg%y@4DH$$Sqd@yf^BOZ zY$EVHLMy1iO9EY9?Q&xn<%nj=4RG$)E}r#R;J7Ha8bL`cnyq9}SH&p=`wRkLC14$H=kp3tFwA`LVV}9S$)j8hXb|fc*|p zLi<$Q)eIOIfJ{(aLm%)yf1}k!CiH!RnJlIy{}IQO#Ta)vBi(&Ah~>8XqF%%$s{QkE$_c z$wPy=VSD5Egj73+p1~pDQpQFXX|*!Qc!{iH@H+>cLdVt{QaA>%Wv?48A~AHv;i1#D zP-b;^k%x-4&w~!v1uLtlEOA}1%9;nUFR_=D-tc>ty^WposmI<7Vj!&5xl>F5%0pr7 z>^P{4vG%mcWM>!w4#1G>y*;n$oOdb=K>gAuR!SPXzvUh~7Rm9HuRK2!n+RG}?HoXf zL`U=_czEOjwZ3NzndaKoxL5E(^@J+3s&oNaQH$=5$?ld0RZK!`Q95Lqs|+1 z!`~SWonhOPqEU(&UK=FDw!dW6ExTikT|J^-ZeA}uP1h)3JdXBmNsywfRl1>pnGx@r z2?H%cVQf|73>V?g>^^^Ketfr)+QJ`oy7!V8Wk6ORckh8t!VKHbn>+n~m{^dHzyGDZ zq`1^u_o?K0(4N|jrQ{s&GOrsWYy{hj`@&60lHi-pwsqZ&qzbaPq}`$)jkEHU5v5Q!y_A4`yEw*Q1iGeiwmK8!UgmL!#2;HQ1;i_yiz9^ z%SKYU1@h5v=3fYJwffiuqsJ>&Ts5TYZ!Qir-uUpHRxR3vkddTrpiyNIf+3ktuecmx zj-r$1E>g(kjda|Eea(zhGE-H=L8y^C;zK&zebT;^RU*Cnx_7oVR8>;gr2s-_6oU}j@Lyl;w@#0&OTmYSns~aNeCzP^Snxy6fnkj1IShZdn0-{iPWBE9Xdvyh)Z*N88S z?;70wkoaWA@+Xr{9o5f!;jt2NWf_k7!*U8MKRxiVu&Ie{4v23Huzk+b=}kP~Cr;(Z zmes!GBk`*Ix{QV0UZPHGTMcysF z>~N*pGYD|qWTfOs<69oU3Np)E0xU&Kmf_uEA;u*v>i%Iyv-@}j-=Uc{Xe@Btx(l*L?6%S|1KJ|0> z#DxzZZ#r5sLoBqii7Q{p+iGn;<&QE^INmzE1bapQfM%8duA#O>gdEQhs$ippn#4$3 z_GY^D>j=){i*gW)^(+F@_%W7VRI_Y&nte$qgX7M?0fGwKVtHSaPN4wY7MR~)`AMYk zc$qZKsh!awf(&|B`5f+?0@$W>4TLO0phM}MHapCB;EZM-`*aOr(tV@x630TGU@+kV z+9PHcTmWwH$|(rL%rh#&R+kW94(WgrCWybY)uN=Z&LFE7W!QXoqo(^xA6YJ2$TM7I z>LtiJ=!Up59WQwvU}{CSXV+6>2708_=;oOxL!?M5TfnCsMxv)w;VhRY9*XgP`|QxRk@GrPU0s=E%cW_$g9fDQYc4f*2s@yLvM8CII+LrPqT~GdPBn=R zuuz*Qwat29VzS>J-cCel@S0$99rl#8)v-5ywf9}oN#Q-{?jqWO1eaKAOTKho0cID3 zA(Xceic18Oh|M4CeB^>rDL4!qyFiG0g~^FA+(D_>nQ*ja&hh^!Owll!&L^jb-w0(u zLYVpZQDZ?kGdnzX1wN~koo&?GIr=uppB~V7aAE5Q5kcq$uXW^;d;-j1p_+J%o@S?) zsuv)|^2@{sH=K85ZZIdy%>|R86SMXcBDex{_Z2D&_b)qNBT|>pLorPYmsR{loE)9I zt7x{N&RC?la?O%pu=n0aPnN^TLJrD(4@)~bl5%hJUS<8VlS4@ZEKk&6fT@jh06{`a zZ&QHX$>WTUl)4Sn8{$aSi2PUrmLnN^NJiBz5s?ld!|&2{f^E?pGWnJt>K@9)_-7BS z34##KEPPi#_+5rHPNW1~Vdk)6_~0bXojCoDq8d_}wkA-04AzqwW0e@P9=qP8sKe)M zN~AeD_%V$>{^uRF3I9PqcF)NX94Tg6*)Q&5?vs&#d=ej>QE@$OWvZKI95fm7*nunL z=&dB7m5Muy7&eK=Qwftx4;-XkVT4MZ^ex&S`uBdrz0V`G$b(SNH1P$=^mhFIu+1xv zemGX+w^~4B%t_87(MY}W8NZF3NNK^)(9Y5gLK@@?#b8VD*Ob+=ANt)RK;5!G4p~7{ zG3nAjbgq|rU-4uaBNu)ZV1PnC2n*o+%Wd1(J1hahIAoFoET|E}Uq@toO|58x%QaqiQ}^+hoLA5|o4`-FtfBGgOZn(%@% zfi>?wSUJP3L}orD7l%48Dy2J>_W!l`!d#_M@m>iBOU=OUILP#G7v1?et7XK;OTRBW z0v6kp(l{7bnoBlsOn-h7nmc6blTP!@dKnMQRV{-$kM4rEk4R8n>!;#_0n=|HjO7v>PsCDaWKbyzqqJR{ED>7q%C*9!k39#8V>Lfxo`tbQ`QMst|-VDuw3D)D=NtGjkB`>gr_wO1OPQbRgI1!8z-i_zNuo{ zTa6BVW`M!3zqxhmh$nuxndx{DmKBZUWnU%FU(OY+d(vZw?|a?jHR@p5y`Qo&pEVlxHdp$;0nfe=InKOSrj8+i}d_HY*1#mm)a5MDYZ0Y)uPTcvb_1$ z%;+v&yZu`k23IZD;{S14zcJLGBDDU?pu^wSMG@cYIZ!g@TIJg>{#{Z{MMZgUgww9^ zJov~t&AtL)fi}kC{nmX^gmE-nGqsjjUCGI3v3`7 z>HQ%72Xvy+SZ#YVmdFya_1@_RB6=&JEiSM^+YJc@a@H^4&^DsrSB*uAn04O#u$wQ2 zcQO?#raJNXjH~Y-*2$B@4YR!23~F@o#hD3kvUU9W=v#cSQ?_s;6lnieGy~*t3bM1V6 z0GG=_+6~rnk!t7qnYBjr`ftLiugi4Px#s~IJ)Mp1#Fuss_bu00X(!2p)=JHCR!SNZ z%Hd(un2yuUj_+dXyx&;(BG8-&v?NS0$CR{PFo<|UTvhX`z^1!@$#iT8fb}d#d0NU; z~DYx&nn2TXflrj2i@U z;Z_iHPFJ#%qy8L8or8b$OhB_!qDhTb?c)U&>pY#`={GIo zzi4%_7uLvEF8c{MmpV={kG8Ef*?5Xw{U%}A)&8+pYS=fNsbPq|FxhpYWf$!*K!Q5M zhM`nNZM;=RCR*aF-yX>JT?Wrt6L_K4n1DtGM}nmUgdlT`<v-e?(xNU zhwN2aUG%RfqO?E^iQ41o7k^6}%+rGm7M?ZJj4PdXHuLO4_h2=`%BSTAR?rWIH;c@` zvQgOv$?;ms{#43X)9b;8KuiYvggMof=2X{{jF@Tf)}uoN(bLb4Ra90C*X7>twqIsV zGe0lZ22Z%NzEE$^hk2$D@a4mXJW}15=NgsOOxTilAVWyiYL>C%ReZv!Qd)Qg6sC90 zn~wnRKF-94HY3%83Bi8bhS4LTLlBqU_b;IDCL;-c6ui?%rLKG_%tIDgD)-jD*4G*=Y~}Zhff|5jbDX_zb!Q zE)o4Ef~Dybg%BZWHjQ4kuv9KgB2ISnGQCg3Z5yZ$aKkq#J?-H(4wgu1@`}7YW=Rf; z!LE=6I{7znwmi6ex1!a`r~22I5hsCZV?%?t+X@&20WO+mlz%Kku>=nq;r6LLcaduL zCh|hctLxt%KTx^O#Hu*1so{Nq1n*m-&}W_4G@IAdd=TCKJ!ErPghS4kUmfNly*+%( z#vg_~%8+L@MCJfC*~=cEG3+vxb1dAsS>&};CM@s&9Lr{ajU&WT-3K+xpTnXrcFWBG zVYTq&$|N#;cWsd}iJUd?Ah|*Ug$Cq5H5}lvo>-7)bXwEi?3mR-SOz>HEm0IrEhakz z$pLKM8oOu35sIhfQV{22^ZLXo7A&n>Q(XGXJA`8#6gF6E^EJnrQ6T&kZ}(%0m!^RB zsbh}pL5?yEb0X6v_$*n&H(*v&9L5@L3H|6#uic-G-T)Cn}X zo`dj-yO{s(NvXrAu3ci{0i}{mr}6VK2GE0y(#76Ki!N__mU*IT4f)L;cq-(}I{LU9 zqPcwyHx|FH%t{58D|tplTGH$+2%Zb$+@1Pp>_3!z88|3q;_aCX7jpy#?ZV1QZX zJeVwrY{e+-Xl8x)FIa~kEbw@={~4o@m%N4G514T~Spb&h<7;YVUHW_eR;Wizia%r}T7EhMMAE$1F!eEn%gqO zNyU6+?`lp3%`|~-%}6;y#A8z;mGsnFI=uV}x4FhOeQi;H>EBC{1sjL9v*i?_I3&}) zzZb2sz7OPDepd%GypqgYySA#02MY1-H=AYSPKWV0_YSq)$qgrbe;h?oR;z6Uqk~QJ zvJ(&OlTy2G#x`|`m8&GidMd}^QILBih;%a%`MrS6+V^X9$kZR0Kb5>fpI(qsxj@}o z`3R(6nt6YLs#@(Ur71@6tv}$PvwLU<&ZqAY+pr;sbClyekEEX zjW=|=Pprs=0I9d64lj)~Q}FQU$Vi3?T``EA!WJ|3?B4_*whQucM(K-aN82%;dWa!6 zYAFUokOY7CTqMo_L7CJ_*P8D>a;(a7)6(#4WA9S45fXGZ&Fjz4oGbe!vB*naiaOu| zZkgTNE>qH0QKbers5UYTEG!JJ!xg4^C#@3Hgh9KID$bgXgst2Rys-IgHdekvZcDZ+ zO-2?5#)7P$23h651G8gk&RbYXI~tyrn|xFzVHW4~bt`1+KM2ylsODeXV`34ytcI*$)0N z-3NDcqSB!!L*El{B$j<2K_3U+F|FPavcy#bdCC@E>-x(J>D|H@@f8Q2Y$^ZuZKAVh zWHM^y;2JWC0eHuU5)I~&uxKV%on()}tXBgZUm(hXtskE(3 zfb*~G+JXEv$qlmeMOD7?7B@HdL^cP87^%wQZ##!dwLjn}>4n-wSF{s3>pr|7Vs}~x z&Y87$Q?{Je5~AL19Z=@)#U>AI)kUjUW2dl{yPCDwl?gOh?iGMttf!->f-pG^-f*pG z!?~;AUeS}v%-=+`V2#42IbzFNrkH*Y)3wWQz=$s!+NHwSyq^)fE^b;r3b zFARkF=rD4lW_hr(9x0(7AA_f}A$a>GheW&fxZb)hofQecTP^T0((d$DO>|R_Kf+yT zYa?RUwrL^`$G|NzRLg>2wW^8v(NgC3N0N^?c(=7nGNaB%*C<=O_4Iea^DwntK6#- z4H?V7Pk+Q?tQOkSGnB9deIDsfxWj&8(g`GNlpsf)KFLuE@X6tB3 zbhVigk_L<5>HvVXWMzZxfxFj1%7W5kpaR(^1*yEK8mVX*@}^gj<@62Oep$x37cY+r zFY4c0y$T5s0iA0#IUS%hMsqi6tb{UOuJ?KM&sR?CJ-*&{TWxrdi}ToiQpe`kE!MRb z{ZdliFoPlp-e(pyiRC%o?%r-E{{NK|mwJRuOYZNsSnNIouI{{+q}21kA@?DlN`#8? zXE=Y)w^Wd|{U@W9&Sjr3o>1XBU42WZzC`qMIfTz?Vbqy*=&?F1mk3+noPKMyN2f5` zdsMN112@7Ie`6-o9JCZ$1D^7{bb_rc{WF*|^n=n*a@a1!_Xi1-Xzwywv$)4|m|gs> z)SxuF8230#qmnSBP*ztHR@dabN}kG!jx~5?O~ujgD+>0Z`$ZW%orI>pELxsUwy#4J zfsowj8i9l-)fDb?Y=~4MD#qZ-;z3ZeW{Oe<+WQLRv<5q(4uPM%& z%H^T{^kCz8VXn2N0cNU?qZ=E6uVgiM;F~0!uf__oDhjuu0zyR zP{Et|9Gfo8qcc>C6{Q*%zZv*FVjFS*ig+b2AV+Cq!XW+5pmO>b$x)IC)fdFf*Uw;% z?xOtmVfja#A3kOpW%-T+@4| zwO-j%i#1PtAmko2w-%)-j50xj2K9^4q_;_J3{c(bPAZo8#7%d{V}nD57$^|N=~SK! z2oS}ulp)ZUp5uC!^3=rapd5e5cNHr!vdFY$K%qI#U>qC8xCX2oB}JoKuA#5thz}D( zI$j&m#_Me3MVIgd<-)CXqh5I;2k^CgQ0}D>?wd4bF3DZk&tGG2(&xx=u2DK-AD}(z z#OWd>;HVSCeIQ*^a6x&u{MOvAbwaWs=Cx!ujj;xM38<`|*O?}*AJ0U#QSBu-v}s3t z=!_%`pxhx43|XI9Q)i7dj`JXVc1c03G`rv~hs$a-9%%CxkA&$uk4isgPKdyc#T!)$Vzx z*EL=e|KGhvQOmZq$e0MSnPLXMk@7tnCM=p)@Dq{sVSQ8nYuX0G8Rh z20#Ug+F!}QUw0MKP-MZ|vG3Omnv9&hX@?>iD%d)(#)=bN`78C@Ie}Ccn2>l(y&DBZ zH{uyqwF(HQIg zE&3U)^hv$7wi{oDsx>rPK7~kcuLFWZ$sybz%FK5)wRh0vw_fXob_)x3YH;`rdQrnu zDB6^qM5*(i6R~aT)5Ai12FZhYI3eV>7RN6`!V<|vFPK+t9W4IX&V1@!8P zG)fGE75H*J3 z7%(63h-M>tqxHiddxaZ)*Q`oyB|Xjk5eLYUHZrUm~NibdV>bycT^2Rv@``Dp`TFehxz_g_q|JEI4;1P%jed<e7Ex|2r_)SkQAPdB5~h>*nD~Yv#>1mW*3Afv zIvMp@#?t_lS~2kejOM><8{1s_qbly5bin1%@{9yo{`Y&OGn*2aI_1~T$sL|2w8gTwRiK%?G`owa- zpm3J+6Mzc9i6q*C(~QZWoaCBA7ErZ%6LSwuy*X;WRWAQIgW0PBjG%GQOD06#>7EPC<0VJ>U&`fzn#drW9vgrPB;_tin5G&1FGv-Ce+E8dI?ed+n3`^gXq|zB{cVDk zn&;8Fbv+0e%j8OTLz&>$-QVF@dih{_yWmWkp=l|yO&PvhL#!FIDhghi9n0^TP}sv& z;sfShTaU!M(Fli3ZgYdxr+cS>L)U49+wJ&N)@nJV3AsZy&Qw#l;+BcPF z_=rD`B^dF*;p;InJwL5~gX?YQeXlOl@jV#<4S>h2SqdwWJyW+&D!?Kz@E8Mg}ulC6e9 zg;D)EBj^B0TAMg)b;QFV)Twzd`0#L@Zi?%ssIGm4p-E>fiM${c$J17E_!6+%?tilh zoH}_$5{L7QS~B3Pgy_LYE@5OnL4_yJgu=0HyOya}QK}x!B#`IPaDY|{A***d5GkK$ z;S~NDK91mENZU@4n-TGKYl<}P*)(*9)z52Tj_JqSIXsY%|Nm;BP8RA&VjK+z15RG~ zY{uD=e;~TEf^^3;TJrj^T6$317UOXW^40r$ia7D`(0(GfCzq`8%TCxD=q(b*HMjZ+ z%jViyWLesu<&*(Zqaw}GYY=!3?d#MuZMw6#{!5Ljywuzcw-dyi>X>Z;(cdXhCda)# z^Ki4$jifQCUa1Z#9f$(O-~oL|*a@FE6W+ zjtHk~Kcsl(zktMh;^-=zZ&DDf}KPAY6$L?Igtp* z*Q2?*HWH`wRd3&UST&j@9&n2yk|vI$QMQt1XnpQ`Z8ibwm!C%^Ogps4pb9CM!a z@2$BJMM9R$^C*OWa-|{+5+DK`=(#+^XR`sXqlFUPJSKxRsACbJ%z+m0jpd>mF%1N^ zEm_cd4wd>nTn$P9GKFfn?!1t{?1xMlqrBB9=)OX7Q8! zn@9Xlo<|a+_b6@MmSKp;z$eQV3ebQT}7l-6uRJ2qDX`;G61CFniEY21|&Xzy}`g9?vW;u zSCvFRQR>lO6q_+{C!)F2t(lG0UZV$zdcJWr7i~wYyj3T5Xdx3DAkYMq6oneSo45$Q z#1b329iF+)l>ZX7kt-toi4-)n2={@)-cK=Uz4*@I{>2w-Il6qX+uYbW4~@XatKTZ! z%j)HnF$#wiTu_kYL2C(n<>qt@`7J>)5Y)QauFcVR+Pyi2%MFX&WK=@~J}a^6TjrG~ z7exgo$4pt#wF)p&AQ?eWBR~;pBI8Tsg7dmt4oDAFwpsbGP7abg4cZNJ311J6xgil} zglVOM*hgMjz71mZaE$>_J$5m=0>Fl~GAEgU2)!8ah_-YNRJ07dIKgXiNd3fpW!{Xvs-kMQi^ryOG{ae9XpPXn541D5%~A>>WC99yjbaqCfR&fWPBRDP zvJ#(b1P|=NhRoe9Fvz@hrak@~N+xazHg96tjrS@&3OM8V8*z~y0sZspmkSeLKV1$X zu%OzPz{Z?~4dFApj^JlI2a=9`Xz?AS*XcuVSQ3NQW#&~#w1yCfKIeT96Ri8sC-jR> zrYp>w63tX~+TIgGFz)z=#j(lmuaQMn>t#=KB%2S4|H`}BbAlwT1pXk-< z`iZWT;R+AgraF_mCVb4ASCv3Ko?2$TM&LaSHnTg*Xb83#W9coXL95IBaCpc>4gYAS0*6Zg5`iyEWsee!*q#T#4ge68sLV3f!GaF*@wzPz5fV>tKhK*pQpO3EjV}Dv2Qlzdm8dwih>#{E8SN7yrV~ z^0j*te}M~ddHd~>bf)bxFO9pu)`D59viR%&;|ra3e*G{DxS!J$=di1KHsEUq&ne*5 zmQlTbjhF@57VytN{?rV1Yeb7B3Qrj6?EwI$R?FL!!b&(Zs^#l3IPuT5cx+ruMM^_y_QnQs*i=+3(agY> zy-|gX{Z%Mv5~In}vW4F~`k3h;T~X!IY(z`{`$U-Q>>%A^c_2*x7*>Rg*NSoyM2V~$ZE>o%FI0|qJ@PELzqYucN_J=`?U z_=;-+AMq6y{1GDwAOV_SdhsYRI>ls>6nJ55w?puqBsJ_hH!K+xv?PSDWBUXxoUOn;aK%t9dBCk7?LpH7GxGlNejIIRp zjop2c?DF4;4bV?i-@S!ve1m+Jy!1uv<$d~)aa`blWj1>VG=Z!C7%Lspy6<4c8O^FV z-|t6;$6;6&UiL*OUv_GB8>*iOWNXKO3MfiU;~^UI9UWW_<|dL$WscBi*(tDPc5l60 zB%`M#DKGVzW$R-C`yWX{!tI5{c8U`_4u~7wj^i4}ep7x#cZWxJn1T;=!@D*he8ZjD z>!$SG2f#Vn#JIF?S62IDs~F@3UEmZ;5!T$!tP=7a{ux-Ms9)=#Wnw|=)fsFnTR)aaNDEzQCDk;a_C@HNX|`(#R#Yfni?~70_T3+R1KR}ye){PL0=J50mVdQyBw_a2C3(tpZ%x) z%P_g&vsa{}*lB+?1IY_pcXQaFzil1+B?1LiUVW@}g(urj{|W^UqtEarBfK5Ql=WY` zAt=#lS97kn?T>dYn{5#7PoddA&<@{~*o|BG&#?pkG|+y2kvg~lT$cuEuLzBqaPE0M z0A$CM_HDu%%x?YCA*@`3Rkq>DIPh+FHPOlwLQzVht2gK0!>rH(K8DOy-LCeVd*TTC zS}5M_V(9Vsvdt+t>2sIh-M2#YNw$GO;o5SVGngj(o$PwkanszNr`8h#u93Cnf`yB) zY_@L2-VzUhGk&($+<_?i; z@$eJS96L9q6Oy$_q(%K1SKGl2X`qq7LcN^XDp&?nLgrA|u5`UKV4IhCu+A-W|$d$ ze)7!aj+6((Jki#zXzJbPxNRR~!jBKd^M>LkE@QejOyF;0)%D8y0rijFT%mx=%&x1| z>y*IE^tQQ0qSN3)vo!`3t|<@=X}u%EG!xc*+LzS(cZQOblOoF%-;$Rl=-++v zmq+sNWq5{>h13XF{nKv}K~ifmq&JiQGAU0vq~Td_&-aWO>%Nd;z_Y{nz>!B*O{Ge2 znO|^`OZ<>E7%r~Z(Ls0b4%gp}Dj?wqzziv+BU5LRvkD`64l+8<;8IMYlMZF(&=JoZ z$4vxMhq;-pdB%vg)8mel`Cy|*hT`2hqp+rdZUQJfxCr@=i;2NPTy{8?MkN&(8`@x? z<#T?(^-df_K##{zC$Ft9zjR1pXsm#BcrR)utBzURyt?YbUy=;1IO7JqvhpI|rW=rWF^t5@Z3(#dr{ms}2C zm=a48{lIkJQk`Qo!T|AtVb=4XX3}kl+Cr?{9QR_%urOg)jQc)eSw(DMC=;~*^glk; zN9*o_7)sdRi{e^`cmU zMt=xPGlVo^ntDNAeKZjr2P!*6wl3W|FzzvCgo|LjccIGvid#PH6 zxRM0W#$L_70#7C37bM|b!QW?Sxv%IHP^@bE4K&SC!vY}au2_OGYw?ma@iJO=7rB*s zIpnP2>+#e-N~v6L?=I>!C{f@Ld^>0syUv$Dj4|GNB|(cZk~T22kBl~5PZO&{J{IiH zI)lEnPBNXr|EPm*80pPwn=C!P(wLF?XJ2RVa$4_fG$Uy)Yq-?hUb3B4W-gfH`oI~$ zqsOGcbkCILYmZ*Dpaf$cI3jMJT>Bz*dvuBC2M?f6S&-?rsmcwvwy@$7`)?I59qT{$ zy_l>|)(&AujNfn?vHKpUsCY;0>QfMKyawMIicg?}#aU?)fxaC?!xyiUz`PweBjK4^44@Y#8>>&xTzn%Dvxp2KbA>lDf9&oFc34(dG0L zFlRqCooEZiCfAPW(>G;=mY&FzBQlY|lS2#JtZ8!kpj~Xi%C(~!XxLk`irE(Xy?83r z@=%@FDuNp#C33q&nYHPYjurys2b-ZMW>kM_G3u5r(KImnf5M~b>0#dZsyh};rYxP9 zCr{d%i^^g&6?;yDjy!FuWMxC9DDi>;#ZHjqmb|VbxMYDh9o1DkK&e0{l+5f5wgixY z3))UK_osnMW2k>j)X7ViSW)`r$Z5@^a(%yaF`{z!O3HY0w_6;tJ|=DBZNK+=%VmO4 zAXK((e#^T>Y0hopho()A*?AHOlzkI`(N0>VH=tVxH=pxS()80N;dnGWx5=^!6*{Jk zA(Yf`$AjLIm95|&P_ky*9#3eZh_Ic8nPQ{$j*pO}$_(sUC<+W3B22ht6ZJBAzub56 z=Qq5I|1Qzn9+;PO0QNmPum1R2j=R zFmu_NAnx63yy76^U8CSXMI#g5o0mjMDr%SnDnN6+8530~rdkQfMu8g*k@mEz2mojv zVG8pNb@D7{tzi&&c%~P?3{T`l3WMp`8A2b;m2D=Zf>*Fl5c_#UVT4ODp)ZB0CtMrM zVQb;iQV-$QEf*uG71kuxLA2gPwCNG*yomv zANnxO+1i2zil){tm13gTO|ZhD=QVobmWsE$zLR^kuDW#YXlBKV`d7z1nd%(pDA^aD z?FTS`r(R;Uz2-S~2O%=FnZqZqT+1L!_l2?jt+xOKb1hB2HqW)7M&4}&M0B=MR65>s zCeEvq%BX^0M6#_Y5so_g`JG`ou8r{Sv8bVG+t!wt^3iw;aKejcNJbpkvQUYfwUb8r z#xysfCNAv5Ij4v`tx96!I74TJ9JZ&ZvbM`~Wno<%8)L!wl=JlG;H56}0TGJRa^A{6=R>nz?zAfnrgo(S>f zA`{f-1x?x?fs=9drF|6>@ttTG@scoSQinfC#O`nfQAf(>)6dHP-g)3|UWNociBwRR zzoyZoFf)}PPn;fA!$gK;FX+>z0tkv_=D@LBWW!LcWIeR?jcg!DV@Gl)TVJtJhaRf5Q!lo6nKu#rny>JxAmnr(kWqB{qMiXWqdc9A4SAFLD z$s&%_dAVdLBiLV@Vq`q6@F3N4EPXkl+{-_lzxCplO)8s)-Si_rY`kGSo$?o{eHj!| zlLI;ek8n;h{$0rVCh1deyp$>sc55kr@Z~~Ewk>1r3M)etV{3&;rAxi7hc5Vag&!H= z&ZV~Ps_N2bGn0R7WTg*Ts6)g!J9m)?7SW%EUCd8FiE`A&3u;G^e*qt*d;Q<~XmDBc zztH1$hGBQVk%s#PGE>pSu8cTa>m%HbEY?oUaCZ7J$Dk5Yhr|VznX!r?>0L`oNUfpD zDc^|gI6Fjg>oa;#_=Sm#5!If1#V&hH?@WM30yJTXYwfarLA6JL-S%p_OC>KJd>&(R z_sHx}U01?hRZ(iT>>Gxf9^@W85fR&Si`6$=Xwe^TARi5L<=1mvbJw2@jx9pnoao2$ zC3R-fK6g4{+R%+jSNLy?6pQjLE^@V2P6H%1coh*YvQY7Ov3e>rs!a@ubz)C_6&9Pt zoNdl2N*Y(Td1`mO3tEy_QBYD6s|DN_1=ftj{AV@`hX~(2!5mC|bR+yZt71yo&Ba{~ zNBk0_T~w~(R!~-lm3tr7WCH?Ms#L9Y$q>+;>dG)aaj2;$sbVx679O(2*f#P;!P&Z84Sa!& zU`_wUwsbS&3LU?06;}luqredpiN6v8!S(xj8UtujUM_KIZnbR#!^%a;BR>h81tr1& z2+?GVuv*Lp6_f;ud~Xi_Mk#BEgLw(ulbUrA(gf#Udhm;{L(f3g^zs?eRMqn)+xmXl zwq?wY#;M{sX_K^U<;$t~IwR)j$Fo((6TS{o4q$Ph96yeI5b$oB?MB6p8sscf?1=Kq4EH{t8nTQ1N!Y zB!3ei1ZT9T@%1M9_cZVbsk)lxC6F+Q=6rlke{6H1&8T1QUd;8@3)r?m-J00S);}W2 zMd;+?uU;2-8R>8r-$xoJ?L;_xTPeJm*h!v&u2>vW-I76#dX|w;o3TB*uYm+8abW!T zs^$Q3#=0M5WmcW7tq6PCJ~&f@LuasM6Dlxg)w(isvyZD$oZNLj1$S6%FA=u9ClBF?JRu7j5MIzKSwfm~ z*3BtVnis_JU@nIQW!Q!@pvC6!TYL=C z?Nq<9^;IotWaeSkvjZQwqN{6{?v)|F-9#n%!C;vMau<{4g{;N>hWBU=2B@@~?s3?r zb2D2iOIq~x^T1QTsBV7jGDUw5YKH{=A$i0rPfEXC|0XZj=*iNL9l}PNs@_#@@H+ z1N~pP*R2q<>2AlNEb#W%zMb&L(m0iEHicHZS>C6HXfbo+iVJ48iz$DbEEPw8{<(e` z$&a>X&w0Gb_(2QPp_Ffgwk0rSXi)Y7aFyI08=fTL9+sv!a6@W)O@us7W_2&_o}h*h zMUI+rXRF2}!@$(kimhClAgVQuizp@>+lRk#R)JAy4qqHvNOmST;%P||6|-PM7hrqI z_ehr6HHSgo8G{Sf68gwZtcR4ZT}eq>DKw=}VGz_Ielw3Z}X=>{g~r{*oB3WznVdjC40hytB3FnAgYD{4&p<4P z<7<(9Y|AhBy&j`7g|w?cLLatW7~BRewioA2fbjAI%=J6CU7#C&Oj@D@=WdX%g4I39 z-@H0@^JQOG&*6IZN4?ekuMFt+M89F|?S1#WaN>La>n0h4)V7=A80sTI3qmB z^Fufa+-8dI)6fdqX_*)FhWeLF2#^+;^tGw~3h;Z6Rf{e39~5SuFKk3+M1Z8=GC6>G zH^1~zJ0n40_b@&Y=n?8Z&+B(&?F9x=h|bK5s@_%?%1{0{`@-i$u{fnTNUOyQ;cx=wkc)Ud!l)5>-5^lG=E)nnbs-`ke0|UHI}$lS zjj3V|gj9r}Q5K~NEF?%{9ca66p{z*FGO-=deQ~Es@*drihQ?A$(9nPOigyi1k zff3{NlUHUs%QsajTc#8?;*gJV!MrP-YtPsc$K4u&n=x_>lc8Y~v~ulI&KiX0{URZE z&Vey)Rf@hU){qvW%2QO=9_}&El2sXpUfOSUOqa}!#ANv-Fmj4V;E-@8?R2$57f0{V zTRE~9K8FDT4S_E*;KTrlD{eL>m{drPfeIR}QaNgO!ijxx3F}JO(`R{eByHDsZD&p! z2=@&Gnr{vVG-Li6m*e^2vXkT4atIdbajD#0EPzMx7aB31?iWF!Wqqxwmc7x~Si z&M-b)-9}H>E{6!{)#%?3Hw-zkw$K86VQ*|rotQKz8x8@MuvfHEF^V4W1#t9RmcIML z{io%EriLaDJh5Kn8RrPO`AC@fg;K>1xOxPvR?AX{^Lk}tC(6z1NTl{Sds6}zlgL+< z(UFmj=;ZK&kh6U+5g2xw2vm?X^7H=OnhXn?E|cFGske8EF-mLQD%uv|Zq`vN%T@zE zliAnCcl`S49OI9q1`TLW1mAZD=Yy z{?E|&+^nd%d2kC)_s2CboFq_-xUgh15=3^8FgF$}WQz~j@odp;K#_3d2E&5x zC@f1XWWnD|$)`?xGJY;Q9xn*|{UtoDa)bbp>sf-4^1O}`E|EXEW!x>$*;q$e?Bbe~ z_{U+A+>=w~%SwP{mB6w^xsedOVfwur-muNw7%KOsX52h173wXQ9W%aj|02rx%6Se z3l07P*d5R8$&*m+Fi1o@aPMx!Q{u_i-e7FbBEs)w}!Dt3}u+`eweYZh)?<}7?GUsG%YZii-6fKS`YZ>Mo;@xaskyD$JR z#Nq&WtN`W*#A&f4`y;R&jGY*9C(zu%6XQ-7t3}=prNU80yH((3l0L!QWY&|WIAGXj zvEG&U^_iex=&Qa=HuogARLKWMhz3Y$XVlpVkm$t}A4}H~Oshq{4}U#ZQ-!!e*G$nD zhX$}Nh!2%)%80+;P!7PSCQkxkpdYu@1_;=nJ0Wr)&TtHq^Z5}@+!}btuEjY_dgo%a z3t-f9!Qs$(Vk+YQj(vDle84I5l^LIv^M`L`dI{eUo&uG-~gSM0?xK%$Ik z7I^DUTdXc^)_J>zKNxrr=Ph)2lwRueam*c@Hvr;&Yif58H|;Fgkx&YS$_{JK^KnV+ zHdwiZ^&vSzigl`j989t1?W^ZcH))EITq$a9)FA2tnDI<5`RQOEW!H}_#ykH~7DX1k z@3qzqK@xrFa8y}aFnbz0T{y)Qz-uO^SnhT^$}05HXN=LiL(|i*t{Zqg1#C`BDQ#73 zId;mb`Y|OKdIvXOSZ^pMWJvvd0e=27y`OSQw>`>Q{Qf&~@x)JjlG-TzRbtfpb~g^A zOeU`0X~4)5@QrxT?Up|^P);(U_7`_yGK!eG3QARWF=-0*2V7a3;Sl%vSsk<9tS;=} zj2%SnX6ngsU<|&^G4!AAN2*7AK#l(Hp?Q+aG>LATVhUM^OKVc7 zHKm>v#Yu!wGlgR1*HFV?pe9zqyw|`bZVf0z+f*Qd2?&@EjOKuUd%5_i`9~y5&52zW znc~kTuA4nYIvL@1Od!iVNdRNY;)iomAm&V_y({czqk#49k*hnFl0?i_Y!G*^%PU?f6^MA~_@djiwx!b)%IijIiUcSO0VaYy2WSf3RajQv9AslJD+ zxUNNVj*ZA)>z1LQ!Jr`uWwB)X-KiuTpJHVn)hA;P&MjBr$ z7*L=uxi$nL;zpr4_z>^8C6u}?AdbDBh}`_E+!eFZTg&Ygoc_6RbsA2M#N<+ey!+h= z8nCs07oRst@~c!M6K=ahg56G?|7wz7Ow(Fk3mcJz8Tob8c_TPK3{K5emVT^@b~kQT zFlsblWK~lB;H4Fi0G2kW#CBs%(~LbLWND}Fe9Oy-t~ngZZjpBzdxANcx)re=u)xpp z$oL^(4Fpx!7|8AGv^PIfvX%;I0uTO*3#4EH0QdWM>Re(x#hIm9@WyruNsMj+MdQ^% zJG}l4AA5hfM=C#&hO`N~=T&6wEs=qYU9kzNAGp>_8rl+cRD*z-xhlSQ9TjqPPOT1W zxTKM7HQ3aIw_m}L7}Barrsrz^V@h!2$PgK?U+ZS^)bC>x)=e*1Z_-_cA|hs%Xr(#j z1_bR%uaYkQSUPG*17@YFVuXUOAU|Hv7Z%pOR3b5iBjWYdT!$751Wz1(7-lU8GLd5y z3t<{lwh}}T&&J&GoDN1a0_H8f^i_oQGsJ&QMlE>FZ*_V$SYDCqThKA3BJSOC@xd4c zS!!P-*8fZD%S%c7E4T~ID|xtEgc&FxE@gb#8ZC?5KcEkcA_LCjU`Mj?9Ap5xE();0 zQ>v&x0-hUFU*=H3@c;4QEg-!#TBK04+76@?r<5t@4)Go>~|ccC`C zW!@O`7ITI~urdukJX;yL5i}flPA$7*#HIro!R#NQ>zEWfDGMee(uXIOg96yXq~>|@ z$+vtyV26b24SD<#w`K=>tz~4<>ql}Gp7Fh4nY%Y~ja_3s*}dm&FxdKG-XCjqIIkMN z(cD3d&d3}TT(247M~`9RETOAz<81H`&lAO*pU07w!14)}Y{DN|(XS-)L5A401_c)> zpE;y?62D;>=Fw&Sjp&dBS_B|7w*AZHAi~l?pjQHAs<>D)M$$&8GOl{1G!p;^yWo~h zR+n9sY!4pI2WK%ckp|@_vOd>**~=TLNra~z4U(#`PTE^WT(b= zC~*JP10k%`fm9+pgMq3qGNzmNhzY)8ehEkuKRT6+4LZCwK}C6!`D{W{w-?SX-u#B)G7$V3XZsHg6 z_bM@1Ug$h1JZPG!%9~W=H6;@15$zkU(pEk!1OfYEgXMh)a`3vKX}jJ)sY%#|YF5MiBDIOh*pXa?ze&2lOax$irPw0tGk&v}uH zox|~o08FvnI}(GcWBwb_PTi+(fs#rEdQN#LL&*&lLI$&w?_RurpoOBp9%1nG{K$To zYisu0k`1vI)DdaN>horrSx#BYAjEFq8chuy!&NG7Z9S9jVaX!7&h=@N0?&DyE!pD* z3SJ}?wp)8c==;?olo(tJIqcG>i@p_FF5zkNKE_ZU`eQQ`qb@I0oS#ONsioqtXuoRr zyNZ*^?Yd?WbfuW*BuVo(SP^J3$i1&pOwDvDjX(i}?5`j*G3)@OvN`-YI&^K_*|2(m z?OGz2{%xyGxHXQ~v19c>_Qo%Jjo@x`D#2>-{&M?@P}=Co!#Np@Y5~lk7>B==k@4UQ zthz%ZW^{!3Pdz&mC&W-W>DpA$Zw>!x^Lp@5(A?WaM+ zDgyo1F#``HgWS*kE)L}yQqyY+!9x%VV1^mzLwV=@LR_4wz<^%_`JTJI#jlTarWD{< zfkPOCuwOJ{k)KF8fx1`|@VN)5RQz@y2i|p8ai|f;RPjttVq$7hrjU+~bp&P<(S$GU zG{HT+VKd`;1L@Weg~W`%`H0X&44LA-V|p)%V*G0%D7RQ;X;Xn~ed?e=QK(rmwb;2mwzfz%%exWT;CZ zEoU&AZ^8pS&mfWP4pZx9?XcpDOrc)d(@g{4TVK|y{JE^?6 zk15G>o^~?sQ1Ztag*~gqjvY;fpZb~VT`$>K2vihBGBD1`OTLI}B>cfN);v*7JvYwb#|RyFfLhULn}7-pyqQCYY@{x4j$@&`%_z9$Kk8|b z?UhUv5iG%7e(%NXMCtQxG ztO_F{H@rQ+>u+i{rRY{;dh^lLoFWIY*$T9ARqfRg)(T8qhi`EU?9rDAKElbix*_hM z)1B!s2285})gkQF#Ia{rvcsB@*i#EA-2KfvglRCL7!rPH{brVG_tB99YPIXinnWS=~z51NZNpI`0#(~{WIMFMpMkc!uKpdJLdW3;$Su1$=eU4HUNJITVT z2XODSQWa)5>)o7rCXTX?AG~md+lDzYd1uMoVT9Y<*fqT;^tyLBg6vA46r*zUe16aA zJ&wQ@xpO(>NCZICA`het8Q=_vH-!nb;*mAXMZ)rx~TuoRy zKN)aS<~c|QVZDU>8PuCVM?A!=p!7VxI=}38H%NHE6AO8zzRMqLNYlwyzEI%=erO1UF^+(h5QBCO-e7VS zy_J~=DR%V-2)lYmkI=5Y`jEIXy(fOT1Vx;?0StZ~m`Q%gPwOi_I-o?4NuKY0c~?{oC~J zvq9kAneToJ@}s}IWbBNTC#|LV(dFsZGDlnZWWA-Y%U_T?OCO( zosSj6Nr}L1`9$~0p3l9`LTFt9N+pUV-&DO+0f!v3?9ybT>1Z&B+>1o3`F<`v`KcTO zbvc?6VI!3rNACq+fDu6$bjC_s z1XfU4uI7`F)}_P2lN?})hHI>JL$sf^3*4?Lx!GG%6XJiGOlEoXmDIk{}jW_py&Y3Nw4J zda!`IM>V;kJ=ZP0f>>FChe!1$@TTp^4TcTDQfKuTmYZ6(Ld^mLA`aRlQX0{Bk3Uyl z)fSitQdn^v-}yJf6R)6N%qDf2D8EdHp?zAQ)ejPR-+@5t*M-odbF)aRYtx-sgIwqR zF-9BiN0P@e*_jU5w&3Ggq(LLiS6$Az?@PEKdg>90oq>O&_`&D$!v=jOyRleFY~Tff z+XWYK{P~X+1|`=*5#Rc12_WYFHjESO(H!M#&*5Zz$Ebm(>S$+TsFd?u+A~GNB57-5 zpfKg$Y_A#lh8FeX1k!#DgEDPhiNOJs-NuivBU>G5g8gD|A?dp_N}KL6q0EImqO^Lc z+z^S_q%%bMefVj}BSYp@pP+lzvvK*hZUu{nIB4V-y7lgH6^#CsY6JC9LL+sEF^=po zcx?-Rk$4&RN4o)Bw*?t`80<RUv?=x`rF2N1Y458bb0k@zT z$3t7pbhqk?B$XTZa3J1UK~To)DLV5lc@1~HLqFGA>3$rvSqs4=VeW&P-4v+XR)RfY z$BuK}GB!&vi-*YIo1Y{ly77C~fH`7xj-fbz)@=rSMcdk|DZC9!3|0DgfU^q3zd^p| z`^15U2Mvcn=r%Xm(QMe4=h>TW!M%$BsfixM=*ip=Ox=P^*QgG!&v?2TtJRpHI zOg33rS_u}b^E%8I!>ZSVdsT{HudQcp!#-w1L*gEdcyU{pw+fEG;VGdT$DOp57Z*@@ z*!mjbJnhZ&q6!Rru1?KjTV46!waMV!-!%zig@ghmpM5JjM%|x;Wvy~LHUgD63 zbADH9-}0_46@ijy!Ie{jU4`;agAwKRo9Nare_^P#Ib4s%>fg1}gbbQHOv(@oQ`kl+r`ZMkyiD5cCxBY`$~*ogT) zapAETD*y^P4G);&Z9Ty6b&rv&SNB55Xd!_Dw+7XP#(#S4gS7kxk`6YKiiR9iKIWiteeQJ;fV+OxD!AA>Cy+8@^H@pzEx zyF94cnQ*&-iOC_MldM2i2vCc**s)#csvog!c!7DKx@ZzfbAEt}23sOvPEJ|Y7yczo zN>jDq{YAz0FA@I{8OH_tkhtqf+6ZI|gY$KDY#8A7qywK&A0OOG9>e~o=$L0j_B+F@ zJ>DN>0iHqQ7y>h=*08fSh_ua3f{OPe&416UY?Cd1boj)ocm*_{;>aal60Pe`vfC6{ z#|jOAr}U`*sVFGqi0BwM202?0bw zwAS@VF@nALeTZ3Q{yw#QMyN?(?#iY%Ar9F~zi4iVtkJgsGCoQ&|1&$y@rA(a*XHP6 z?!^VNZv|dO1T1xoq$<}0or$ux(JrW*=5?YMBQj5!S# zGAsPwq2jHW$3%#J`-e-c*`DPLwr=qD@GgJH5zJ5M`&ldd)rk%7pn>R#e(1?>geSQn zj5d+7yvZ4lQ0_pW2c$Tk42j1++n#=jG}wq_ZIK2(|J2L)WmygsnY+FhQ;wV_v9@Y* zA)$~lXehf~JNclH$boB0Gk4_xs0#T3|6?&fM{q25+9%^mc9n2K&<6;FJ7V3O@LpX| z0i@i|781@7p)IHbu`9jgeE|(4<@Z8Mzc8wiiAP1WYLyf#^KW+5xjO|s&eX<%_zOVC zO8b}oWe#{1%IanA)j?N_9jF?iO#Zj{$;`_M0PUC)dq*7HZ3=h?Tfyz zz`DC-=`hH%SUt1tsK+YW&ELQb;|5A9=tW8@5{W=_B2^@#-ayu#x0hVq z(oNq6MTD_p>jf^q)UVxq{uZA4Ncj`yupZqxSKGUmw5Ld7@kUt#u_oSVzdFV(OrNUO z^;|#Rr!OfrQfDwFP{Pc80As#%SoB3SsK3i?3K}UlC1%Crl5}o-9cvu#^kl0{L-(oP zqX<%)1M0v&RY>Gy6arH3%W@`CMXQg#bd+v80Glsgc;Wty0@z|Ih63GH&iBJKu3fUH z%4%ud^@CadLeC)PUj|PwT0?+2Bhq@v37ksKuDY#Cqv2Wo(a!3=Yk7%&ExdFeEU(lr zG>Jjek$y$}CO3?|C|#sk52PSB8J=)go>97{c1-xQalIjJjm&m;O;O3IRdUOc1!?;` zK55dQ+6enP^O{|HP}k`SBgO<`H#QN1kK)Eo}>9qu%r|Msl^JbmvRs*-N5!#T3!UuzQ*8;3`K z8_hc=%vQwt3{ktkS6w4q=RQJ0hJJdN_D9=Sx;fsR%bc0FmgPHbT6ktIt>b`%`j%qM zxQIQ&6R;wv+b0^%W1e0aa%^O_%WEFiCJ)Pg+0K9CXj`C{!m%SDq}v>DVtE2JN>bo*;E=Nv7VqQ1aE5srFsy-O^UB8w@STVwU6I0D~VKSHcixmm)Wk#7nk4gq>^D z^AO!;<6`XPhfXE-Z^zhH%6(vMCNT;W;ekp&mNuohE-V%I$5P0yIBD&&`JFXBcnSt+ z#xjCLDoH!dDVQd`nf?PnU{r+al6h_i$_q0ZS_ikyGWnrLA@jzaKs&6D{<>sMjbL^} zO<_}4pSWB0godfJG`8y1jCF`}ufQz8Ol=SKRp+EVlo?Rc{wZ|~Bdk$35F^<9WZT0; zKUq!JB3TzcIN~z!YCBe=n5}Ziu_)i%)$AV8y5pg=HW@b{=VPAZSMGSI`mZrxsyleO zaXCWzj_ofwg8L6Vc1@`!e>}ClYbTt@XQaMLf6`;PqV>H<(g*~>yHGoDC;)r>1mf8y z+-5lb8-@uiO!;h!!eR!S8W~c!5d9_}e+)Ie2EOOeH}-03BEA^aZ9TOKI&%Wb1Y-a& zlq71DJU5m=KAEY22-8eXB=9ljcJW@Fgnu@iI2RF@72VXDMV!bxt^&mcPANJWTzkU8 zYEhp_n#gap$dBu4-BM%d))ieBNV9d?P0#u$^=C1 zyf?c6rb^8)0EgrONJi5Y;#oKK5sw8QQ-BQFNv!6krDZNw$+Qb)AUYtm7?VDa;7LMS z+-U_W@m#xE5avXkE_u8(NTnTZQld>K%lwbJ#)Uud>zN@5=Uzp0eN}nbZ{o_3gwL@; zIA4)m7{=J=ldu$7x>`dxnsHY!^xXxqv^s4caaV_#mtGDssPp_v;rz-_?VJc&SL zeD>1XeD+;_XujXdVKjr~a5%I^&p&a%1O1 zyWEW50L4)#5&og=>v!XH@hxf9h2WlsUooOZmB9QFz~0HBQFYSUu5AKF=DUzxc z2IZvs-AZLX7-BSp>x9U&3u`%Neuod3i-Nr(X$>cq4dye13lL+72QV0)Ccn7gtz-8=su_n`BKn`WasA;`lvW=wHu-=f>7z zk>BtN^^frL=>I0x6f;z=+@3nGb^g5nN_ErZ6w7&#UE>F{_;I(Ni$+nd|67H2GtEmv6dp8;{N~p5K#Nh z+*)|_D*myj@ zaJ=-iw0P0LA(aSz70#E&pe3jGzjo7Eja-J6KRJ;~J5~y7S4`(vC*cqz?M6jw2Iu9B zq~|06gc!o)+Y2`XSH(Mn78D@8@esd`CtpC&7`cZU#HefolD>zx=d=FTF~UZG?Aopg z`islp3G|*OuS*Mhb#O}J8K5aYU2u7ncE{{# zq5WA0G`L=n;Kr7aklgyyi7^{rEitu{k}gW*ob>l@X^2pRy;3m>$1|@Ky_SG_lOmSS zV$6PeDRX1UlwbW?sG}hpiW83zuuMQsc*?_VWV}vcZZMYNUPsfv2mAhOyN`t?{*$xJ zGj3$a5s&+z$!0x~GR%r%(jGa)HsMqPyI0{Fh}hOpBmNUrIq93ju1BV)1Z!7NQsUAV zvFxBk2^#V4VC$!q5CpP;XuQM@!rEBb78E4bgLh5i#t%}g*u_)C-v@MX#~%I`Wf}HN z;*%UN4$MhT2}$Ncwt@j>Uv$mUqurXNfqL2C*9L*)S}7cVZDg@P2Ft{6NxeE`t^ zxZZxi_zg#M_Vp}SLu^tNjijRN$9A!bYI(0|R;_!#9!9Lbm}K)vA4V=rW1rvis9-NU zlO<)6hL{q94mtKL1J<}M&kCIhwdZt|C7fn06aoxPra+1mbL|j?S80NHfEnHFCXt+AS?n67n*zKlozn% zA>OpiXdPy@}x0Mg* z!M%RaL!PL?7gjr%=NBJ-!_@$2uE&Y>uI+q`95*1;utG^QgolAW;B%Yrp$Ck{Zlpbs zH4yda0kbFQNm8pQp;*W4-Z2 z{6(ccae2v_rrIw`4i7O4>f?iZb5eTT<2%Z*sg^)!qd%V*JNa$+VdVUFNyqfTO=?*0 zwz4-?J^9t;EGnbrp6$;Q3YARAMYX*)=Ep`i9Nti8J4F?za9vgz!ja|3ar|fS3GzV9 zA0FMev%<9`o`MqEjXFbao5^uWE0lS)_qz3E7O`d28=S1U;lrmPZAaXocr&~i5si`* zBMy)6WkfSxImV|vxyAQG+BEQIsR2eXtJ4L?AGw1KDAR#2AF&f}v(})8W`#H*dI&wwUFCn@`E?r*Bm< z9@f-hpGx)Y{RRPZv~H=@$JXG}HY1&``g0z&xjUDc&vnM8vzGlb`q+zhS|{*UA3@o?2C*_@vPekjZK0rBXZb$C@Riqfz_si$+w;amJj%}I zmhT4H6m0Ic6-=2br7{l@u=h-`ly5Aqzksd2G~3FVx<9Y=ZCK zE$p~jJ<8((i0I$9rlGRh1h6F7=Z;4=-WY92yAZ{E(?vo%FAYf$O>U5mQ-mB@eLPzcTd5G)_eo$Sgsuhv3UaGDKsF2 z^SFhB^m=;2Cc>yLBW~bp2jdjTovbkE3u-D}`T7RjJyU-?J$LtQ;j{Y}4g#q2IkhabA$0Zav2$+0p)x)l$sG%lq zt?&DU2-uzj zvu@sbLQ7_(q!G`4C_8W*O)FuDz>p|ztpTPde$|&an}TUFjLjQT;o=Buc8H6HJ-h#N z?(CH8*vH>us%W8f8lbai4e7LI===!-g`-N6r#j(OabbfD0qK-`KsAui#K_W3oL^p4 zQF(TO^~}~;70n;{(oTB40LL~Oq*9UdH3_a7=X)8SVM8KJ*P?$2Y<~{KVF?8l_gm9W zPCb8^>&)^FGKNRimpw^DP(Ic?5mgY=U^_&RXw}H>RuY+x3XHT z!g8@^NyyVIvJcOPHNv7Wv*DS7?JMyBMl0;<#UD*_lm(bj{yYkzF<9nKQO1u5&E}(s9UV*_2H*7}_7HhR#xtP&0Im zMo8M3Z}x!HOIVmhd-DinWF^vIurAvn3o5lw432ljIJ(uI)p3gt=pgsLc4> zQodYlj%TkSn^^asSav>V6&exvAaKYwJRo`8GBK=a`-(tc*bDVAL-Yz^Z3z;j7T%9uk@AL};pBTMV{Vn?_O5SVkT6nb1pmKz{q0l8b|G?B z=lZ#p>$Z_m*IR~-gN@Q%sD&w4sj6v;E~dYaP3OYN1b>`Pfq?iWk_1u^RWOVjVBZfZ zL-9&sUt0n7qwHrdHUm6)x)(LOq%y>)b5zLg#Q0lkjE7{ zHZ|EYAfH6D$cY(5oBE*R={tv%OX(XTSA~38QnHsytmv~>-{Xz-r>1lqfEF#?rWi)1 znc78Ze7-1g72r)rDnQ~1POb%al7odKO62y|o>Ye?As=opX&~{jSG{zOHOE4w>CJI` zN;(4c(p-3OstM9p_kypRSkBOTB}&W9tZejkdo*C`(guueK~uSmC+U#cAqLWl6+vo1 z4>n-di+#s3gJ{YrIjhT9sr^VA4eB%VX4qfYhMR|?c}@`%p~)~Be>GUW@v?T96juO= zbQOIE_Yw82w+qbb1GEXa;gUnX-s%{g%n>Y?N+`VPKeTo~27a^KuemY5a?&poHd%#z zM&)d!l79HUV@N^e*ccNdG#=04LpC~@52XwGz6`}^5091CaY?_zSHPDuFwKsahxzKd z)T8IE9obtH`19J*1vC|=AH&q-SD)**PiC)%Je5CUR0bx`%%GDYrTr5z1+E2cbSura zvnJbqvLMX7N$^(zM}vK@V=n+~fjpwr9RLJp2rP5iXBLYy7M|r=pUWU+scjjQa*Pb1 zhnc)3X7jAgHr)7w3;sIHn;5*UdW^Ek5JS5+a}r%vLY6Zm%)y-5eM|z&^h+&jXEc^C zYj%qwgdX|N6bOSHO3*XBsd)7RY*)n$AvGzsVf5=}Y+T=>?rC4;BAesP77I^o2e{cPW=9pMDX^WvF*_d|v4R?+tqh2@QlhtsF#mJP45d5347` zSh0FO8;!|qML8+_zzkx5mQ=WzfH#(0Ty<2Q_}{SgkC!O;vGv>$c#0-g1>>ND59%RF z(_bpOp^gqrNwwFT070prvA%~HU$mPdp4-&ph%ip_%joJzL3!>_B@6Z6OlZ5@j0uS2mDGG2z|=F4IO=1ebnM{#7aDUDcZEpE|yBQ z8xRGXMP%ow)z{ofBcc5E!nVC|$Z&Uv-%|k6gkni4F{vEb*P6Q32y7YWURpn=IK!q; zD7zefTBES{1bWj0D@+m{5y4CP6hSF*p|_=VQ_Z~2PIcyL`z;f8vtf?s*_os}wDp~` zFafP&(hvntc4r#aSIIK1?WJ|od=;+hC$RFOzv=N#r>#azv594fuHCXUX`x6+%TV`~ z@*-l3p4NauUxLCB$g+)L<~$AMz$!OEcjdSmWWATSEzBe9~)_6pv+=PE?pg% z#3yr2*n{T5$JfQ#C5ArSl~-DAN4#SMW!TH5jY~R6Y=d2jMPma8i`E6UDV}OKe0$1$ zZsEgrK716Fw4{`xOppHo+Vjr zBvMNjkKjmgu9wI$sqMsByK1?FQl+d1OWGgUW*6dtLt3{TU9({T@Cn63m5e ziD0F7-`}!i%Ph;lq_`9}5;YalF3@^n+z050MB}l9Eb!aOolEOf$ng{s3DMWV5^}q& zq^24U@Mn!aIv0SO$4a0j{UQ)FsBO21ga4Z^36Habf|&BWfnpTZ_3PeT$Wp>OC4sj) zfpb(CX_-hy`1m-MCR%i6&dgEIo30Fm=@#zlxM0v3mWR^df8icU8^Y6CFhsn95`{~- zt`~#LSs$39KoJ1SusgXDVw1()93E~iW(s?87!2Aa=za@v%sWSni&)Dyf8zKQzg!u= zLoXVY{XU8=RDwo_n9r}!Vl}`kZMuD-)tp)QT@VtiaytxMsmjk)!Hr6&t&BbB^%VzM zwYkmoGLutR-;>s_ z8Xitl8;kyv+n4l$kR&y}WC|@dMNlLqmM0hMV+64QFHmey0YO$|z~~{^6c`P?dH6O} z4I7Z|9qP^p`@r;Viy9@5rh6o92%|)|(+I)IW=)52u9eTC`<*b|%6e{6Tok$VG-OCbse}+{e!B}?ZmU@Y!e@Yw%k>4mD zkj?k~;M#0(R62c_{S#^`w~>(h-Ce5wPjT{@iH0|hZ$q3pO}z)IG{(3u#Zk=)SWcoZ zmXqL6C+(LfgK|-Ft^~d^Wf1&Udyf*<6dQCYph_$a($+F;2YoqjHuUC7P*Z*p1~(6~pQ>OFgav&!!;bJ`HMkz-bk4MDXYmC>UAQmp+cb;8iUpfxR< zf`?o^XZ4rO2B*jV%QUM1Qh=O$fjJc*d}JO^@GUN;2J1HWZgmkWTyl?2NU4Na5@Vqk zf`UU#nu?_HaN82_dn8P)_>m7h+IC_*7DiTJ+Rl^lX-n)?D;tpXOQia>B&V@0cTulW z0q~o{Pfnct4Fu*B23kl-l7Y)#snCE%nGgtTB<|}9B#$tL@w_O)F>eQa&WqP7Tv@2n zGbp<0o?kB=2Tnon?OMR4gU|upL8btIRGbWvtao}sTG)agH>!*^y|n#+*yf8PWPWO` zvny;`zjsmlW(et+PUQm#VxX|vAjcHs?05lbS(=S)Fhy8lkF{iRt^ zR^&Wbxx8QYbp}|B?S(3dn~tuihx_N6F$UGr&Vh+qwP%uzWYVGmY!A_Rz%Ar@5OL)Q zQ^so3&)z0b?wwWF(*w9ekRPLbz|B;HCaHvudh(yO%R{5K|HJm)wTzU(f+g~3a8Sxah2aa`xJg1AN?^DQv7HrLk^I3a2|BkRC~MA& z9@%7VUPpool^fzX?4upIkYtBCVYpZzfTjOldfyIfM@}8wlOFh2`zJ zadS;;zfu63aS_fi>+e*^2qFIU;=Ih^9B%5c45OJ*QT7xt_x;=A^f!!^1j7GoL=C?E zrDgm)(1o<+uhS&Ih5Fx_+OC6ud+hpBAsGFVv>uKpYPWf3iqi!Z)CtGrUrQpVtuJdO z4lJlE$1T!wXrzxf9wqlFOK|*I_*%a1e2HRJ^F8z`tO)FvvHkYZ2ah94ZH)$|J#)$^ zvCLwx4^~XLTIz^p1_a000pAc!nSChXJ4rZq*z7I=Hr7`R zP^9MqnWvTy#I-1WwR(J=4PqXl3peD949~k~&Nvn{(T>|=PPrVB;y^r#4Fcg|&;q0L zUKiv6>cBRv=WCAxji-jN7cg(O5ge%S30JCmYbcpeKew#-WdvPLe1(Q^+I&Ak&5}az zm2ksMiCu+tKG3E?Sm5hVZ_a#g<+KpXdq}MvB`G`!@!hc<$jK#)+~roEqu3y>SAl=iXyQ}rJp(Ee7c0U z-nDsYySUugWgeWAt=_YL-1PLJ{L`Mx$*>1Qn+#wwtU|3hR$l9Wb7qvh_$>+x_J8T2 zyGOL9(m^*dq3OVB5qfG~;d%A5X5C;$tnPO$Skd8CZnf?g#@#puFRaSZ`OciGwg6$U2Sk(IDVnQ# za-r3&1)H6_dp4QJ755@Z!eo%z5M*`kytkJ9gB_7rM^|5jy(Eav&{#}o5!d!`HZqEI zc6p4Sj_#v^=!`GK&neo08tmFee@oQbwN0Du5n8K;bGCb9EGmF#Y3^P(xybhF2=JI1 z6|G9t0{r3ivooed1?A<7v<)b7q~NDCNpL1%bgZq6c;9ocJuR{P*)GwSM!e%AEy=Xk z-dC5j`TA@8v3&eA#Ue|;dfXg4cSQZP{&6a@Yj!SETwVu0suq(ouiLFnnhN#^Do;)5 z+UCgLyee0If<7re??dJEjZ9+tm+ZiNz#9*=_P>s>n`ch}OgZGMfZl$F{C0Bh^Uea9 zB~J_$J{6afuM7y)UZ-rXiFaTW8L+{DJ;56rleL-M^8(}dq2D4Z0d_-d#qjHVL0^5{+1_}c_#2E*@ zD-UHvRNmA-+Ec<&eu8LfkFP2eD`eVBb?~>%jDEIu!!m^~F)!S0^Z`;SQM8pQIe?lm zD^pUj+bg4U!((#PAe80~&$+!Ne|rivvIQV29b-;ziy-!8%^%$yOsEj?kKj8o?W&V3 z>3MWta&DS0eS@^8R!O!Rl+dq}Y4f_KWS4T{!yx1%n0?HX;P3~V%+p*Y5|k$|KTqs! z1>bmWw+GJF5Dr&T{Q1&n#kmM72d;d!q`=ao_NEbHLqjt&rVl?Z5&0v@TX|le{m_Jf zTJ(I6D3*oh>x15kP7C*Myun|~Q zK*QiL^Qp%c>#R-eO#v6m!g)ES(xN9-Ip95`_o1pR$r52kfPL`5zOOEQ9H1pH2g${g4idxUMUVEv4#z zu0!qq!3sGriyK8R+E_V12d7f=krTx2WfU|Jd1>Q^Qg;daFYd>D_Q4Be;Q3BGP7}mk z64Yk_qVA`J>zRP{oLuSiX3hdZ0CgjN+^bjRBvzJT*+OpuR~AkT&l#7I;z2A!njUEw zU2bkd8!_OnqMQaOIiC<7A8$Cqgah1otnR8c*fOJBUJ*!v@FjCQw$xsR@_ZK z0cs(^s4gA82&@W$=-CvOPIMaz$WWKLw{;_nkCf>;c+U7v|7R1+pm;+Hx!;Hcs#uWu zJ@3rsEm5LhKSUp0oi-1`Hxqz5&2Alyqj)mpD5~6AhEg5+HEKrIbV={*LzfjfPJT-h zoNZCn#YERZJ)*rk1r6w~fRL`fzCC-AFs-w@JXk_2LQSnL)t>9vI@-I zSpkIN%G=pZtYvH475zkstOuT~vaq38Ingd?NXrQUZG=(qCV6~%xM`8S0Q!mJX1Tr< zLUH?1=Y#mhwl->P22xupE8DsUi-+}?kVHb5!k}cp6kjy{8?BShMjm>cvWZ#TEAGEr zuREV#H&aORSR~fB+V({U1?cIDt?Rz~!zG>hYrX7tySm=8AP{1g z(U}1Tq3~0UhnNEwLwk_H_u3#RyMO&LjguJVrAlb-rX~c@?SlP%38jCyKOT29IMD3= zv=YVbDhUl{>z;%Y>G0?#r3rI%6WBs@^L%G%6nfFM;RNyy6tEIH^&B7MU{Ep&dXFmg z5_(l!0hP`JDBk%GnqW^TXl7q3$C8zzSb~z3*)67?NGXik53pDd?#`f7n9(^1;i5%$ zj#j8eh`yp|W2F(^99&r@_BZL|Iu66Hvj27-RG?`%XHgCZ{#g~OC}33)n_@;a&Nu`S z7(^x(_m4azob!bJy9%Vgee9$!*aFihB>?r_?rJP-;Q!fNk!(=7wn@pie29h9o^IozD~cvdlb8uh z_t3KU8E3rLSk__G5hUhKZHs$CbSvQ!F!m4i%Q3B`I+kp-YV;E8D? z7pGFx+uRn*bFejG=Qz^-(r_(C=A_JN5LhyJ2*EQsIUZJ{*XLA(jp(F_UvRff7o_-T z^2g>NnXIYowScK_n!oQig>+!H|4l}IIv*|Od;J<(=Yv_2=I)R|P>xe~3B)by9ej*0W#YQro zN(}8$JnZNXL~Y*?y119?e-s&=bYJjF6-%Lb3D$kt-2F;WqyIZn{p4_p8+F@akpAe^ z1klDCK3JOH_bo-*6Rl21g^2dH3)UczWKYaeku1Crkd0Y}K7-ZZ4YHBH; z|KP=twg2-68K^SrbJ9l} zs_vrZt7d9bO?GqX3=DRnq9K-e%O?3Ud4!ms+2-OpF?F9Qf3pmtny5Is=}x}7NlDsE zcH2lBd$~-?3f+27Peu|9@owiG8LEt(-#@E3Z^}pzEs2*$sq?un!RH~3v8fm3ije4OFAT*K7OTGVuvLe%dR^S0&m@combcJRVdJ~7X~`x zVQzS>Gi$0Y#oa?an2)azX>E!>r3E*cBBK5Rb^7&t!75)EVx2CpUA-_)>x8&-{sa%ktuDe+AiC}r zEP|M`j@BsfAi@9$q}D4H#q$^SK$15jmMMPeizuhQ=qq7NTB*JBOu5a=*{RD=34ZMC z-jo%hR9C0NuS9K-%MgOwZ6m98GTB>y(>xunWLFNg{|6R)SBcascRX>vGc z09(`1C~okN(iBVbSJOssGAa7+vfd5G&e6q>!@7gzx}( zimyLNS;5?iS#Y_bmYp?|lao_e43B0_8%PVZ*pspbU6|iGC47rRc5Tw$LqMt4DW@#y zJCL9gC?ND+%%+QGdWA=9`6Wk27uGnPicc^@b>oI?k%pF^;5o9Am{b5H<@mmm77;t9 zhU_G~pXYEODd2g;R8c(9voNiw?Zt%ObCotQvOUM&rN%@?(zs@tv4$cE3jc4UNltt4j1h%w-x0*p{Ns zm2amE*Xd8Mjdz_u;WQ0Sbp;;``RAepJ(_Tai`20%#ipvH(FYw=i`rYbe~piYxHzkL z#W_dwgU%k0xLMW!q^Rl$oL?UW-LK#N5{XNG_rr)m>Q6k-l-ax3l+GgDGp8BNU?G+r z17!Qq^-3zgKvU*SX2|u7%XQC(`Egc+ttzQ?*W-?GZ$s}^4abOkr-M4E})Q#$j(QjcE= ze}h|LE#Sq=U)WHe$mM`~Fn~0EOPfUpciFxC)*samN9OGdFU>89;VR_zh?1{ZEL^L7 zK{Y2=yT!#37oJ+hCAKBwgu%97%AMXcTtIi^10XYz*qM2nd-B2Sq53d0A3+Kos!oHL zD7X&*AgTLQo1ld2enY!)kw(tsxp9#%WH?n80@ybjE8ydSaB$8c(k02cVl`pu3GNnv zs3AQ9e0=Vw@P-!vT8zwcoIs@xjzq0o=I((Sy`LVGo}bg`(hhjj@ z$52c^{usxmkYV{-c#!RsQRD5G@S(FBDX3)Y#l;9;Q_m;{HkozaG5+zc;)r^#g8r;T$-zVb_UulXAfuSUxu=Kqf(9vDTWX=V&UrIj! z!hMB#Gf>Yu#PoW>5xl_p71Rpb20M{5Ki6E|#y-RP6%yh15IPzIYei^eFrwF9-~OJG zis0fjyXp%%DM-WUIwRmo*vOMG4lpGLZ3R&>Ug+u!Vtmcx$SvpfCVU8UIY4IhYVrJ3 zzz>f3htS7vss_YJMXw$x>d5);dn{4mtpBy?Kgj8r-s#1CrQ1wm9c~T8fE^O4O6%M0ti-(QQZ@10^bOoJ;K?kX*se?5pXpd zyPl`LNrvxLlIJUtquz4bWn?tMTm@lv+)n|A@3!+edzl%jLQ5W-MXMOppo8-@dU3OI z+>$JZqSz%hI*oThngrEye5wn?FOo{RGR_l__z(@@;o#kkSwrBQYj=I7l4v<^hjAw< zwI;uOJMUT?nS$op2b|rZN`FdDM~)1?{(@f+a&@FE&I}m4aXB5fUN{paqstl;ki5;) zTIEJ9n*({1BLDqR%gE*B?Pj?th;{rddbc*56YVp21nlpeYe|tLBndz6A+&!#?I9 zckT`ia!U#5)gUl406LE;zuOv=ub>BE!wuj)EMPPmM)Fc z3xNXjM-o=PNkf*|b68qDI9v}fbqhm70$1{h1AKNT5?i1Uw7qr!N14_eu?ELZDL!+- z_a37NOATO=cT5XbDZuTZ5KWN_r?pG&rl@;333yx$ zn}}1?>jlZwU-9;)|NRVb7XsQ@&x5MF=5`RKc@~UG-qX1?%tEn7{RPSgGUZ!$f~!LM z5hT(DD(y1dP{+5BOqJYc3)t_%2@419k@enY1V1(Dn0XNx48CIu#3n+Q_BM;Q^^0tF z)5&3idtiv0nI;^NB+Q~U_t;hE?fvo^X{@}zz$~BLL55`?{E-l_ZK(I2Ttrg&RI&g# zmG|l`!&N}6j`Bg*o?DrNSiHROc*`gpdf*x}b0lGI@!6VeW|;p;srn##>F3pyxu&0f zTHV;0Zh%+Zkq#e{Q*?&m&doh<5yMAX13&rh@~CoN)ZjyNb5r~3#}N$E`8FJA_d3$| z@rysx(&rL!QYAKv~K&8cTlie4G}!=m|M0OXmdc3pu4G_vOacNQkQ zy9~~+7BO><+B2Zuvz5h9zY$8b0Du`L)@c6Cqz}`9iQu6dYDzX41k0Yjna6#z#eVbw zAZm|l_S%>Xdp+1btAIAP(@}-U&5^?oVmALeY`CufUx&IErMFZj$sL}SkxS_JTyQ2C zdXn--=!K;&E~P?ltmd)K-)+l*98cL46OaaQJuUZph=%t;k*+{3PAX@bfOtq@(!{DY z6y_+xf;Wjk7{g5@hK3Vj2AUYYZ@ar~6{2u&X>LZV;`(Y2!cgrjgz_&|5i01nNPCr2 z44coVo|&&rCg!VuYYvwwY}R-_LR4=$t_y^FR`SRn&_;8Jp-_%Kd3X~)QNEbVWV>fa z14VYmf@rfQrnBkB8Cwlypk-I5_n@Xv`)gFrehiⅆHQc!s*Qwe@Vw6Fz921?#zU) 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

` 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 WcGr_`N@Q>ku3n%D;o?3D4SWihJu62l+r#%xypz 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