From 1af513c548bb874ee7e195d851b753254f1b2fe7 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Sun, 23 Jun 2024 23:36:25 +0400 Subject: [PATCH 01/15] docs: update chat protocol (#4344) --- docs/protocol/simplex-chat.md | 45 ++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/protocol/simplex-chat.md b/docs/protocol/simplex-chat.md index e3d8e88ae0..efa4edb959 100644 --- a/docs/protocol/simplex-chat.md +++ b/docs/protocol/simplex-chat.md @@ -17,18 +17,23 @@ SimpleX Chat Protocol is a protocol used by SimpleX Chat clients to exchange mes The scope of SimpleX Chat Protocol is application level messages, both for chat functionality, related to the conversations between the clients, and extensible for any other application functions. Currently supported chat functions: - direct and group messages, -- message replies (quoting), forwarded messages and message deletions, -- message attachments: images and files, +- message replies (quoting), message editing, forwarded messages and message deletions, +- message attachments: images, videos, voice messages and files, - creating and managing chat groups, - invitation and signalling for audio/video WebRTC calls. ## General message format -SimpleX Chat protocol supports two message formats: +SimpleX Chat protocol supports these message formats: - JSON-based format for chat and application messages. +- compressed format for adapting larger messages to reduced size of message envelope, caused by addition of PQ encryption keys to SMP agent message envelope. - binary format for sending files or any other binary data. +JSON-based message format supports batching inside a single container message, by encoding list of messages as JSON array. + +Current implementation of chat protocol in SimpleX Chat uses SimpleX File Transfer Protocol (XFTP) for file transfer, with passing file description as chat protocol messages, instead passing files in binary format via SMP connections. + ### JSON format for chat and application messages This document uses JTD schemas [RFC 8927](https://www.rfc-editor.org/rfc/rfc8927.html) to define the properties of chat messages, with some additional restrictions on message properties included in metadata member of JTD schemas. In case of any contradiction between JSON examples and JTD schema the latter MUST be considered correct. @@ -77,8 +82,22 @@ For example, this message defines a simple text message `"hello!"`: `params` property includes message data, depending on `event`, as defined below and in [JTD schema](./simplex-chat.schema.json). +### Compressed format + +The syntax of compressed message is defined by the following ABNF notation: + +```abnf +compressedMessage = %s"X" 1*15780 OCTET; compressed message data +``` + +Compressed message is required to fit into 13388 bytes, accounting for agent overhead (see Protocol's maxCompressedMsgLength). + +The actual JSON message is required to fit into 15610 bytes, accounting for group message forwarding (x.grp.msg.forward) overhead (see Protocol's maxEncodedMsgLength). + ### Binary format for sending files +> Note: Planned to be deprecated. No longer used for file transfer in SimpleX Chat implementation of chat protocol. + SimpleX Chat clients use separate connections to send files using a binary format. File chunk size send in each message MUST NOT be bigger than 15,780 bytes to fit into 16kb (16384 bytes) transport block. The syntax of each message used to send files is defined by the following ABNF notation: @@ -117,7 +136,9 @@ SimpleX Chat Protocol supports the following message types passed in `event` pro - `x.contact` - contact profile and additional data sent as part of contact request to a long-term contact address. - `x.info*` - messages to send, update and de-duplicate contact profiles. - `x.msg.*` - messages to create, update and delete content chat items. +- `x.msg.file.descr` - message to transfer XFTP file description. - `x.file.*` - messages to accept and cancel sending files (see files sub-protocol). +- `x.direct.del` - message to notify about contact deletion. - `x.grp.*` - messages used to manage groups and group members (see group sub-protocol). - `x.call.*` - messages to invite to WebRTC calls and send signalling messages. - `x.ok` - message sent during connection handshake. @@ -155,6 +176,8 @@ Message content can be one of four types: - `text` - no file attachment is expected for this format, `text` property MUST be non-empty. - `file` - attached file is required, `text` property MAY be empty. - `image` - attached file is required, `text` property MAY be empty. +- `video` - attached file is required, `text` property MAY be empty. +- `voice` - attached file is required, `text` property MAY be empty. - `link` - no file attachment is expected, `text` property MUST be non-empty. `preview` property contains information about link preview. See `/definition/msgContent` in [JTD schema](./simplex-chat.schema.json) for message container format. @@ -181,6 +204,8 @@ File attachment can optionally include connection address to receive the file - `x.file.cancel` message is sent to notify the recipient that sending of the file was cancelled. It is sent in response to accepting the file with `x.file.acpt.inv` message. It is sent in the same connection where the file was offered. +`x.msg.file.descr` message is used to send XFTP file description. File descriptions that don't fit into a single chat protocol message are sent in parts, with messages including part number (`fileDescrPartNo`) and description completion marker (`fileDescrComplete`). Recipient client accumulates description parts and starts file download upon completing file description. + ## Sub-protocol for chat groups ### Decentralized design for chat groups @@ -197,6 +222,8 @@ The diagram below shows the sequence of messages sent between the users' clients ![Adding member to the group](./diagrams/group.svg) +While introduced members establish connection inside group, inviting member forwards messages between them by sending `x.grp.msg.forward` messages. When introduced members finalize connection, they notify inviting member to stop forwarding via `x.grp.mem.con` message. + ### Member roles Currently members can have one of three roles - `owner`, `admin` and `member`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role. @@ -207,6 +234,10 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T `x.grp.acpt` message is sent as part of group member connection handshake, only to the inviting user. +`x.grp.link.inv` message is sent as part of connection handshake to member joining via group link, and contains group profile and initial information about inviting and joining member. + +`x.grp.link.mem` message is sent as part of connection handshake to member joining via group link, and contains remaining information about inviting member. + `x.grp.mem.new` message is sent by the inviting user to all connected members (and scheduled as pending to all announced but not yet connected members) to announce a new member to the existing members. This message MUST only be sent by members with `admin` or `owner` role. Receiving clients MUST ignore this message if it is received from member with `member` role. `x.grp.mem.intro` messages are sent by the inviting user to the invited member, via their group member connection, one message for each existing member. When this messageĀ is sent by any other member than the one who invited the recipient it MUST be ignored. @@ -219,6 +250,10 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T `x.grp.mem.role` message is sent to update group member role - it is sent to all members by the member who updated the role of the member referenced in this message. This message MUST only be sent by members with `admin` or `owner` role. Receiving clients MUST ignore this message if it is received from member with role less than `admin`. +`x.grp.mem.restrict` message is sent to group members to communicate group member restrictions, such as member being blocked for sending messages. + +`x.grp.mem.con` message is sent by members connecting inside group to inviting member, to notify the inviting member they have completed the connection and no longer require forwarding messages between them. + `x.grp.mem.del` message is sent to delete a member - it is sent to all members by the member who deletes the member referenced in this message. This message MUST only be sent by members with `admin` or `owner` role. Receiving clients MUST ignore this message if it is received from member with `member` role. `x.grp.leave` message is sent to all members by the member leaving the group. If the only group `owner` leaves the group, it will not be possible to delete it with `x.grp.del` message - but all members can still leave the group with `x.grp.leave` message and then delete a local copy of the group. @@ -227,6 +262,10 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T `x.grp.info` message is sent to all members by the member who updated group profile. Only group owners can update group profiles. Clients MAY implement some conflict resolution strategy - it is currently not implemented by SimpleX Chat client. This message MUST only be sent by members with `owner` role. Receiving clients MUST ignore this message if it is received from member other than with `owner` role. +`x.grp.direct.inv` message is sent to a group member to propose establishing a direct connection between members, thus creating a contact with another member. + +`x.grp.msg.forward` message is sent by inviting member to forward messages between introduced members, while they are connecting. + ## Sub-protocol for WebRTC audio/video calls This sub-protocol is used to send call invitations and to negotiate end-to-end encryption keys and pass WebRTC signalling information. From d9510031917bed8aa8caa6c428d4385c3bd790ba Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:51:55 +0400 Subject: [PATCH 02/15] core: servers summary api (#4319) * core: servers summary api * rework * server known types * set stats file path * rename * local simplexmq * update * rfc * update * update * get servers * compile summary * remove sort * rename * rename, refactor * refactor attempt * refactor attempt 2 * refactor * fix * fix2 * remove space * refactor xftp * update --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- cabal.project | 2 +- .../2024-06-17-agent-stats-persistence.md | 87 ++++++ scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 26 +- src/Simplex/Chat/Call.hs | 2 +- src/Simplex/Chat/Controller.hs | 5 +- src/Simplex/Chat/Markdown.hs | 5 +- src/Simplex/Chat/Messages/CIContent.hs | 5 +- src/Simplex/Chat/Protocol.hs | 3 +- src/Simplex/Chat/Stats.hs | 268 ++++++++++++++++++ src/Simplex/Chat/Store/Profiles.hs | 7 +- src/Simplex/Chat/Types.hs | 36 ++- src/Simplex/Chat/Types/Preferences.hs | 2 +- src/Simplex/Chat/Types/UITheme.hs | 1 + src/Simplex/Chat/Types/Util.hs | 11 - src/Simplex/Chat/View.hs | 2 +- tests/ChatTests/Profiles.hs | 2 +- 18 files changed, 432 insertions(+), 35 deletions(-) create mode 100644 docs/rfcs/2024-06-17-agent-stats-persistence.md create mode 100644 src/Simplex/Chat/Stats.hs diff --git a/cabal.project b/cabal.project index 5a607b257d..be95820371 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 8a3b72458f917e9867f4e3640dda0fa1827ff6cf + tag: c7886926870e97fa592d51fa36a2cdec49296388 source-repository-package type: git diff --git a/docs/rfcs/2024-06-17-agent-stats-persistence.md b/docs/rfcs/2024-06-17-agent-stats-persistence.md new file mode 100644 index 0000000000..2f5d641769 --- /dev/null +++ b/docs/rfcs/2024-06-17-agent-stats-persistence.md @@ -0,0 +1,87 @@ +# Agent stats persistence + +## Problem + +State/state tracked in agent are lost on app restart, which makes it difficult to debug user bugs. + +## Solution + +Persist stats between sessions. + +App terminal signals may vary per platform / be absent (?) -> persist stats periodically. + +Stats would have `` key, so we don't want to store them in a plaintext file to not leak used servers locally -> persist in encrypted db. + +There's couple of orthogonal design decision to be made: +- persist in chat or in agent db + - pros for chat: + - possibly less contention for db than agent + - pros for agent: + - no unnecessary back and forth, especially if agent starts accumulating from past sessions and has to be parameterized with past stats (see below) +- agent to start accumulating from past sessions stats, or keep past separately and only accumulate for current session from zeros + - pros for accumulating from past sessions: + - easier to maintain stats - e.g. user deletion has to remove keys, which is more convoluted if past stats are not stored in memory + - simpler UI - overall stats, no differentiation for past/current session (or less logic in backend preparing presentation data) + - pros for accumulating from zeros: + - simpler start logic - no need to restore stats from agent db / pass initial stats from chat db + - can differentiate between past sessions and current session stats in UI + +### Option 1 - Persist in chat db, agent to track only current session + +- Chat stores stats in such table: + +```sql +CREATE TABLE agent_stats( + agent_stats_id INTEGER PRIMARY KEY, -- dummy id, there will only be one record + past_stats TEXT, -- accumulated from previous sessions + session_stats TEXT, -- current session + past_started_at TEXT NOT NULL DEFAULT(datetime('now')), -- starting point of tracking stats, reset on stats reset + session_started_at TEXT NOT NULL DEFAULT(datetime('now')), -- starting point of current session + session_updated_at TEXT NOT NULL DEFAULT(datetime('now')) -- last update of current session stats (periodic, frequent updates) +); +``` + +- Chat periodically calls getAgentServersStats api and updates `session_stats`. + - interval? should be short to not lose too much data, 5-30 seconds? +- On start `session_stats` are accumulated into `past_stats` and set to null. +- On user deletion, agent updates current session stats in memory (removes keys), chat has to do same for both stats fields in db. + - other cases where stats have to be manipulated in similar way? + +### Option 2 - Persist in chat db, agent to accumulate stats from past sessions + +- Table is only used for persistence of overall stats: + +```sql +CREATE TABLE agent_stats( + agent_stats_id INTEGER PRIMARY KEY, -- dummy id, there will only be one record + agent_stats TEXT, -- overall stats - past and session + started_tracking_at TEXT NOT NULL DEFAULT(datetime('now')), -- starting point of tracking stats, reset on stats reset + updated_at TEXT NOT NULL DEFAULT(datetime('now')) +); +``` + +- Chat to parameterize creation of agent client with initial stats. + +### Option 3 - Persist in agent db, agent to differentiate past stats and session stats + +- Table in agent db similar to option 1. +- Agent is responsible for periodic updates in session, as well as accumulating into "past" and resetting session stats on start. +- Agent only communicates stats to chat on request. +- On user deletion agent is fully responsible for maintaining both in-memory session stats, and updating db records. + +### Option 4 - Persist in agent db, agent to accumulate stats from past sessions + +- Table in agent db similar to option 2. +- On start agent restores initial stats into memory by itself. +- Since all stats are in memory, on user deletion it's enough to update in memory without updating db. + - there is a race possible where agent crashes after updating stats (removing user keys) in memory before database stats have been overwritten by a periodic update, so it may be better to immediately overwrite and not wait for periodic update. + - still at least there's at least no additional logic to update past stats. + +### Other considerations + +Why is it important to timely remove user keys from past stats? +- stats not being saved for past users: + - important both privacy-wise and to not cause confusion when showing "All" stats (e.g. user summing up across users stats would have smaller total than total stats). + - to avoid accidentally mixing up with newer users. + - though we do have an AUTOINCREMENT user_id in agent so probably it wouldn't be a problem. +- on the other hand maybe we don't want to "forget" stats on user deletion so that stats would reflect networking more accurately? diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index fc8833ee31..3a8284a74b 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."8a3b72458f917e9867f4e3640dda0fa1827ff6cf" = "1mmxdaj563kjmlkacxdnq62n6mzw9khampzaqghnk6iiwzdig0qy"; + "https://github.com/simplex-chat/simplexmq.git"."c7886926870e97fa592d51fa36a2cdec49296388" = "1r3nibcgw3whl0q3ssyr1606x4ilqphhzqyihi3aw4nw5fmz226h"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index f8ab75568b..3b9d2681d9 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -160,6 +160,7 @@ library Simplex.Chat.Remote.RevHTTP Simplex.Chat.Remote.Transport Simplex.Chat.Remote.Types + Simplex.Chat.Stats Simplex.Chat.Store Simplex.Chat.Store.AppSettings Simplex.Chat.Store.Connections diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index c46eed70e1..539ad003ba 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -20,6 +20,7 @@ import Control.Applicative (optional, (<|>)) import Control.Concurrent.STM (retry) import Control.Logger.Simple import Control.Monad +import Simplex.Chat.Stats import Control.Monad.Except import Control.Monad.IO.Unlift import Control.Monad.Reader @@ -84,7 +85,6 @@ import Simplex.Chat.Store.Shared import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared -import Simplex.Chat.Types.Util import Simplex.Chat.Util (encryptFile, liftIOEither, shuffle) import qualified Simplex.Chat.Util as U import Simplex.FileTransfer.Client.Main (maxFileSize, maxFileSizeHard) @@ -113,7 +113,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (base64P) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol, ProtocolServer) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import qualified Simplex.Messaging.TMap as TM @@ -369,7 +369,7 @@ activeAgentServers ChatConfig {defaultServers} p = fromMaybe (cfgServers p defaultServers) . nonEmpty . map (\ServerCfg {server} -> server) - . filter (\ServerCfg {enabled} -> enabled) + . filter (\ServerCfg {enabled} -> enabled == SEEnabled) cfgServers :: UserProtocol p => SProtocolType p -> (DefaultAgentServers -> NonEmpty (ProtoServerWithAuth p)) cfgServers p DefaultAgentServers {smp, xftp} = case p of @@ -1315,7 +1315,7 @@ processChatCommand' vr = \case servers' = fromMaybe (L.map toServerCfg defServers) $ nonEmpty servers pure $ CRUserProtoServers user $ AUPS $ UserProtoServers p servers' defServers where - toServerCfg server = ServerCfg {server, preset = True, tested = Nothing, enabled = True} + toServerCfg server = ServerCfg {server, preset = True, tested = Nothing, enabled = SEEnabled} GetUserProtoServers aProtocol -> withUser $ \User {userId} -> processChatCommand $ APIGetUserProtoServers userId aProtocol APISetUserProtoServers userId (APSC p (ProtoServersConfig servers)) -> withUserId userId $ \user -> withServerProtocol p $ do @@ -2253,6 +2253,21 @@ processChatCommand' vr = \case CLUserContact ucId -> "UserContact " <> show ucId CLFile fId -> "File " <> show fId DebugEvent event -> toView event >> ok_ + GetAgentServersSummary userId -> withUserId userId $ \user -> do + agentServersSummary <- lift $ withAgent' getAgentServersSummary + users <- withStore' getUsers + smpServers <- getUserServers user SPSMP + xftpServers <- getUserServers user SPXFTP + let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers + pure $ CRAgentServersSummary user presentedServersSummary + where + getUserServers :: forall p. (ProtocolTypeI p, UserProtocol p) => User -> SProtocolType p -> CM [ProtocolServer p] + getUserServers users protocol = do + ChatConfig {defaultServers} <- asks config + let defServers = cfgServers protocol defaultServers + servers <- map (\ServerCfg {server} -> server) <$> withStore' (`getProtocolServers` users) + let srvs = if null servers then L.toList defServers else servers + pure $ map protoServer srvs GetAgentWorkers -> lift $ CRAgentWorkersSummary <$> withAgent' getAgentWorkersSummary GetAgentWorkersDetails -> lift $ CRAgentWorkersDetails <$> withAgent' getAgentWorkersDetails GetAgentStats -> lift $ CRAgentStats . map stat <$> withAgent' getAgentStats @@ -7611,6 +7626,7 @@ chatCommandP = ("/version" <|> "/v") $> ShowVersion, "/debug locks" $> DebugLocks, "/debug event " *> (DebugEvent <$> jsonP), + "/get servers summary " *> (GetAgentServersSummary <$> A.decimal), "/get stats" $> GetAgentStats, "/reset stats" $> ResetAgentStats, "/get subs" $> GetAgentSubs, @@ -7755,7 +7771,7 @@ chatCommandP = (Just <$> (AutoAccept <$> (" incognito=" *> onOffP <|> pure False) <*> optional (A.space *> msgContentP))) (pure Nothing) srvCfgP = strP >>= \case AProtocolType p -> APSC p <$> (A.space *> jsonP) - toServerCfg server = ServerCfg {server, preset = False, tested = Nothing, enabled = True} + toServerCfg server = ServerCfg {server, preset = False, tested = Nothing, enabled = SEEnabled} rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (jsonP <|> text1P)) text1P = safeDecodeUtf8 <$> A.takeTill (== ' ') char_ = optional . A.char diff --git a/src/Simplex/Chat/Call.hs b/src/Simplex/Chat/Call.hs index 115cd839e4..9968d170aa 100644 --- a/src/Simplex/Chat/Call.hs +++ b/src/Simplex/Chat/Call.hs @@ -21,10 +21,10 @@ import Data.Time.Clock (UTCTime) import Database.SQLite.Simple.FromField (FromField (..)) import Database.SQLite.Simple.ToField (ToField (..)) import Simplex.Chat.Types (Contact, ContactId, User) -import Simplex.Chat.Types.Util (decodeJSON, encodeJSON) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON) +import Simplex.Messaging.Util (decodeJSON, encodeJSON) data Call = Call { contactId :: ContactId, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 04f360cc3e..4404360c2a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -60,6 +60,7 @@ import Simplex.Chat.Messages.CIContent import Simplex.Chat.Protocol import Simplex.Chat.Remote.AppVersion import Simplex.Chat.Remote.Types +import Simplex.Chat.Stats (PresentedServersSummary) import Simplex.Chat.Store (AutoAccept, ChatLockEntity, StoreError (..), UserContactLink, UserMsgReceiptSettings) import Simplex.Chat.Types import Simplex.Chat.Types.Preferences @@ -75,7 +76,7 @@ import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction) import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB -import Simplex.Messaging.Client (SMPProxyMode (..), SMPProxyFallback (..)) +import Simplex.Messaging.Client (SMPProxyFallback (..), SMPProxyMode (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) import qualified Simplex.Messaging.Crypto.File as CF @@ -505,6 +506,7 @@ data ChatCommand | ShowVersion | DebugLocks | DebugEvent ChatResponse + | GetAgentServersSummary UserId | GetAgentStats | ResetAgentStats | GetAgentSubs @@ -756,6 +758,7 @@ data ChatResponse | CRSQLResult {rows :: [Text]} | CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]} | CRDebugLocks {chatLockName :: Maybe String, chatEntityLocks :: Map String String, agentLocks :: AgentLocks} + | CRAgentServersSummary {user :: User, serversSummary :: PresentedServersSummary} | CRAgentStats {agentStats :: [[String]]} | CRAgentWorkersDetails {agentWorkersDetails :: AgentWorkersDetails} | CRAgentWorkersSummary {agentWorkersSummary :: AgentWorkersSummary} diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index d7c6d31fc8..688ffa6b1d 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -29,13 +29,12 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Simplex.Chat.Types -import Simplex.Chat.Types.Util import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, sumTypeJSON) import Simplex.Messaging.Protocol (ProtocolServer (..)) import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) -import Simplex.Messaging.Util (safeDecodeUtf8) +import Simplex.Messaging.Util (decodeJSON, safeDecodeUtf8) import System.Console.ANSI.Types import qualified Text.Email.Validate as Email @@ -146,7 +145,7 @@ parseMarkdown s = fromRight (unmarked s) $ A.parseOnly (markdownP <* A.endOfInpu isSimplexLink :: Format -> Bool isSimplexLink = \case - SimplexLink {} -> True; + SimplexLink {} -> True _ -> False markdownP :: Parser Markdown diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index 3d71047d57..e198183b06 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -29,12 +29,11 @@ import Simplex.Chat.Protocol import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared -import Simplex.Chat.Types.Util import Simplex.Messaging.Agent.Protocol (MsgErrorType (..), RatchetSyncState (..), SwitchPhase (..)) -import Simplex.Messaging.Crypto.Ratchet (PQEncryption, pattern PQEncOn, pattern PQEncOff) +import Simplex.Messaging.Crypto.Ratchet (PQEncryption, pattern PQEncOff, pattern PQEncOn) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON) -import Simplex.Messaging.Util (safeDecodeUtf8, tshow, (<$?>)) +import Simplex.Messaging.Util (encodeJSON, safeDecodeUtf8, tshow, (<$?>)) data MsgDirection = MDRcv | MDSnd deriving (Eq, Show) diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 8c5a9e1905..d611760fbf 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -46,14 +46,13 @@ import Database.SQLite.Simple.ToField (ToField (..)) import Simplex.Chat.Call import Simplex.Chat.Types import Simplex.Chat.Types.Shared -import Simplex.Chat.Types.Util import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion) import Simplex.Messaging.Compression (Compressed, compress1, decompress1) import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON) import Simplex.Messaging.Protocol (MsgBody) -import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>)) +import Simplex.Messaging.Util (decodeJSON, eitherToMaybe, encodeJSON, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Version hiding (version) -- Chat version history: diff --git a/src/Simplex/Chat/Stats.hs b/src/Simplex/Chat/Stats.hs new file mode 100644 index 0000000000..f6ea195e1c --- /dev/null +++ b/src/Simplex/Chat/Stats.hs @@ -0,0 +1,268 @@ +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE TemplateHaskell #-} +{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} + +module Simplex.Chat.Stats where + +import Control.Applicative ((<|>)) +import qualified Data.Aeson.TH as J +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Maybe (fromMaybe, isJust) +import Data.Time.Clock (UTCTime) +import Simplex.Chat.Types +import Simplex.Messaging.Agent.Client +import Simplex.Messaging.Agent.Protocol (UserId) +import Simplex.Messaging.Agent.Stats +import Simplex.Messaging.Parsers (defaultJSON) +import Simplex.Messaging.Protocol + +data PresentedServersSummary = PresentedServersSummary + { statsStartedAt :: UTCTime, + currentUserServers :: ServersSummary, + allUsersServers :: ServersSummary + } + deriving (Show) + +-- Presentation of servers will be split into separate categories, +-- so users can differentiate currently used (connected) servers, +-- previously connected servers that were in use in previous sessions, +-- and servers that are only proxied (not connected directly). +data ServersSummary = ServersSummary + { -- currently used SMP servers are those with Just in sessions and/or subs in SMPServerSummary; + -- all other servers would fall either into previously used or only proxied servers category + currentlyUsedSMPServers :: [SMPServerSummary], + -- previously used SMP servers are those with Nothing in sessions and subs, + -- and have any of sentDirect, sentProxied, recvMsgs, etc. > 0 in server stats (see toPresentedServersSummary); + -- remaining servers would fall into only proxied servers category + previouslyUsedSMPServers :: [SMPServerSummary], + -- only proxied SMP servers are those that aren't (according to current state - sessions and subs) + -- and weren't (according to stats) connected directly; they would have Nothing in sessions and subs, + -- and have all of sentDirect, sentProxied, recvMsgs, etc. = 0 in server stats + onlyProxiedSMPServers :: [SMPServerSummary], + -- currently used XFTP servers are those with Just in sessions in XFTPServerSummary, + -- and/or have upload/download/deletion in progress; + -- all other servers would fall into previously used servers category + currentlyUsedXFTPServers :: [XFTPServerSummary], + -- previously used XFTP servers are those with Nothing in sessions and don't have any process in progress + previouslyUsedXFTPServers :: [XFTPServerSummary] + } + deriving (Show) + +data SMPServerSummary = SMPServerSummary + { smpServer :: SMPServer, + -- known: + -- for simplicity always Nothing in totalServersSummary - allows us to load configured servers only for current user, + -- and also unnecessary unless we want to add navigation to other users servers settings; + -- always Just in currentUserServers - True if server is in list of user servers, otherwise False; + -- True - allows to navigate to server settings, False - allows to add server to configured as known (SEKnown) + known :: Maybe Bool, + sessions :: Maybe ServerSessions, + subs :: Maybe SMPServerSubs, + -- stats: + -- even if sessions and subs are Nothing, stats can be Just - server could be used earlier in session, + -- or in previous sessions and stats for it were restored; server would fall into a category of + -- previously used or only proxied servers - see ServersSummary above + stats :: Maybe AgentSMPServerStatsData + } + deriving (Show) + +data XFTPServerSummary = XFTPServerSummary + { xftpServer :: XFTPServer, + known :: Maybe Bool, -- same as for SMPServerSummary + sessions :: Maybe ServerSessions, + stats :: Maybe AgentXFTPServerStatsData, + rcvInProgress :: Bool, + sndInProgress :: Bool, + delInProgress :: Bool + } + deriving (Show) + +-- Maps AgentServersSummary to PresentedServersSummary: +-- - currentUserServers is for currentUser; +-- - users are passed to exclude hidden users from totalServersSummary; +-- - if currentUser is hidden, it should be accounted in totalServersSummary; +-- - known is set only in user level summaries based on passed userSMPSrvs and userXFTPSrvs +toPresentedServersSummary :: AgentServersSummary -> [User] -> User -> [SMPServer] -> [XFTPServer] -> PresentedServersSummary +toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrvs = do + let (userSMPSrvsSumms, allSMPSrvsSumms) = accSMPSrvsSummaries + (userSMPCurr, userSMPPrev, userSMPProx) = smpSummsIntoCategories userSMPSrvsSumms + (allSMPCurr, allSMPPrev, allSMPProx) = smpSummsIntoCategories allSMPSrvsSumms + (userXFTPSrvsSumms, allXFTPSrvsSumms) = accXFTPSrvsSummaries + (userXFTPCurr, userXFTPPrev) = xftpSummsIntoCategories userXFTPSrvsSumms + (allXFTPCurr, allXFTPPrev) = xftpSummsIntoCategories allXFTPSrvsSumms + PresentedServersSummary + { statsStartedAt, + currentUserServers = + ServersSummary + { currentlyUsedSMPServers = userSMPCurr, + previouslyUsedSMPServers = userSMPPrev, + onlyProxiedSMPServers = userSMPProx, + currentlyUsedXFTPServers = userXFTPCurr, + previouslyUsedXFTPServers = userXFTPPrev + }, + allUsersServers = + ServersSummary + { currentlyUsedSMPServers = allSMPCurr, + previouslyUsedSMPServers = allSMPPrev, + onlyProxiedSMPServers = allSMPProx, + currentlyUsedXFTPServers = allXFTPCurr, + previouslyUsedXFTPServers = allXFTPPrev + } + } + where + AgentServersSummary {statsStartedAt, smpServersSessions, smpServersSubs, smpServersStats, xftpServersSessions, xftpServersStats, xftpRcvInProgress, xftpSndInProgress, xftpDelInProgress} = agentSummary + countUserInAll auId = auId == aUserId currentUser || auId `notElem` hiddenUserIds + hiddenUserIds = map aUserId $ filter (isJust . viewPwdHash) users + smpSummsIntoCategories :: Map SMPServer SMPServerSummary -> ([SMPServerSummary], [SMPServerSummary], [SMPServerSummary]) + smpSummsIntoCategories = foldr partitionSummary ([], [], []) + where + partitionSummary srvSumm (curr, prev, prox) + | isCurrentlyUsed srvSumm = (srvSumm : curr, prev, prox) + | isPreviouslyUsed srvSumm = (curr, srvSumm : prev, prox) + | otherwise = (curr, prev, srvSumm : prox) + isCurrentlyUsed SMPServerSummary {sessions, subs} = isJust sessions || isJust subs + isPreviouslyUsed SMPServerSummary {stats} = case stats of + Nothing -> False + -- add connCompleted, connDeleted? + -- check: should connCompleted be counted for proxy? is it? + Just AgentSMPServerStatsData {_sentDirect, _sentProxied, _sentDirectAttempts, _sentProxiedAttempts, _recvMsgs, _connCreated, _connSecured, _connSubscribed, _connSubAttempts} -> + _sentDirect > 0 || _sentProxied > 0 || _sentDirectAttempts > 0 || _sentProxiedAttempts > 0 || _recvMsgs > 0 || _connCreated > 0 || _connSecured > 0 || _connSubscribed > 0 || _connSubAttempts > 0 + xftpSummsIntoCategories :: Map XFTPServer XFTPServerSummary -> ([XFTPServerSummary], [XFTPServerSummary]) + xftpSummsIntoCategories = foldr partitionSummary ([], []) + where + partitionSummary srvSumm (curr, prev) + | isCurrentlyUsed srvSumm = (srvSumm : curr, prev) + | otherwise = (curr, srvSumm : prev) + isCurrentlyUsed XFTPServerSummary {sessions, rcvInProgress, sndInProgress, delInProgress} = + isJust sessions || rcvInProgress || sndInProgress || delInProgress + accSMPSrvsSummaries :: (Map SMPServer SMPServerSummary, Map SMPServer SMPServerSummary) + accSMPSrvsSummaries = M.foldrWithKey' (addServerData addStats) summs2 smpServersStats + where + summs1 = M.foldrWithKey' (addServerData addSessions) (M.empty, M.empty) smpServersSessions + summs2 = M.foldrWithKey' (addServerData addSubs) summs1 smpServersSubs + addServerData :: + (a -> SMPServerSummary -> SMPServerSummary) -> + (UserId, SMPServer) -> + a -> + (Map SMPServer SMPServerSummary, Map SMPServer SMPServerSummary) -> + (Map SMPServer SMPServerSummary, Map SMPServer SMPServerSummary) + addServerData addData (userId, srv) d (userSumms, allUsersSumms) = (userSumms', allUsersSumms') + where + userSumms' + | userId == aUserId currentUser = alterSumms newUserSummary userSumms + | otherwise = userSumms + allUsersSumms' + | countUserInAll userId = alterSumms newSummary allUsersSumms + | otherwise = allUsersSumms + alterSumms n = M.alter (Just . addData d . fromMaybe n) srv + newUserSummary = (newSummary :: SMPServerSummary) {known = Just $ srv `elem` userSMPSrvs} + newSummary = + SMPServerSummary + { smpServer = srv, + known = Nothing, + sessions = Nothing, + subs = Nothing, + stats = Nothing + } + addSessions :: ServerSessions -> SMPServerSummary -> SMPServerSummary + addSessions s summ@SMPServerSummary {sessions} = summ {sessions = Just $ maybe s (s `addServerSessions`) sessions} + addSubs :: SMPServerSubs -> SMPServerSummary -> SMPServerSummary + addSubs s summ@SMPServerSummary {subs} = summ {subs = Just $ maybe s (s `addSMPSubs`) subs} + addStats :: AgentSMPServerStatsData -> SMPServerSummary -> SMPServerSummary + addStats s summ@SMPServerSummary {stats} = summ {stats = Just $ maybe s (s `addSMPStats`) stats} + accXFTPSrvsSummaries :: (Map XFTPServer XFTPServerSummary, Map XFTPServer XFTPServerSummary) + accXFTPSrvsSummaries = M.foldrWithKey' (addServerData addStats) summs1 xftpServersStats + where + summs1 = M.foldrWithKey' (addServerData addSessions) (M.empty, M.empty) xftpServersSessions + addServerData :: + (a -> XFTPServerSummary -> XFTPServerSummary) -> + (UserId, XFTPServer) -> + a -> + (Map XFTPServer XFTPServerSummary, Map XFTPServer XFTPServerSummary) -> + (Map XFTPServer XFTPServerSummary, Map XFTPServer XFTPServerSummary) + addServerData addData (userId, srv) d (userSumms, allUsersSumms) = (userSumms', allUsersSumms') + where + userSumms' + | userId == aUserId currentUser = alterSumms newUserSummary userSumms + | otherwise = userSumms + allUsersSumms' + | countUserInAll userId = alterSumms newSummary allUsersSumms + | otherwise = allUsersSumms + alterSumms n = M.alter (Just . addData d . fromMaybe n) srv + newUserSummary = (newSummary :: XFTPServerSummary) {known = Just $ srv `elem` userXFTPSrvs} + newSummary = + XFTPServerSummary + { xftpServer = srv, + known = Nothing, + sessions = Nothing, + stats = Nothing, + rcvInProgress = srv `elem` xftpRcvInProgress, + sndInProgress = srv `elem` xftpSndInProgress, + delInProgress = srv `elem` xftpDelInProgress + } + addSessions :: ServerSessions -> XFTPServerSummary -> XFTPServerSummary + addSessions s summ@XFTPServerSummary {sessions} = summ {sessions = Just $ maybe s (s `addServerSessions`) sessions} + addStats :: AgentXFTPServerStatsData -> XFTPServerSummary -> XFTPServerSummary + addStats s summ@XFTPServerSummary {stats} = summ {stats = Just $ maybe s (s `addXFTPStats`) stats} + addServerSessions :: ServerSessions -> ServerSessions -> ServerSessions + addServerSessions ss1 ss2 = + ServerSessions + { ssConnected = ssConnected ss1 + ssConnected ss2, + ssErrors = ssErrors ss1 + ssErrors ss2, + ssConnecting = ssConnecting ss1 + ssConnecting ss2 + } + addSMPSubs :: SMPServerSubs -> SMPServerSubs -> SMPServerSubs + addSMPSubs ss1 ss2 = + SMPServerSubs + { ssActive = ssActive ss1 + ssActive ss2, + ssPending = ssPending ss1 + ssPending ss2 + } + addSMPStats :: AgentSMPServerStatsData -> AgentSMPServerStatsData -> AgentSMPServerStatsData + addSMPStats sd1 sd2 = + AgentSMPServerStatsData + { _sentDirect = _sentDirect sd1 + _sentDirect sd2, + _sentViaProxy = _sentViaProxy sd1 + _sentViaProxy sd2, + _sentProxied = _sentProxied sd1 + _sentProxied sd2, + _sentDirectAttempts = _sentDirectAttempts sd1 + _sentDirectAttempts sd2, + _sentViaProxyAttempts = _sentViaProxyAttempts sd1 + _sentViaProxyAttempts sd2, + _sentProxiedAttempts = _sentProxiedAttempts sd1 + _sentProxiedAttempts sd2, + _sentAuthErrs = _sentAuthErrs sd1 + _sentAuthErrs sd2, + _sentQuotaErrs = _sentQuotaErrs sd1 + _sentQuotaErrs sd2, + _sentExpiredErrs = _sentExpiredErrs sd1 + _sentExpiredErrs sd2, + _sentOtherErrs = _sentOtherErrs sd1 + _sentOtherErrs sd2, + _recvMsgs = _recvMsgs sd1 + _recvMsgs sd2, + _recvDuplicates = _recvDuplicates sd1 + _recvDuplicates sd2, + _recvCryptoErrs = _recvCryptoErrs sd1 + _recvCryptoErrs sd2, + _recvErrs = _recvErrs sd1 + _recvErrs sd2, + _connCreated = _connCreated sd1 + _connCreated sd2, + _connSecured = _connSecured sd1 + _connSecured sd2, + _connCompleted = _connCompleted sd1 + _connCompleted sd2, + _connDeleted = _connDeleted sd1 + _connDeleted sd2, + _connSubscribed = _connSubscribed sd1 + _connSubscribed sd2, + _connSubAttempts = _connSubAttempts sd1 + _connSubAttempts sd2, + _connSubErrs = _connSubErrs sd1 + _connSubErrs sd2 + } + addXFTPStats :: AgentXFTPServerStatsData -> AgentXFTPServerStatsData -> AgentXFTPServerStatsData + addXFTPStats sd1 sd2 = + AgentXFTPServerStatsData + { _uploads = _uploads sd1 + _uploads sd2, + _uploadAttempts = _uploadAttempts sd1 + _uploadAttempts sd2, + _uploadErrs = _uploadErrs sd1 + _uploadErrs sd2, + _downloads = _downloads sd1 + _downloads sd2, + _downloadAttempts = _downloadAttempts sd1 + _downloadAttempts sd2, + _downloadAuthErrs = _downloadAuthErrs sd1 + _downloadAuthErrs sd2, + _downloadErrs = _downloadErrs sd1 + _downloadErrs sd2, + _deletions = _deletions sd1 + _deletions sd2, + _deleteAttempts = _deleteAttempts sd1 + _deleteAttempts sd2, + _deleteErrs = _deleteErrs sd1 + _deleteErrs sd2 + } + +$(J.deriveJSON defaultJSON ''SMPServerSummary) + +$(J.deriveJSON defaultJSON ''XFTPServerSummary) + +$(J.deriveJSON defaultJSON ''ServersSummary) + +$(J.deriveJSON defaultJSON ''PresentedServersSummary) diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index be06ea8878..a740389c6a 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -523,9 +523,10 @@ getProtocolServers db User {userId} = (userId, decodeLatin1 $ strEncode protocol) where protocol = protocolTypeI @p - toServerCfg :: (NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Bool) -> ServerCfg p - toServerCfg (host, port, keyHash, auth_, preset, tested, enabled) = + toServerCfg :: (NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Int) -> ServerCfg p + toServerCfg (host, port, keyHash, auth_, preset, tested, enabledInt) = let server = ProtoServerWithAuth (ProtocolServer protocol host port keyHash) (BasicAuth . encodeUtf8 <$> auth_) + enabled = toServerEnabled enabledInt in ServerCfg {server, preset, tested, enabled} overwriteProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> [ServerCfg p] -> ExceptT StoreError IO () @@ -542,7 +543,7 @@ overwriteProtocolServers db User {userId} servers = (protocol, host, port, key_hash, basic_auth, preset, tested, enabled, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?) |] - ((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_) :. (preset, tested, enabled, userId, currentTs, currentTs)) + ((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_) :. (preset, tested, fromServerEnabled enabled, userId, currentTs, currentTs)) pure $ Right () where protocol = decodeLatin1 $ strEncode $ protocolTypeI @p diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 106c4b3373..07c9afa2e2 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -1632,10 +1632,42 @@ data ServerCfg p = ServerCfg { server :: ProtoServerWithAuth p, preset :: Bool, tested :: Maybe Bool, - enabled :: Bool + enabled :: ServerEnabled } deriving (Show) +data ServerEnabled + = SEDisabled + | SEEnabled + | -- server is marked as known, but it's not in the list of configured servers; + -- e.g., it may be added via an unknown server dialogue and user didn't manually configure it, + -- meaning server wasn't tested (or at least such option wasn't presented in UI) + -- and it may be inoperable for user due to server password + SEKnown + deriving (Eq, Show) + +pattern DBSEDisabled :: Int +pattern DBSEDisabled = 0 + +pattern DBSEEnabled :: Int +pattern DBSEEnabled = 1 + +pattern DBSEKnown :: Int +pattern DBSEKnown = 2 + +toServerEnabled :: Int -> ServerEnabled +toServerEnabled = \case + DBSEDisabled -> SEDisabled + DBSEEnabled -> SEEnabled + DBSEKnown -> SEKnown + _ -> SEDisabled + +fromServerEnabled :: ServerEnabled -> Int +fromServerEnabled = \case + SEDisabled -> DBSEDisabled + SEEnabled -> DBSEEnabled + SEKnown -> DBSEKnown + data ChatVersion instance VersionScope ChatVersion @@ -1764,6 +1796,8 @@ $(JQ.deriveJSON defaultJSON ''ContactRef) $(JQ.deriveJSON defaultJSON ''NoteFolder) +$(JQ.deriveJSON (enumJSON $ dropPrefix "SE") ''ServerEnabled) + instance ProtocolTypeI p => ToJSON (ServerCfg p) where toEncoding = $(JQ.mkToEncoding defaultJSON ''ServerCfg) toJSON = $(JQ.mkToJSON defaultJSON ''ServerCfg) diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index 4cf9f862d2..bccfd4bdce 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -36,7 +36,7 @@ import Simplex.Chat.Types.Shared import Simplex.Chat.Types.Util import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON) -import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) +import Simplex.Messaging.Util (decodeJSON, encodeJSON, safeDecodeUtf8, (<$?>)) data ChatFeature = CFTimedMessages diff --git a/src/Simplex/Chat/Types/UITheme.hs b/src/Simplex/Chat/Types/UITheme.hs index ef445c5a7c..cc5290aa69 100644 --- a/src/Simplex/Chat/Types/UITheme.hs +++ b/src/Simplex/Chat/Types/UITheme.hs @@ -18,6 +18,7 @@ import Database.SQLite.Simple.ToField (ToField (..)) import Simplex.Chat.Types.Util import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_) +import Simplex.Messaging.Util (decodeJSON, encodeJSON) data UITheme = UITheme { themeId :: Text, diff --git a/src/Simplex/Chat/Types/Util.hs b/src/Simplex/Chat/Types/Util.hs index e19d48caba..47edf8eaf8 100644 --- a/src/Simplex/Chat/Types/Util.hs +++ b/src/Simplex/Chat/Types/Util.hs @@ -2,26 +2,15 @@ module Simplex.Chat.Types.Util where -import Data.Aeson (FromJSON, ToJSON) import qualified Data.Aeson as J import qualified Data.Aeson.Types as JT import Data.ByteString (ByteString) -import qualified Data.ByteString.Lazy.Char8 as LB -import Data.Text (Text) -import Data.Text.Encoding (encodeUtf8) import Data.Typeable import Database.SQLite.Simple (ResultError (..), SQLData (..)) import Database.SQLite.Simple.FromField (FieldParser, returnError) import Database.SQLite.Simple.Internal (Field (..)) import Database.SQLite.Simple.Ok (Ok (Ok)) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Util (safeDecodeUtf8) - -encodeJSON :: ToJSON a => a -> Text -encodeJSON = safeDecodeUtf8 . LB.toStrict . J.encode - -decodeJSON :: FromJSON a => Text -> Maybe a -decodeJSON = J.decode . LB.fromStrict . encodeUtf8 textParseJSON :: TextEncoding a => String -> J.Value -> JT.Parser a textParseJSON name = J.withText name $ maybe (fail $ "bad " <> name) pure . textDecode diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index d1f3625e18..5b14084552 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -54,7 +54,6 @@ import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..)) import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..)) import Simplex.Messaging.Agent.Protocol -import Simplex.Messaging.Agent.Protocol (AgentErrorType (RCP)) import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import Simplex.Messaging.Client (SMPProxyFallback, SMPProxyMode (..)) import qualified Simplex.Messaging.Crypto as C @@ -366,6 +365,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe "chat entity locks: " <> viewJSON chatEntityLocks, "agent locks: " <> viewJSON agentLocks ] + CRAgentServersSummary u serversSummary -> ttyUser u ["agent servers summary: " <> viewJSON serversSummary] CRAgentStats stats -> map (plain . intercalate ",") stats CRAgentSubs {activeSubs, pendingSubs, removedSubs} -> [plain $ "Subscriptions: active = " <> show (sum activeSubs) <> ", pending = " <> show (sum pendingSubs) <> ", removed = " <> show (sum $ M.map length removedSubs)] diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index c1388072ab..f29d6d848a 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -17,7 +17,7 @@ import Simplex.Chat.Store.Shared (createContact) import Simplex.Chat.Types (ConnStatus (..), Profile (..)) import Simplex.Chat.Types.Shared (GroupMemberRole (..)) import Simplex.Chat.Types.UITheme -import Simplex.Chat.Types.Util (encodeJSON) +import Simplex.Messaging.Util (encodeJSON) import Simplex.Messaging.Encoding.String (StrEncoding (..)) import System.Directory (copyFile, createDirectoryIfMissing) import Test.Hspec hiding (it) From fba0478e509cf56851cef44dc0543a0531bec32b Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:37:54 +0400 Subject: [PATCH 03/15] docs: more additions to chat protocol (#4355) * docs: more additions to chat protocol * schema wip * schema * update diagram * remove comments * punctuation * diagram text * update threat model --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- docs/protocol/diagrams/group.mmd | 26 ++- docs/protocol/diagrams/group.svg | 4 +- docs/protocol/simplex-chat.md | 77 ++++++- docs/protocol/simplex-chat.schema.json | 281 +++++++++++++++++++++++-- 4 files changed, 364 insertions(+), 24 deletions(-) diff --git a/docs/protocol/diagrams/group.mmd b/docs/protocol/diagrams/group.mmd index 18d392caa5..6a9bd0c786 100644 --- a/docs/protocol/diagrams/group.mmd +++ b/docs/protocol/diagrams/group.mmd @@ -4,9 +4,16 @@ sequenceDiagram participant B as Bob participant C as Existing
contact - note over A, B: 1. send and accept group invitation - A ->> B: x.grp.inv
invite Bob to group
(via contact connection) - B ->> A: x.grp.acpt
accept invitation
(via member connection)
establish group member connection + alt invite contact + note over A, B: 1a. send and accept group invitation + A ->> B: x.grp.inv
invite Bob to group
(via contact connection) + B ->> A: x.grp.acpt
accept invitation
(via member connection)
establish group member connection + else join via group link + note over A, B: 1b. join via group link and accept request + B ->> A: join via group link
SimpleX contact address + A ->> B: x.grp.link.inv in SMP confirmation
accept joining member request,
sending group profile, etc.
establish group member connection + A ->> B: x.grp.link.mem
send inviting member profile + end note over M, B: 2. introduce new member Bob to all existing members A ->> M: x.grp.mem.new
"announce" Bob
to existing members
(via member connections) @@ -20,14 +27,25 @@ sequenceDiagram end A ->> M: x.grp.mem.fwd
forward "invitations" and
Bob's chat protocol version
to all members
(via member connections) + note over M, B: group message forwarding
(while connections between members are being established) + M -->> B: messages between members and Bob are forwarded by Alice + B -->> M: + note over M, B: 3. establish direct and group member connections M ->> B: establish group member connection opt chat protocol compatible version < 2 M ->> B: establish direct connection - note over M, C: 4. deduplicate new contact + note over M, C: 3*. deduplicate new contact B ->> M: x.info.probe
"probe" is sent to all new members B ->> C: x.info.probe.check
"probe" hash,
in case contact and
member profiles match C ->> B: x.info.probe.ok
original "probe",
in case contact and member
are the same user note over B: merge existing and new contacts if received and sent probe hashes match end + + note over M, B: 4. notify inviting member that connection is established + M ->> A: x.grp.mem.con + B ->> A: x.grp.mem.con + note over A: stops forwarding messages + M -->> B: messages are sent via group connection without forwarding + B -->> M: diff --git a/docs/protocol/diagrams/group.svg b/docs/protocol/diagrams/group.svg index 8c1b65dee2..f3c9aa8a26 100644 --- a/docs/protocol/diagrams/group.svg +++ b/docs/protocol/diagrams/group.svg @@ -1 +1,3 @@ -ExistingcontactBobAliceN existingmembersExistingcontactBobAliceN existingmembers1. send and accept group invitation2. introduce new member Bob to all existing membersprepare group member connectionsprepare direct connectionsopt[chat protocolcompatible version< 2]loop[batched]3. establish direct and group member connections4. deduplicate new contactmerge existing and new contacts if received and sent probe hashes matchopt[chat protocol compatible version < 2]x.grp.invinvite Bob to group(via contact connection)x.grp.acptaccept invitation(via member connection)establish group member connectionx.grp.mem.new"announce" Bobto existing members(via member connections)x.grp.mem.intro * N"introduce" members andtheir chat protocol versions(via member connection)x.grp.mem.inv * N"invitations" to connectfor all members(via member connection)x.grp.mem.fwdforward "invitations" andBob's chat protocol versionto all members(via member connections)establish group member connectionestablish direct connectionx.info.probe"probe" is sent to all new membersx.info.probe.check"probe" hash,in case contact andmember profiles matchx.info.probe.ok original "probe", in case contact and memberare the same user \ No newline at end of file + + +ExistingcontactBobAliceN existingmembersExistingcontactBobAliceN existingmembers1a. send and accept group invitation1b. join via group link and accept requestalt[invite contact][join via group link]2. introduce new member Bob to all existing membersprepare group member connectionsprepare direct connectionsopt[chat protocolcompatible version< 2]loop[batched]group message forwarding(while connections between members are being established)3. establish direct and group member connections3*. deduplicate new contactmerge existing and new contacts if received and sent probe hashes matchopt[chat protocol compatible version < 2]4. notify inviting member that connection is establishedstops forwarding messagesx.grp.invinvite Bob to group(via contact connection)x.grp.acptaccept invitation(via member connection)establish group member connectionjoin via group linkSimpleX contact addressx.grp.link.inv in SMP confirmationaccept joining member request,sending group profile, etc.establish group member connectionx.grp.link.memsend inviting member profilex.grp.mem.new"announce" Bobto existing members(via member connections)x.grp.mem.intro * N"introduce" members andtheir chat protocol versions(via member connection)x.grp.mem.inv * N"invitations" to connectfor all members(via member connection)x.grp.mem.fwdforward "invitations" andBob's chat protocol versionto all members(via member connections)messages between members and Bob are forwarded by Alice​establish group member connectionestablish direct connectionx.info.probe"probe" is sent to all new membersx.info.probe.check"probe" hash,in case contact andmember profiles matchx.info.probe.ok original "probe", in case contact and memberare the same userx.grp.mem.conx.grp.mem.conmessages are sent via group connection without forwarding​ \ No newline at end of file diff --git a/docs/protocol/simplex-chat.md b/docs/protocol/simplex-chat.md index efa4edb959..4b5f87821b 100644 --- a/docs/protocol/simplex-chat.md +++ b/docs/protocol/simplex-chat.md @@ -2,7 +2,7 @@ title: SimpleX Chat Protocol revision: 08.08.2022 --- -DRAFT Revision 0.1, 2022-08-08 +Revision 2, 2024-06-24 Evgeny Poberezkin @@ -157,7 +157,7 @@ This message is sent by both sides of the connection during the connection hands ### Probing for duplicate contacts -As there are no globally unique user identitifiers, when the contact a user is already connected to is added to the group by some other group member, this contact will be added to user's list of contacts as a new contact. To allow merging such contacts, "a probe" (random base64url-encoded 32 bytes) SHOULD be sent to all new members as part of `x.info.probe` message and, in case there is a contact with the same profile, the hash of the probe MAY be sent to it as part of `x.info.probe.check` message. In case both the new member and the existing contact are the same user (they would receive both the probe and its hash), the contact would send back the original probe as part of `x.info.probe.ok` message via the previously existing contact connection – proving to the sender that this new member and the existing contact are the same user, in which case the sender SHOULD merge these two contacts. +As there are no globally unique user identifiers, when the contact a user is already connected to is added to the group by some other group member, this contact will be added to user's list of contacts as a new contact. To allow merging such contacts, "a probe" (random base64url-encoded 32 bytes) SHOULD be sent to all new members as part of `x.info.probe` message and, in case there is a contact with the same profile, the hash of the probe MAY be sent to it as part of `x.info.probe.check` message. In case both the new member and the existing contact are the same user (they would receive both the probe and its hash), the contact would send back the original probe as part of `x.info.probe.ok` message via the previously existing contact connection – proving to the sender that this new member and the existing contact are the same user, in which case the sender SHOULD merge these two contacts. Sending clients MAY disable this functionality, and receiving clients MAY ignore probe messages. @@ -210,23 +210,23 @@ File attachment can optionally include connection address to receive the file - ### Decentralized design for chat groups -SimpleX Chat groups are fully decentralized and do not have any globally unique group identifiers - they are only defined on client devices as a group profile and a set of bi-directional SimpleX connections with other group members. When a new member accepts group invitation, the inviting member introduces a new member to all existing members and forwards the connection addresses so that they can establish direct and group member connections. +SimpleX Chat groups are fully decentralized and do not have any globally unique group identifiers - they are only defined on client devices as a group profile and a set of bi-directional SimpleX connections with other group members. When a new member accepts group invitation or joins via group link, the inviting member introduces a new member to all existing members and forwards the connection addresses so that they can establish direct and group member connections. There is a possibility of the attack here: as the introducing member forwards the addresses, they can substitute them with other addresses, performing MITM attack on the communication between existing and introduced members - this is similar to the communication operator being able to perform MITM on any connection between the users. To mitigate this attack this group sub-protocol will be extended to allow validating security of the connection by sending connection verification out-of-band. -Clients are RECOMMENDED to indicate in the UI whether the connection to a group member or contact was made directly or via annother user. +Clients are RECOMMENDED to indicate in the UI whether the connection to a group member or contact was made directly or via another user. Each member in the group is identified by a group-wide unique identifier used by all members in the group. This is to allow referencing members in the messages and to allow group message integrity validation. The diagram below shows the sequence of messages sent between the users' clients to add the new member to the group. -![Adding member to the group](./diagrams/group.svg) - While introduced members establish connection inside group, inviting member forwards messages between them by sending `x.grp.msg.forward` messages. When introduced members finalize connection, they notify inviting member to stop forwarding via `x.grp.mem.con` message. +![Adding member to the group](./diagrams/group.svg) + ### Member roles -Currently members can have one of three roles - `owner`, `admin` and `member`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role. +Currently members can have one of three roles - `owner`, `admin`, `member` and `observer`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role. `Observer` members only receive messages and aren't allowed to send messages. ### Messages to manage groups and add members @@ -279,3 +279,66 @@ These message are used for WebRTC calls: 3. `x.call.answer`: to continue with call connection the initiating clients must reply with `x.call.answer` message. This message contains WebRTC answer and collected ICE candidates. Additional ICE candidates can be sent in `x.call.extra` message. 4. `x.call.end` message is sent to notify the other party that the call is terminated. + +## Threat model + +This threat model compliments SMP, XFTP, push notifications and XRCP protocols threat models: + +- [SimpleX Messaging Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md#threat-model); +- [SimpleX File Transfer Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/xftp.md#threat-model); +- [Push notifications threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/push-notifications.md#threat-model); +- [SimpleX Remote Control Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/xrcp.md#threat-model). + +#### A user's contact + +*can:* + +- send messages prohibited by user's preferences or otherwise act non-compliantly with user's preferences (for example, if message with updated preferences was lost or failed to be processed, or with modified client), in which case user client should treat such messages and actions as prohibited. + +- by exchanging special messages with user's client, match user's contact with existing group members and/or contacts that have identical user profile (see [Probing for duplicate contacts](#probing-for-duplicate-contacts)). + +- identify that and when a user is using SimpleX, in case user has delivery receipts enabled, or based on other automated client responses. + +*cannot:* + +- match user's contact with existing group members and/or contacts with different or with incognito profiles. + +- match user's contact without communicating with the user's client. + +#### A group member + +*can:* + +- send messages prohibited by group's preferences and member restrictions or otherwise act non-compliantly with preferences and restrictions (for example, if decentralized group state diverged, or with modified client), in which case user client should treat such messages and actions as prohibited. + +- create a direct contact with a user if group permissions allow it. + +- by exchanging special messages with user's client, match user's group member record with the existing group members and/or contacts that have identical user profile. + +- undetectably send different messages to different group members, or selectively send messages to some members and not send to others. + +- identify that and when a user is using SimpleX, in case user has delivery receipts enabled, or based on other automated client responses. + +- join the same group several times, from the same or from different user profile, and pretend to be different members. + +*cannot:* + +- match user's contact with existing group members and/or contacts with different or with incognito profiles. + +- match user's group member record with existing group members and/or contacts without communication of user's client. + +- determine whether two group members with different or with incognito profiles are the same user. + +#### A group admin + +*can:* + +- carry out MITM attack between user and other group member(s) when forwarding invitations for group connections (user can detect such attack by verifying connection security codes out-of-band). + +- undetectably forward different messages to different group members, selectively adding, modifying, and dropping forwarded messages. + +- disrupt decentralized group state by sending different messages that change group state (such as adding or removing members, member role changes, etc.) to different group members, or sending such messages selectively. + +*cannot:* + +- prove that two group members with incognito profiles is the same user. diff --git a/docs/protocol/simplex-chat.schema.json b/docs/protocol/simplex-chat.schema.json index a9738190bd..2e94a4f2c2 100644 --- a/docs/protocol/simplex-chat.schema.json +++ b/docs/protocol/simplex-chat.schema.json @@ -8,7 +8,7 @@ "displayName": { "type": "string", "metadata": { - "format": "non-empty string without spaces, the first character must not be # or @" + "format": "non-empty string, the first character must not be # or @" } }, "fullName": {"type": "string"} @@ -19,6 +19,39 @@ "metadata": { "format": "data URI format for base64 encoded image" } + }, + "contactLink": {"ref": "connReqUri"}, + "preferences": { + "type": "string", + "metadata": { + "format": "JSON encoded user preferences" + } + } + }, + "additionalProperties": true + }, + "groupProfile": { + "properties": { + "displayName": { + "type": "string", + "metadata": { + "format": "non-empty string, the first character must not be # or @" + } + }, + "fullName": {"type": "string"} + }, + "optionalProperties": { + "image": { + "type": "string", + "metadata": { + "format": "data URI format for base64 encoded image" + } + }, + "groupPreferences": { + "type": "string", + "metadata": { + "format": "JSON encoded user preferences" + } } }, "additionalProperties": true @@ -29,6 +62,8 @@ }, "optionalProperties": { "file": {"ref": "fileInvitation"}, + "ttl": {"type": "integer"}, + "live": {"type": "boolean"}, "quote": { "properties": { "msgRef": {"ref": "msgRef"}, @@ -56,17 +91,47 @@ } }, "image": { - "text": {"type": "string", "metadata": {"comment": "can be empty"}}, - "image": {"ref": "base64url"} + "properties": { + "text": {"type": "string", "metadata": {"comment": "can be empty"}}, + "image": {"ref": "base64url"} + } + }, + "video": { + "properties": { + "text": {"type": "string", "metadata": {"comment": "can be empty"}}, + "image": {"ref": "base64url"}, + "duration": {"type": "integer"} + } + }, + "voice": { + "properties": { + "text": {"type": "string", "metadata": {"comment": "can be empty"}}, + "duration": {"type": "integer"} + } }, "file": { - "text": {"type": "string", "metadata": {"comment": "can be empty"}} + "properties": { + "text": {"type": "string", "metadata": {"comment": "can be empty"}} + } } }, "metadata": { "comment": "it is RECOMMENDED that the clients support other values in `type` properties showing them as text messages in case `text` property is present" } }, + "msgReaction" : { + "discriminator": "type", + "mapping": { + "emoji": { + "properties": { + "emoji": { + "type": "string", + "metadata": {"comment": "emoji character"} + } + } + } + } + }, "msgRef": { "properties": { "msgId": {"ref": "base64url"}, @@ -91,7 +156,31 @@ "fileSize": {"type": "uint32"} }, "optionalProperties": { - "fileConnReq": {"ref": "connReqUri"} + "fileDigest": {"ref": "base64url"}, + "fileConnReq": {"ref": "connReqUri"}, + "fileDescr": {"ref": "fileDescription"} + } + }, + "fileDescription": { + "properties": { + "fileDescrText": { + "type": "string", + "metadata": { + "format": "XFTP file description part text" + } + }, + "fileDescrPartNo": { + "type": "integer", + "metadata": { + "format": "XFTP file description part number" + } + }, + "fileDescrComplete": { + "type": "boolean", + "metadata": { + "format": "XFTP file description completion marker" + } + } } }, "linkPreview": { @@ -100,6 +189,21 @@ "title": {"type": "string"}, "description": {"type": "string"}, "image": {"ref": "base64url"} + }, + "optionalProperties": { + "content": {"ref": "linkContent"} + } + }, + "linkContent": { + "discriminator": "type", + "mapping": { + "page": {}, + "image": {}, + "video": { + "optionalProperties": { + "duration": {"type": "integer"} + } + } } }, "groupInvitation": { @@ -107,15 +211,27 @@ "fromMember": {"ref": "memberIdRole"}, "invitedMember": {"ref": "memberIdRole"}, "connRequest": {"ref": "connReqUri"}, - "groupProfile": {"ref": "profile"} + "groupProfile": {"ref": "groupProfile"} }, "optionalProperties": { "groupLinkId": {"ref": "base64url"}, + "groupSize": {"type": "integer"}, "metadata": { - "comment": "used to identify invitation via group link" + "comment": "groupLinkId is used to identify invitation via group link" } } }, + "groupLinkInvitation": { + "properties": { + "fromMember": {"ref": "memberIdRole"}, + "fromMemberName": {"type": "string"}, + "invitedMember": {"ref": "memberIdRole"}, + "groupProfile": {"ref": "groupProfile"} + }, + "optionalProperties": { + "groupSize": {"type": "integer"} + } + }, "memberIdRole": { "properties": { "memberId": {"ref": "base64url"}, @@ -127,16 +243,35 @@ "memberId": {"ref": "base64url"}, "memberRole": {"ref": "groupMemberRole"}, "profile": {"ref": "profile"} + }, + "optionalProperties": { + "v": {"ref": "chatVersionRange"} + } + }, + "memberRestrictions": { + "properties": { + "restriction": {"ref": "memberRestrictionStatus"} + } + }, + "memberRestrictionStatus": { + "enum": ["blocked", "unrestricted"] + }, + "chatVersionRange": { + "type": "string", + "metadata": { + "format": "chat version range string encoded as `-`, or as `` if min = max" } }, "introInvitation": { "properties": { - "groupConnReq": {"ref": "connReqUri"}, + "groupConnReq": {"ref": "connReqUri"} + }, + "optionalProperties": { "directConnReq": {"ref": "connReqUri"} } }, "groupMemberRole": { - "enum": ["author", "member", "admin", "owner"] + "enum": ["observer", "author", "member", "admin", "owner"] }, "callInvitation": { "properties": { @@ -257,6 +392,17 @@ "params": {"ref": "msgContainer"} } }, + "x.msg.file.descr": { + "properties": { + "msgId": {"ref": "base64url"}, + "params": { + "properties": { + "msgId": {"ref": "base64url"}, + "fileDescr": {"ref": "fileDescription"} + } + } + } + }, "x.msg.update": { "properties": { "msgId": {"ref": "base64url"}, @@ -264,6 +410,10 @@ "properties": { "msgId": {"ref": "base64url"}, "content": {"ref": "msgContent"} + }, + "optionalProperties": { + "ttl": {"type": "integer"}, + "live": {"type": "boolean"} } } } @@ -274,6 +424,24 @@ "params": { "properties": { "msgId": {"ref": "base64url"} + }, + "optionalProperties": { + "memberId": {"ref": "base64url"} + } + } + } + }, + "x.msg.react": { + "properties": { + "msgId": {"ref": "base64url"}, + "params": { + "properties": { + "msgId": {"ref": "base64url"}, + "reaction": {"ref": "msgReaction"}, + "add": {"type": "boolean"} + }, + "optionalProperties": { + "memberId": {"ref": "base64url"} } } } @@ -294,8 +462,10 @@ "params": { "properties": { "msgId": {"ref": "base64url"}, - "fileConnReq": {"ref": "connReqUri"}, "fileName": {"type": "string"} + }, + "optionalProperties": { + "fileConnReq": {"ref": "connReqUri"} } } } @@ -310,6 +480,14 @@ } } }, + "x.direct.del": { + "properties": { + "msgId": {"ref": "base64url"}, + "params": { + "properties": {} + } + } + }, "x.grp.inv": { "properties": { "msgId": {"ref": "base64url"}, @@ -330,6 +508,26 @@ } } }, + "x.grp.link.inv": { + "properties": { + "msgId": {"ref": "base64url"}, + "params": { + "properties": { + "groupLinkInvitation": {"ref": "groupLinkInvitation"} + } + } + } + }, + "x.grp.link.mem": { + "properties": { + "msgId": {"ref": "base64url"}, + "params": { + "properties": { + "profile": {"ref": "profile"} + } + } + } + }, "x.grp.mem.new": { "properties": { "msgId": {"ref": "base64url"}, @@ -346,6 +544,9 @@ "params": { "properties": { "memberInfo": {"ref": "memberInfo"} + }, + "optionalProperties": { + "memberRestrictions": {"ref": "memberRestrictions"} } } } @@ -394,6 +595,27 @@ } } }, + "x.grp.mem.restrict": { + "properties": { + "msgId": {"ref": "base64url"}, + "params": { + "properties": { + "memberId": {"ref": "base64url"}, + "memberRestrictions": {"ref": "memberRestrictions"} + } + } + } + }, + "x.grp.mem.con": { + "properties": { + "msgId": {"ref": "base64url"}, + "params": { + "properties": { + "memberId": {"ref": "base64url"} + } + } + } + }, "x.grp.mem.del": { "properties": { "msgId": {"ref": "base64url"}, @@ -425,7 +647,42 @@ "msgId": {"ref": "base64url"}, "params": { "properties": { - "groupProfile": {"ref": "profile"} + "groupProfile": {"ref": "groupProfile"} + } + } + } + }, + "x.grp.direct.inv": { + "properties": { + "msgId": {"ref": "base64url"}, + "params": { + "properties": { + "connReq": {"ref": "connReqUri"} + }, + "optionalProperties": { + "content": {"ref": "msgContent"} + } + } + } + }, + "x.grp.msg.forward": { + "properties": { + "msgId": {"ref": "base64url"}, + "params": { + "properties": { + "memberId": {"ref": "base64url"}, + "msg": { + "type": "string", + "metadata": { + "format": "JSON encoded chat message" + } + }, + "msgTs": { + "type": "string", + "metadata": { + "format": "ISO8601 UTC time of the message" + } + } } } } @@ -436,7 +693,7 @@ "params": { "properties": { "callId": {"ref": "base64url"}, - "invitation": {} + "invitation": {"ref": "callInvitation"} } } } From fca098f53509950281ac52d51c7894a18d6025d8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 28 Jun 2024 00:10:51 +0100 Subject: [PATCH 04/15] docs: update donation addresses --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4e74bbc866..29ee7e189e 100644 --- a/README.md +++ b/README.md @@ -158,20 +158,19 @@ We are prioritizing users privacy and security - it would be impossible without Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, - so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure. -Your donations help us raise more funds – any amount, even the price of the cup of coffee, would make a big difference for us. +Your donations help us raise more funds - any amount, even the price of the cup of coffee, would make a big difference for us. It is possible to donate via: -- [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us. -- [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in crypto-currencies. +- [GitHub](https://github.com/sponsors/simplex-chat) (commission-free) or [OpenCollective](https://opencollective.com/simplex-chat) (~10% commission). +- Bitcoin: bc1qd74rc032ek2knhhr3yjq2ajzc5enz3h4qwnxad - Monero: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Bitcoin: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- BCH: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG +- BCH: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg +- Ethereum: 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692 - USDT: - - BNB Smart Chain: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 - - Tron: TNnTrKLBmdy2Wn3cAQR98dAVvWhLskQGfW -- Ethereum: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 -- Solana: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L + - Ethereum: 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692 +- Solana: 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu +- please ask if you want to donate any other coins. Thank you, From 5baf8789d675dc7ad45ed225141c33522e5e7cfe Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:09:34 +0000 Subject: [PATCH 05/15] flatpak: add desktop and metainfo (#4366) * flatpak: add desktop and metainfo * metainfo: update * corrections * ampersand * shorter --------- Co-authored-by: Evgeny Poberezkin --- scripts/flatpak/chat.simplex.simplex.desktop | 10 +++ .../flatpak/chat.simplex.simplex.metainfo.xml | 81 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 scripts/flatpak/chat.simplex.simplex.desktop create mode 100644 scripts/flatpak/chat.simplex.simplex.metainfo.xml diff --git a/scripts/flatpak/chat.simplex.simplex.desktop b/scripts/flatpak/chat.simplex.simplex.desktop new file mode 100644 index 0000000000..4f4cd4eece --- /dev/null +++ b/scripts/flatpak/chat.simplex.simplex.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Terminal=false +Name=SimpleX Chat +Comment=A private & encrypted open-source messenger without any user IDs (not even random ones)! +Keywords=chat;message;private;secure;simplex; +Categories=Utility;Chat;InstantMessaging; +Exec=simplex %U +Icon=chat.simplex.simplex +StartupWMClass=simplex-chat diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml new file mode 100644 index 0000000000..dd3f1143b5 --- /dev/null +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -0,0 +1,81 @@ + + + chat.simplex.simplex + + SimpleX Chat + A private & encrypted open-source messenger without any user IDs (not even random)! + + + SimpleX Chat + + + MIT + AGPL-3.0-or-later + + +

SimpleX - the first messaging platform that has no user identifiers, not even random numbers!

+

Security assessment was done by Trail of Bits in November 2022.

+

SimpleX Chat features:

+
    +
  1. end-to-end encrypted messages, with editing, replies and deletion of messages.
  2. +
  3. sending end-to-end encrypted images and files.
  4. +
  5. single-use and long-term user addresses.
  6. +
  7. secret chat groups - only group members know it exists and who is the member.
  8. +
  9. end-to-end encrypted audio and video calls.
  10. +
  11. private instant notifications.
  12. +
  13. portable chat profile - you can transfer your chat contacts and history to another device (terminal or mobile).
  14. +
+

SimpleX Chat advantages:

+
    +
  1. Full privacy of your identity, profile, contacts and metadata: unlike any other existing messaging platform, SimpleX uses no phone numbers or any other identifiers assigned to the users - not even random numbers. This protects the privacy of who you are communicating with, hiding it from SimpleX platform servers and from any observers.
  2. +
  3. Complete protection against spam and abuse: as you have no identifier on SimpleX platform, you cannot be contacted unless you share a one-time invitation link or an optional temporary user address.
  4. +
  5. Full ownership, control and security of your data: SimpleX stores all user data on client devices, the messages are only held temporarily on SimpleX relay servers until they are received.
  6. +
  7. Decentralized network: you can use SimpleX with your own servers and still communicate with people using the servers that are pre-configured in the apps or any other SimpleX servers.
  8. +
+

You can connect to anybody you know via link or scan QR code (in the video call or in person) and start sending messages instantly - no emails, phone numbers or passwords needed.

+

Your profile and contacts are only stored in the app on your device - our servers do not have access to this information.

+

All messages are end-to-end encrypted using open-source double-ratchet protocol; the messages are routed via our servers using open-source SimpleX Messaging Protocol.

+
+ + + + +

General:

+
    +
  1. fixes in sending/receiving files.
  2. +
  3. better error reporting when connecting to desktop app.
  4. +
  5. prevent forwarding to conversations where conversation preferences do not allow message.
  6. +
+

Android and desktop apps:

+
    +
  1. support transparent theme colors for chat message bubbles.
  2. +
  3. do not reset changed network settings when switching SOCKS proxy on/off
  4. +
  5. fix swipe to reply when animation is disabled.
  6. +
  7. fix bug when duplicate group shown in the UI.
  8. +
+
+
+
+ + + + https://simplex.chat/ + https://github.com/simplex-chat/simplex-chat/issues + https://opencollective.com/simplex-chat + https://simplex.chat/docs/translations + https://simplex.chat/faq + https://github.com/simplex-chat/simplex-chat + + chat.simplex.simplex.desktop + + + #a5f0ff + #110e26 + + + + + https://simplex.chat/blog/images/simplex-desktop-light.png + + +
From d8126e0cd067cbf53db9edc31ec7fd1dbfb91025 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 28 Jun 2024 19:09:01 +0100 Subject: [PATCH 06/15] website: update join team --- docs/JOIN_TEAM.md | 55 +++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/docs/JOIN_TEAM.md b/docs/JOIN_TEAM.md index 3379f9ae04..26502a05af 100644 --- a/docs/JOIN_TEAM.md +++ b/docs/JOIN_TEAM.md @@ -15,29 +15,32 @@ We want to add up to 3 people to the team. ## Who we are looking for +### Product/UI designer + +You will be designing the user experience and the interface of both the app and the website in collaboration with the team. + +The current focus of the app is privacy and security, but we hope to have the design that would support the feeling of psychological safety, enabling people to achieve the results in the smallest amount of time. + +You are an experienced and innovative product designer with: +- 8+ years of user experience and visual design. +- Expertise in typography and high sensitivity to colors. +- Exceptional precision and attention to details. +- Strong opinions (weakly held). +- A strong empathy. + ### Application Haskell engineer +You will work with the Haskell core of the client applications and with the network servers. + You are an expert in language models, databases and Haskell: - expert knowledge of SQL. -- exception handling, concurrency, STM. -- type systems - we use ad hoc dependent types a lot. -- experience integrating open-source language models. -- experience developing community-centric applications. -- interested to build the next generation of messaging network. - -You will be focussed mostly on our client applications, and will also contribute to the servers also written in Haskell. - -### iOS / Mac engineer - -You are an expert in Apple platforms, including: -- iOS and Mac platform architecture. -- Swift and Objective-C. -- SwiftUI and UIKit. -- extensions, including notification service extension and sharing extension. -- low level inter-process communication primitives for concurrency. -- interested about creating the next generation of UX for a communication/social network. - -Knowledge of Android and Kotlin Multiplatform would be a bonus - we use Kotlin Jetpack Compose for our Android and desktop apps. +- Haskell exception handling, concurrency, STM, type systems. +- 8y+ of software engineering experience in complex projects, +- deep understanding of the common programming principles: + - data structures, bits and bytes, text encoding. + - software design and algorithms. + - concurrency. + - networking. ## About you @@ -53,20 +56,10 @@ Knowledge of Android and Kotlin Multiplatform would be a bonus - we use Kotlin J - focus on solving only today's problems and resist engineering for the future (aka over-engineering) – see [The Duct Tape Programmer](https://www.joelonsoftware.com/2009/09/23/the-duct-tape-programmer/) and [Why I Hate Frameworks](https://medium.com/@johnfliu/why-i-hate-frameworks-6af8cbadba42). - do not suffer from "not invented here" syndrome, at the same time interested to design and implement protocols and systems from the ground up when appropriate. -- **Love software engineering**: - - have 5y+ of software engineering experience in complex projects, - - great understanding of the common principles: - - data structures, bits and byte manipulation - - text encoding and manipulation - - software design and algorithms - - concurrency - - networking - - **Want to join a very early stage startup**: - high pace and intensity, longer hours. - a substantial part of the compensation is stock options. - - full transparency – we believe that too much [autonomy](https://twitter.com/KentBeck/status/851459129830850561) hurts learning and slows down progress. - + - full transparency - we believe that too much [autonomy](https://twitter.com/KentBeck/status/851459129830850561) hurts learning and slows down progress. ## How to join the team @@ -75,3 +68,5 @@ Knowledge of Android and Kotlin Multiplatform would be a bonus - we use Kotlin J 2. Also look through [GitHub issues](https://github.com/simplex-chat/simplex-chat/issues) submitted by the users to see what would you want to contribute as a test. 3. [Connect to us](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FKBCmxJ3-lEjpWLPPkI6OWPk-YJneU5uY%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEAtixHJWDXvYWcoe-77vIfjvI6XWEuzUsapMS9nVHP_Go%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) via SimpleX Chat to chat about what you want to contribute and about joining the team. + +4. You can also email [jobs@simplex.chat](mailto:jobs@simplex.chat?subject=Join%20SimpleX%20Chat%20team) From fab38276970021af3c0b0a45fb18753bbad257f8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 29 Jun 2024 08:41:21 +0100 Subject: [PATCH 07/15] core: fix markdown for trailing / and ) characters in the links (#4357) --- src/Simplex/Chat/Markdown.hs | 8 ++++++-- tests/MarkdownTests.hs | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index 688ffa6b1d..8e82bfe727 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -222,11 +222,15 @@ markdownP = mconcat <$> A.many' fragmentP wordMD s | T.null s = unmarked s | isUri s = - let t = T.takeWhileEnd isPunctuation s - uri = uriMarkdown $ T.dropWhileEnd isPunctuation s + let t = T.takeWhileEnd isPunctuation' s + uri = uriMarkdown $ T.dropWhileEnd isPunctuation' s in if T.null t then uri else uri :|: unmarked t | isEmail s = markdown Email s | otherwise = unmarked s + isPunctuation' = \case + '/' -> False + ')' -> False + c -> isPunctuation c uriMarkdown s = case strDecode $ encodeUtf8 s of Right cReq -> markdown (simplexUriFormat cReq) s _ -> markdown Uri s diff --git a/tests/MarkdownTests.hs b/tests/MarkdownTests.hs index a279201bea..615edc02c4 100644 --- a/tests/MarkdownTests.hs +++ b/tests/MarkdownTests.hs @@ -149,6 +149,11 @@ textWithUri = describe "text with Uri" do parseMarkdown "http://simplex.chat" `shouldBe` uri "http://simplex.chat" parseMarkdown "this is https://simplex.chat" `shouldBe` "this is " <> uri "https://simplex.chat" parseMarkdown "https://simplex.chat site" `shouldBe` uri "https://simplex.chat" <> " site" + parseMarkdown "SimpleX on GitHub: https://github.com/simplex-chat/" `shouldBe` "SimpleX on GitHub: " <> uri "https://github.com/simplex-chat/" + parseMarkdown "SimpleX on GitHub: https://github.com/simplex-chat." `shouldBe` "SimpleX on GitHub: " <> uri "https://github.com/simplex-chat" <> "." + parseMarkdown "https://github.com/simplex-chat/ - SimpleX on GitHub" `shouldBe` uri "https://github.com/simplex-chat/" <> " - SimpleX on GitHub" + -- parseMarkdown "SimpleX on GitHub (https://github.com/simplex-chat/)" `shouldBe` "SimpleX on GitHub (" <> uri "https://github.com/simplex-chat/" <> ")" + parseMarkdown "https://en.m.wikipedia.org/wiki/Servo_(software)" `shouldBe` uri "https://en.m.wikipedia.org/wiki/Servo_(software)" it "ignored as markdown" do parseMarkdown "_https://simplex.chat" `shouldBe` "_https://simplex.chat" parseMarkdown "this is _https://simplex.chat" `shouldBe` "this is _https://simplex.chat" From d066c662820f1fdb01835fe89326e3f8360e3d7c Mon Sep 17 00:00:00 2001 From: sergeyshadok <41486169+sergeyshadok@users.noreply.github.com> Date: Sat, 29 Jun 2024 15:50:28 +0000 Subject: [PATCH 08/15] Update XFTP-SERVER.md (#4184) Systemd itself uses % tokens so %% needs to be used for specifiers (https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html) --- docs/XFTP-SERVER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/XFTP-SERVER.md b/docs/XFTP-SERVER.md index a0ad0e0cf7..a2eb9816e5 100644 --- a/docs/XFTP-SERVER.md +++ b/docs/XFTP-SERVER.md @@ -75,7 +75,7 @@ Manual installation requires some preliminary actions: Group=xftp Type=simple ExecStart=/usr/local/bin/xftp-server start +RTS -N -RTS - ExecStopPost=/usr/bin/env sh -c '[ -e "/var/opt/simplex-xftp/file-server-store.log" ] && cp "/var/opt/simplex-xftp/file-server-store.log" "/var/opt/simplex-xftp/file-server-store.log.$(date +'%FT%T')"' + ExecStopPost=/usr/bin/env sh -c '[ -e "/var/opt/simplex-xftp/file-server-store.log" ] && cp "/var/opt/simplex-xftp/file-server-store.log" "/var/opt/simplex-xftp/file-server-store.log.$(date +'%%FT%%T')"' LimitNOFILE=65535 KillSignal=SIGINT TimeoutStopSec=infinity From 231082860e65c51dca61700c35021e11607ae529 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:34:38 +0000 Subject: [PATCH 09/15] flatpak: verify app (#4379) --- website/src/.well-known/org.flathub.VerifiedApps.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 website/src/.well-known/org.flathub.VerifiedApps.txt diff --git a/website/src/.well-known/org.flathub.VerifiedApps.txt b/website/src/.well-known/org.flathub.VerifiedApps.txt new file mode 100644 index 0000000000..3a9a2d04de --- /dev/null +++ b/website/src/.well-known/org.flathub.VerifiedApps.txt @@ -0,0 +1 @@ +ae8b5b2e-76c9-4a31-a044-bcbda1cdf264 From 5907d8bd0c58a57ba7c96fea7ad912e792856718 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 1 Jul 2024 11:25:27 +0100 Subject: [PATCH 10/15] core: remove legacy agent stats (#4375) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 18 +++--------------- src/Simplex/Chat/Controller.hs | 5 ----- src/Simplex/Chat/View.hs | 2 -- 5 files changed, 5 insertions(+), 24 deletions(-) diff --git a/cabal.project b/cabal.project index be95820371..2177a5a130 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: c7886926870e97fa592d51fa36a2cdec49296388 + tag: 6a54a58a0d47aeca9d5482b0d27e451731b692f1 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 3a8284a74b..beb33a733a 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."c7886926870e97fa592d51fa36a2cdec49296388" = "1r3nibcgw3whl0q3ssyr1606x4ilqphhzqyihi3aw4nw5fmz226h"; + "https://github.com/simplex-chat/simplexmq.git"."6a54a58a0d47aeca9d5482b0d27e451731b692f1" = "1hfbzcirncmmzag2c3gbxnpxl1dwy9gy42d78a5zzf1979rp3yjq"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 539ad003ba..5e288f886c 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -20,7 +20,6 @@ import Control.Applicative (optional, (<|>)) import Control.Concurrent.STM (retry) import Control.Logger.Simple import Control.Monad -import Simplex.Chat.Stats import Control.Monad.Except import Control.Monad.IO.Unlift import Control.Monad.Reader @@ -72,6 +71,7 @@ import Simplex.Chat.ProfileGenerator (generateRandomProfile) import Simplex.Chat.Protocol import Simplex.Chat.Remote import Simplex.Chat.Remote.Types +import Simplex.Chat.Stats import Simplex.Chat.Store import Simplex.Chat.Store.AppSettings import Simplex.Chat.Store.Connections @@ -95,7 +95,7 @@ import Simplex.FileTransfer.Protocol (FileParty (..), FilePartyI) import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.FileTransfer.Types (FileErrorType (..), RcvFileId, SndFileId) import Simplex.Messaging.Agent as Agent -import Simplex.Messaging.Agent.Client (AgentStatsKey (..), SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary, getNetworkConfig', ipAddressProtected, withLockMap) +import Simplex.Messaging.Agent.Client (SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary, getNetworkConfig', ipAddressProtected, withLockMap) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), createAgentStore, defaultAgentConfig) import Simplex.Messaging.Agent.Lock (withLock) import Simplex.Messaging.Agent.Protocol @@ -113,7 +113,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (base64P) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol, ProtocolServer) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import qualified Simplex.Messaging.TMap as TM @@ -2270,15 +2270,6 @@ processChatCommand' vr = \case pure $ map protoServer srvs GetAgentWorkers -> lift $ CRAgentWorkersSummary <$> withAgent' getAgentWorkersSummary GetAgentWorkersDetails -> lift $ CRAgentWorkersDetails <$> withAgent' getAgentWorkersDetails - GetAgentStats -> lift $ CRAgentStats . map stat <$> withAgent' getAgentStats - where - stat (AgentStatsKey {host, clientTs, cmd, res}, count) = - map B.unpack [host, clientTs, cmd, res, bshow count] - ResetAgentStats -> lift (withAgent' resetAgentStats) >> ok_ - GetAgentMsgCounts -> lift $ do - counts <- map (first decodeLatin1) <$> withAgent' getMsgCounts - let allMsgs = foldl' (\(ts, ds) (_, (t, d)) -> (ts + t, ds + d)) (0, 0) counts - pure CRAgentMsgCounts {msgCounts = ("all", allMsgs) : sortOn (Down . snd) (filter (\(_, (_, d)) -> d /= 0) counts)} GetAgentSubs -> lift $ summary <$> withAgent' getAgentSubscriptions where summary SubscriptionsInfo {activeSubscriptions, pendingSubscriptions, removedSubscriptions} = @@ -7627,13 +7618,10 @@ chatCommandP = "/debug locks" $> DebugLocks, "/debug event " *> (DebugEvent <$> jsonP), "/get servers summary " *> (GetAgentServersSummary <$> A.decimal), - "/get stats" $> GetAgentStats, - "/reset stats" $> ResetAgentStats, "/get subs" $> GetAgentSubs, "/get subs details" $> GetAgentSubsDetails, "/get workers" $> GetAgentWorkers, "/get workers details" $> GetAgentWorkersDetails, - "/get msgs" $> GetAgentMsgCounts, "/get queues" $> GetAgentQueuesInfo, "//" *> (CustomChatCommand <$> A.takeByteString) ] diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 4404360c2a..366e767154 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -507,13 +507,10 @@ data ChatCommand | DebugLocks | DebugEvent ChatResponse | GetAgentServersSummary UserId - | GetAgentStats - | ResetAgentStats | GetAgentSubs | GetAgentSubsDetails | GetAgentWorkers | GetAgentWorkersDetails - | GetAgentMsgCounts | GetAgentQueuesInfo | -- The parser will return this command for strings that start from "//". -- This command should be processed in preCmdHook @@ -759,12 +756,10 @@ data ChatResponse | CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]} | CRDebugLocks {chatLockName :: Maybe String, chatEntityLocks :: Map String String, agentLocks :: AgentLocks} | CRAgentServersSummary {user :: User, serversSummary :: PresentedServersSummary} - | CRAgentStats {agentStats :: [[String]]} | CRAgentWorkersDetails {agentWorkersDetails :: AgentWorkersDetails} | CRAgentWorkersSummary {agentWorkersSummary :: AgentWorkersSummary} | CRAgentSubs {activeSubs :: Map Text Int, pendingSubs :: Map Text Int, removedSubs :: Map Text [String]} | CRAgentSubsDetails {agentSubs :: SubscriptionsInfo} - | CRAgentMsgCounts {msgCounts :: [(Text, (Int, Int))]} | CRAgentQueuesInfo {agentQueuesInfo :: AgentQueuesInfo} | CRContactDisabled {user :: User, contact :: Contact} | CRConnectionDisabled {connectionEntity :: ConnectionEntity} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 5b14084552..04c06103ea 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -366,7 +366,6 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe "agent locks: " <> viewJSON agentLocks ] CRAgentServersSummary u serversSummary -> ttyUser u ["agent servers summary: " <> viewJSON serversSummary] - CRAgentStats stats -> map (plain . intercalate ",") stats CRAgentSubs {activeSubs, pendingSubs, removedSubs} -> [plain $ "Subscriptions: active = " <> show (sum activeSubs) <> ", pending = " <> show (sum pendingSubs) <> ", removed = " <> show (sum $ M.map length removedSubs)] <> ("active subscriptions:" : listSubs activeSubs) @@ -384,7 +383,6 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe [ "agent workers details:", viewJSON agentWorkersDetails -- this would be huge, but copypastable when has its own line ] - CRAgentMsgCounts {msgCounts} -> ["received messages (total, duplicates):", viewJSON msgCounts] CRAgentQueuesInfo {agentQueuesInfo} -> [ "agent queues info:", plain . LB.unpack $ J.encode agentQueuesInfo From 593c7d247c2acd557987d6ae6bb60c7a5326a1d8 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:10:22 +0400 Subject: [PATCH 11/15] ui: add ServerEnabled type (#4381) --- .../UserSettings/ProtocolServerView.swift | 9 +++++++-- .../UserSettings/ProtocolServersView.swift | 15 ++++++++------- .../UserSettings/ScanProtocolServer.swift | 2 +- apps/ios/SimpleXChat/APITypes.swift | 18 ++++++++++++------ .../chat/simplex/common/model/SimpleXAPI.kt | 17 ++++++++++++----- .../views/usersettings/ProtocolServerView.kt | 6 ++++-- .../views/usersettings/ProtocolServersView.kt | 12 ++++++------ .../views/usersettings/ScanProtocolServer.kt | 3 ++- 8 files changed, 52 insertions(+), 30 deletions(-) diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift index 6702ab7ce8..8926ee09fa 100644 --- a/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift @@ -14,6 +14,7 @@ struct ProtocolServerView: View { let serverProtocol: ServerProtocol @Binding var server: ServerCfg @State var serverToEdit: ServerCfg + @State var serverEnabled: Bool @State private var showTestFailure = false @State private var testing = false @State private var testFailure: ProtocolTestFailure? @@ -110,7 +111,10 @@ struct ProtocolServerView: View { Spacer() showTestStatus(server: serverToEdit) } - Toggle("Use for new connections", isOn: $serverToEdit.enabled) + Toggle("Use for new connections", isOn: $serverEnabled) + .onChange(of: serverEnabled) { enabled in + serverToEdit.enabled = enabled ? .enabled : .disabled + } } } } @@ -179,7 +183,8 @@ struct ProtocolServerView_Previews: PreviewProvider { ProtocolServerView( serverProtocol: .smp, server: Binding.constant(ServerCfg.sampleData.custom), - serverToEdit: ServerCfg.sampleData.custom + serverToEdit: ServerCfg.sampleData.custom, + serverEnabled: true ) } } diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift index b9163d4bad..db82008182 100644 --- a/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift +++ b/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift @@ -159,7 +159,7 @@ struct ProtocolServersView: View { } private var allServersDisabled: Bool { - servers.allSatisfy { !$0.enabled } + servers.allSatisfy { $0.enabled != .enabled } } private func protocolServerView(_ server: Binding) -> some View { @@ -168,7 +168,8 @@ struct ProtocolServersView: View { ProtocolServerView( serverProtocol: serverProtocol, server: server, - serverToEdit: srv + serverToEdit: srv, + serverEnabled: srv.enabled == .enabled ) .navigationBarTitle(srv.preset ? "Preset server" : "Your server") .navigationBarTitleDisplayMode(.large) @@ -181,7 +182,7 @@ struct ProtocolServersView: View { invalidServer() } else if !uniqueAddress(srv, address) { Image(systemName: "exclamationmark.circle").foregroundColor(.red) - } else if !srv.enabled { + } else if srv.enabled != .enabled { Image(systemName: "slash.circle").foregroundColor(.secondary) } else { showTestStatus(server: srv) @@ -194,7 +195,7 @@ struct ProtocolServersView: View { .padding(.trailing, 4) let v = Text(address?.hostnames.first ?? srv.server).lineLimit(1) - if srv.enabled { + if srv.enabled == .enabled { v } else { v.foregroundColor(.secondary) @@ -235,7 +236,7 @@ struct ProtocolServersView: View { private func addAllPresets() { for srv in presetServers { if !hasPreset(srv) { - servers.append(ServerCfg(server: srv, preset: true, tested: nil, enabled: true)) + servers.append(ServerCfg(server: srv, preset: true, tested: nil, enabled: .enabled)) } } } @@ -260,7 +261,7 @@ struct ProtocolServersView: View { private func resetTestStatus() { for i in 0.. [String: ProtocolTestFailure] { var fs: [String: ProtocolTestFailure] = [:] for i in 0.. + onUpdate(server.copy(enabled = if (enable) ServerEnabled.Enabled else ServerEnabled.Disabled)) + } SectionItemView(onDelete, disabled = testing) { Text(stringResource(MR.strings.smp_servers_delete_server), color = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.error) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt index fe847432fb..bbf3e07c93 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt @@ -34,7 +34,7 @@ fun ModalData.ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: Ser val currServers = remember(rhId) { mutableStateOf(servers) } val testing = rememberSaveable(rhId) { mutableStateOf(false) } val serversUnchanged = remember(servers) { derivedStateOf { servers == currServers.value || testing.value } } - val allServersDisabled = remember { derivedStateOf { servers.none { it.enabled } } } + val allServersDisabled = remember { derivedStateOf { servers.none { it.enabled == ServerEnabled.Enabled } } } val saveDisabled = remember(servers) { derivedStateOf { servers.isEmpty() || @@ -250,12 +250,12 @@ private fun ProtocolServerView(serverProtocol: ServerProtocol, srv: ServerCfg, s val address = parseServerAddress(srv.server) when { address == null || !address.valid || address.serverProtocol != serverProtocol || !uniqueAddress(srv, address, servers) -> InvalidServer() - !srv.enabled -> Icon(painterResource(MR.images.ic_do_not_disturb_on), null, tint = MaterialTheme.colors.secondary) + srv.enabled != ServerEnabled.Enabled -> Icon(painterResource(MR.images.ic_do_not_disturb_on), null, tint = MaterialTheme.colors.secondary) else -> ShowTestStatus(srv) } Spacer(Modifier.padding(horizontal = 4.dp)) val text = address?.hostnames?.firstOrNull() ?: srv.server - if (srv.enabled) { + if (srv.enabled == ServerEnabled.Enabled) { Text(text, color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground, maxLines = 1) } else { Text(text, maxLines = 1, color = MaterialTheme.colors.secondary) @@ -292,7 +292,7 @@ private fun addAllPresets(rhId: Long?, presetServers: List, servers: Lis val toAdd = ArrayList() for (srv in presetServers) { if (!hasPreset(srv, servers)) { - toAdd.add(ServerCfg(remoteHostId = rhId, srv, preset = true, tested = null, enabled = true)) + toAdd.add(ServerCfg(remoteHostId = rhId, srv, preset = true, tested = null, enabled = ServerEnabled.Enabled)) } } return toAdd @@ -319,7 +319,7 @@ private suspend fun testServers(testing: MutableState, servers: List): List { val copy = ArrayList(servers) for ((index, server) in servers.withIndex()) { - if (server.enabled) { + if (server.enabled == ServerEnabled.Enabled) { copy.removeAt(index) copy.add(index, server.copy(tested = null)) } @@ -331,7 +331,7 @@ private suspend fun runServersTest(servers: List, m: ChatModel, onUpd val fs: MutableMap = mutableMapOf() val updatedServers = ArrayList(servers) for ((index, server) in servers.withIndex()) { - if (server.enabled) { + if (server.enabled == ServerEnabled.Enabled) { interruptIfCancelled() val (updatedServer, f) = testServerConnection(server, m) updatedServers.removeAt(index) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt index 502b579d64..c1951341a4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt @@ -7,6 +7,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress import chat.simplex.common.model.ServerCfg +import chat.simplex.common.model.ServerEnabled import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCodeScanner @@ -25,7 +26,7 @@ fun ScanProtocolServerLayout(rhId: Long?, onNext: (ServerCfg) -> Unit) { QRCodeScanner { text -> val res = parseServerAddress(text) if (res != null) { - onNext(ServerCfg(remoteHostId = rhId, text, false, null, true)) + onNext(ServerCfg(remoteHostId = rhId, text, false, null, ServerEnabled.Enabled)) } else { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.smp_servers_invalid_address), From 00ba46889867a56cd5089ebb9e0091605ee17f3a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 2 Jul 2024 14:35:47 +0100 Subject: [PATCH 12/15] core: update simplexmq (disable fast handshake) (#4388) * core: update simplexmq (disable fast handshake) * fix encoding tests * fix * update simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 1 - tests/ProtocolTests.hs | 23 ++++++++++++----------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cabal.project b/cabal.project index 2177a5a130..49e35a6e48 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 6a54a58a0d47aeca9d5482b0d27e451731b692f1 + tag: aa1d8d6c8bcff82f02aa580b1b729fc4b1396fd9 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index beb33a733a..fab7a3de0e 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."6a54a58a0d47aeca9d5482b0d27e451731b692f1" = "1hfbzcirncmmzag2c3gbxnpxl1dwy9gy42d78a5zzf1979rp3yjq"; + "https://github.com/simplex-chat/simplexmq.git"."aa1d8d6c8bcff82f02aa580b1b729fc4b1396fd9" = "01walrsa69n4f01bs5w4sj7svsah483dxmg88a0si7qh5ylay0r0"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 5e288f886c..2a7c0ec83e 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -47,7 +47,6 @@ import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, listToMaybe, mapMaybe, maybeToList) -import Data.Ord (Down (..)) import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index c4dfa7da25..0bc801e824 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -34,7 +34,8 @@ queue = SMPQueueAddress { smpServer = srv, senderId = "\223\142z\251", - dhPublicKey = "MCowBQYDK2VuAyEAjiswwI3O/NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o=" + dhPublicKey = "MCowBQYDK2VuAyEAjiswwI3O/NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o=", + sndSecure = False } connReqData :: ConnReqUriData @@ -194,7 +195,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.msg.deleted\",\"params\":{}}" #==# XMsgDeleted it "x.file" $ - "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" + "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" #==# XFile FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Just testConnReq, fileInline = Nothing, fileDescr = Nothing} it "x.file without file invitation" $ "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" @@ -203,7 +204,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.file.acpt\",\"params\":{\"fileName\":\"photo.jpg\"}}" #==# XFileAcpt "photo.jpg" it "x.file.acpt.inv" $ - "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" + "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" #==# XFileAcptInv (SharedMsgId "\1\2\3\4") (Just testConnReq) "photo.jpg" it "x.file.acpt.inv" $ "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\"}}" @@ -230,10 +231,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.contact\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" ==# XContact testProfile Nothing it "x.grp.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Nothing, groupSize = Nothing} it "x.grp.inv with group link id" $ - "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing} it "x.grp.acpt without incognito profile" $ "{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}" @@ -254,16 +255,16 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} (Just MemberRestrictions {restriction = MRSBlocked}) it "x.grp.mem.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" #==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.inv w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" #==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.fwd" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-8\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-8\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.info" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" @@ -284,10 +285,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.del\",\"params\":{}}" ==# XGrpDel it "x.grp.direct.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" #==# XGrpDirectInv testConnReq (Just $ MCText "hello") it "x.grp.direct.inv without content" $ - "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" + "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" #==# XGrpDirectInv testConnReq Nothing -- it "x.grp.msg.forward" -- $ "{\"v\":\"1\",\"event\":\"x.grp.msg.forward\",\"params\":{\"msgForward\":{\"memberId\":\"AQIDBA==\",\"msg\":\"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}\",\"msgTs\":\"1970-01-01T00:00:01.000000001Z\"}}}" From ddeaa1c7c342d1cd11f3445eb3899449ee359ec0 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 2 Jul 2024 14:50:10 +0100 Subject: [PATCH 13/15] core: servers --- src/Simplex/Chat.hs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 2a7c0ec83e..cc829eaa16 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -175,12 +175,11 @@ defaultChatConfig = _defaultSMPServers :: NonEmpty SMPServerWithAuth _defaultSMPServers = L.fromList - [ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion", - "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion", - "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion", - "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion", - "smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion", - "smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion" + [ "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", + "smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion", + "smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion", + "smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion", + "smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion" ] _defaultNtfServers :: [NtfServer] From 0d3928bd5179eb4e29cb78dad08b12c91a49405f Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 2 Jul 2024 21:02:36 +0700 Subject: [PATCH 14/15] android, desktop: handle situation when not all databases created before shut down (#4294) * android, desktop: handle situation when not all databases created before shut down * more logic of choosing whether to delete databases or not * comment * rename * refactoring --- .../chat/simplex/common/model/ChatModel.kt | 1 + .../chat/simplex/common/model/SimpleXAPI.kt | 7 +++ .../chat/simplex/common/platform/Core.kt | 15 +++++++ .../views/database/DatabaseErrorView.kt | 43 ++++++++++++++++--- .../common/views/database/DatabaseView.kt | 2 + .../common/views/helpers/DatabaseUtils.kt | 11 +++-- 6 files changed, 68 insertions(+), 11 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 0ebe77e524..c155010a28 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -52,6 +52,7 @@ object ChatModel { val chatDbStatus = mutableStateOf(null) val ctrlInitInProgress = mutableStateOf(false) val dbMigrationInProgress = mutableStateOf(false) + val incompleteInitializedDbRemoved = mutableStateOf(false) val chats = mutableStateListOf() // map of connections network statuses, key is agent connection id val networkStatuses = mutableStateMapOf() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 795a4d50e1..60957d0f41 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -176,6 +176,12 @@ class AppPreferences { val selfDestruct = mkBoolPreference(SHARED_PREFS_SELF_DESTRUCT, false) val selfDestructDisplayName = mkStrPreference(SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME, null) + // This flag is set when database is first initialized and resets only when the database is removed. + // This is needed for recover from incomplete initialization when only one database file is created. + // If false - the app will clear database folder on missing file and re-initialize. + // Note that this situation can only happen if passphrase for the first database is incorrect because, otherwise, backend will re-create second database automatically + val newDatabaseInitialized = mkBoolPreference(SHARED_PREFS_NEW_DATABASE_INITIALIZED, false) + val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM_THEME_NAME) val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.themeName) val currentThemeIds = mkMapPreference(SHARED_PREFS_CURRENT_THEME_IDs, mapOf(), encode = { @@ -361,6 +367,7 @@ class AppPreferences { private const val SHARED_PREFS_ENCRYPTED_SELF_DESTRUCT_PASSPHRASE = "EncryptedSelfDestructPassphrase" private const val SHARED_PREFS_INITIALIZATION_VECTOR_SELF_DESTRUCT_PASSPHRASE = "InitializationVectorSelfDestructPassphrase" private const val SHARED_PREFS_ENCRYPTION_STARTED_AT = "EncryptionStartedAt" + private const val SHARED_PREFS_NEW_DATABASE_INITIALIZED = "NewDatabaseInitialized" private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades" private const val SHARED_PREFS_SELF_DESTRUCT = "LocalAuthenticationSelfDestruct" private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index d6b8901042..57b93d4d6e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -88,8 +88,23 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat chatModel.chatDbStatus.value = res if (res != DBMigrationResult.OK) { Log.d(TAG, "Unable to migrate successfully: $res") + if (!appPrefs.newDatabaseInitialized.get() && DatabaseUtils.hasOnlyOneDatabase(dataDir.absolutePath)) { + if (chatModel.incompleteInitializedDbRemoved.value) { + Log.d(TAG, "Incomplete initialized databases were removed but after repeated migration only one database exists again, not trying to remove again") + } else { + val dbPath = dbAbsolutePrefixPath + File(dbPath + "_chat.db").delete() + File(dbPath + "_agent.db").delete() + chatModel.incompleteInitializedDbRemoved.value = true + Log.d(TAG, "Incomplete initialized databases were removed for the first time, repeating migration") + chatModel.ctrlInitInProgress.value = false + initChatController(useKey, confirmMigrations, startChat) + } + } return } + appPrefs.newDatabaseInitialized.set(true) + chatModel.incompleteInitializedDbRemoved.value = false platform.androidRestartNetworkObserver() controller.apiSetAppFilePaths( appFilesDir.absolutePath, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt index 5f0356bb2d..109e5bc737 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt @@ -22,8 +22,11 @@ import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.AppVersionText +import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.datetime.Clock import java.io.File @@ -106,7 +109,7 @@ fun DatabaseErrorView( } } is DBMigrationResult.ErrorMigration -> when (val err = status.migrationError) { - is MigrationError.Upgrade -> + is MigrationError.Upgrade -> { DatabaseErrorDetails(MR.strings.database_upgrade) { TextButton({ callRunChat(confirmMigrations = MigrationConfirmation.YesUp) }, Modifier.align(Alignment.CenterHorizontally), enabled = !progressIndicator.value) { Text(generalGetString(MR.strings.upgrade_and_open_chat)) @@ -116,7 +119,9 @@ fun DatabaseErrorView( MigrationsText(err.upMigrations.map { it.upName }) AppVersionText() } - is MigrationError.Downgrade -> + OpenDatabaseDirectoryButton() + } + is MigrationError.Downgrade -> { DatabaseErrorDetails(MR.strings.database_downgrade) { TextButton({ callRunChat(confirmMigrations = MigrationConfirmation.YesUpDown) }, Modifier.align(Alignment.CenterHorizontally), enabled = !progressIndicator.value) { Text(generalGetString(MR.strings.downgrade_and_open_chat)) @@ -127,29 +132,41 @@ fun DatabaseErrorView( MigrationsText(err.downMigrations) AppVersionText() } - is MigrationError.Error -> + OpenDatabaseDirectoryButton() + } + is MigrationError.Error -> { DatabaseErrorDetails(MR.strings.incompatible_database_version) { FileNameText(status.dbFile) Text(String.format(generalGetString(MR.strings.error_with_info), mtrErrorDescription(err.mtrError))) } + OpenDatabaseDirectoryButton() + } } - is DBMigrationResult.ErrorSQL -> + is DBMigrationResult.ErrorSQL -> { DatabaseErrorDetails(MR.strings.database_error) { FileNameText(status.dbFile) Text(String.format(generalGetString(MR.strings.error_with_info), status.migrationSQLError)) } - is DBMigrationResult.ErrorKeychain -> + OpenDatabaseDirectoryButton() + } + is DBMigrationResult.ErrorKeychain -> { DatabaseErrorDetails(MR.strings.keychain_error) { Text(generalGetString(MR.strings.cannot_access_keychain)) } - is DBMigrationResult.InvalidConfirmation -> + OpenDatabaseDirectoryButton() + } + is DBMigrationResult.InvalidConfirmation -> { DatabaseErrorDetails(MR.strings.invalid_migration_confirmation) { // this can only happen if incorrect parameter is passed } - is DBMigrationResult.Unknown -> + OpenDatabaseDirectoryButton() + } + is DBMigrationResult.Unknown -> { DatabaseErrorDetails(MR.strings.database_error) { Text(String.format(generalGetString(MR.strings.unknown_database_error_with_info), status.json)) } + OpenDatabaseDirectoryButton() + } is DBMigrationResult.OK -> {} null -> {} } @@ -294,6 +311,18 @@ private fun ColumnScope.SaveAndOpenButton(enabled: Boolean, onClick: () -> Unit) } } +@Composable +private fun OpenDatabaseDirectoryButton() { + if (appPlatform.isDesktop) { + Spacer(Modifier.padding(top = DEFAULT_PADDING)) + SettingsActionItem( + painterResource(MR.images.ic_folder_open), + stringResource(MR.strings.open_database_folder), + ::desktopOpenDatabaseDir + ) + } +} + @Composable private fun ColumnScope.OpenChatButton(enabled: Boolean, onClick: () -> Unit) { TextButton(onClick, Modifier.align(Alignment.CenterHorizontally), enabled = enabled) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index ca0a6f2f93..6c51f778d6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.updatingChatsMutex import chat.simplex.common.ui.theme.* @@ -492,6 +493,7 @@ fun deleteChatDatabaseFilesAndState() { wallpapersDir.deleteRecursively() wallpapersDir.mkdirs() DatabaseUtils.ksDatabasePassword.remove() + appPrefs.newDatabaseInitialized.set(false) controller.appPrefs.storeDBPassphrase.set(true) controller.ctrl = null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt index 0ad7af439f..c621f186cd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt @@ -39,22 +39,25 @@ object DatabaseUtils { } } - private fun hasDatabase(rootDir: String): Boolean = - File(rootDir + File.separator + chatDatabaseFileName).exists() && File(rootDir + File.separator + agentDatabaseFileName).exists() + private fun hasAtLeastOneDatabase(rootDir: String): Boolean = + File(rootDir + File.separator + chatDatabaseFileName).exists() || File(rootDir + File.separator + agentDatabaseFileName).exists() + + fun hasOnlyOneDatabase(rootDir: String): Boolean = + File(rootDir + File.separator + chatDatabaseFileName).exists() != File(rootDir + File.separator + agentDatabaseFileName).exists() fun useDatabaseKey(): String { Log.d(TAG, "useDatabaseKey ${appPreferences.storeDBPassphrase.get()}") var dbKey = "" val useKeychain = appPreferences.storeDBPassphrase.get() if (useKeychain) { - if (!hasDatabase(dataDir.absolutePath)) { + if (!hasAtLeastOneDatabase(dataDir.absolutePath)) { dbKey = randomDatabasePassword() ksDatabasePassword.set(dbKey) appPreferences.initialRandomDBPassphrase.set(true) } else { dbKey = ksDatabasePassword.get() ?: "" } - } else if (appPlatform.isDesktop && !hasDatabase(dataDir.absolutePath)) { + } else if (appPlatform.isDesktop && !hasAtLeastOneDatabase(dataDir.absolutePath)) { // In case of database was deleted by hand dbKey = randomDatabasePassword() ksDatabasePassword.set(dbKey) From 85af368371378d35878f4554958dd29967893540 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 2 Jul 2024 15:07:46 +0100 Subject: [PATCH 15/15] core: 5.8.2.0, simplexmq: 5.8.2.0 --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index 49e35a6e48..3f05fb8561 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: aa1d8d6c8bcff82f02aa580b1b729fc4b1396fd9 + tag: f392ce0a9355cd3883400906ae6c361b77ca46ea source-repository-package type: git diff --git a/package.yaml b/package.yaml index 15f9c5bfa1..0bf1c1e858 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.8.1.0 +version: 5.8.2.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index fab7a3de0e..d046469ea8 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."aa1d8d6c8bcff82f02aa580b1b729fc4b1396fd9" = "01walrsa69n4f01bs5w4sj7svsah483dxmg88a0si7qh5ylay0r0"; + "https://github.com/simplex-chat/simplexmq.git"."f392ce0a9355cd3883400906ae6c361b77ca46ea" = "0id9mg30kmhlfcpnn2np3f0a4bb4smdzvhrbw6km8vv26si1js60"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 3b9d2681d9..48cd8a67d4 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 5.8.1.0 +version: 5.8.2.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat