2024-04-11 15:51:56 +02:00
// myChatMessages provides functions to manage a user's sent and received chat messages.
// Raw messages are decrypted and then stored in an unencrypted form.
package myChatMessages
//TODO: Prune deleted/undecryptable message hashes lists
// Message hashes should be deleted from this lists once the message expiration time is reached
import "seekia/internal/allowedText"
import "seekia/internal/badgerDatabase"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/messaging/myChatKeys"
import "seekia/internal/messaging/myCipherKeys"
import "seekia/internal/messaging/myInbox"
import "seekia/internal/messaging/myReadStatus"
import "seekia/internal/messaging/peerChatKeys"
import "seekia/internal/messaging/peerDevices"
import "seekia/internal/messaging/peerSecretInboxes"
import "seekia/internal/messaging/readMessages"
import "seekia/internal/myBlockedUsers"
import "seekia/internal/myDatastores/myMap"
import "seekia/internal/myDatastores/myMapList"
import "seekia/internal/myIdentity"
import "seekia/internal/mySettings"
import "seekia/internal/parameters/getParameters"
import "slices"
import "bytes"
import "sync"
import "errors"
import "time"
// This will be locked whenever the chat messages map lists are being updated
var updatingChatMessagesMutex sync . Mutex
var myMateChatMessagesMapListDatastore * myMapList . MyMapList
var myModeratorChatMessagesMapListDatastore * myMapList . MyMapList
// This map stores deleted message hashes
// This map exists to prevent importing/decrypting messages that a user has deleted (or were maliciously crafted).
var myDeletedMessagesMapDatastore * myMap . MyMap
// This map stores unreadable message hashes
// This map exists to prevent attempting to import messages which we cannot decrypt more than once
var myUndecryptableMessagesMapDatastore * myMap . MyMap
func getMyChatMessagesMapListDatastore ( myIdentityType string ) ( * myMapList . MyMapList , error ) {
if ( myIdentityType == "Mate" ) {
return myMateChatMessagesMapListDatastore , nil
}
if ( myIdentityType == "Moderator" ) {
return myModeratorChatMessagesMapListDatastore , nil
}
return nil , errors . New ( "getMyChatMessagesMapListDatastore called with invalid myIdentityType: " + myIdentityType )
}
// This function must be called whenever an app user signs in
func InitializeMyChatMessageDatastores ( ) error {
updatingChatMessagesMutex . Lock ( )
defer updatingChatMessagesMutex . Unlock ( )
newMyMateChatMessagesMapListDatastore , err := myMapList . CreateNewMapList ( "MyMateChatMessages" )
if ( err != nil ) { return err }
newMyModeratorChatMessagesMapListDatastore , err := myMapList . CreateNewMapList ( "MyModeratorChatMessages" )
if ( err != nil ) { return err }
newMyDeletedMessagesMapDatastore , err := myMap . CreateNewMap ( "MyDeletedMessages" )
if ( err != nil ) { return err }
newMyUndecryptableMessagesMapDatastore , err := myMap . CreateNewMap ( "MyUndecryptableMessages" )
if ( err != nil ) { return err }
myMateChatMessagesMapListDatastore = newMyMateChatMessagesMapListDatastore
myModeratorChatMessagesMapListDatastore = newMyModeratorChatMessagesMapListDatastore
myDeletedMessagesMapDatastore = newMyDeletedMessagesMapDatastore
myUndecryptableMessagesMapDatastore = newMyUndecryptableMessagesMapDatastore
return nil
}
// This function is called when a user deletes their identity
func DeleteMyChatMessagesMapList ( myIdentityType string ) error {
if ( myIdentityType != "Mate" && myIdentityType != "Moderator" ) {
return errors . New ( "DeleteMyChatMessagesMapList called with invalid myIdentityType: " + myIdentityType )
}
updatingChatMessagesMutex . Lock ( )
defer updatingChatMessagesMutex . Unlock ( )
myChatMessagesMapListDatastore , err := getMyChatMessagesMapListDatastore ( myIdentityType )
if ( err != nil ) { return err }
err = myChatMessagesMapListDatastore . DeleteMapList ( )
if ( err != nil ) { return err }
return nil
}
// This function checks to see if chat messages are ready
// Messages become not ready every time a new message is downloaded for one of the user's inboxes
func GetMyChatMessagesReadyStatus ( myIdentityType string ) ( bool , error ) {
if ( myIdentityType != "Mate" && myIdentityType != "Moderator" ) {
return false , errors . New ( "GetMyChatMessagesReadyStatus called with invalid myIdentityType: " + myIdentityType )
}
exists , readyStatus , err := mySettings . GetSetting ( myIdentityType + "ChatMessagesReadyStatus" )
if ( err != nil ) { return false , err }
if ( exists == false || readyStatus != "Yes" ) {
return false , nil
}
return true , nil
}
// This function returns an updated MyChatMessages map list
// This map list contains decrypted chat messages sent to the user
// The function will attempt to decrypt messages sent to a user's inboxes
// This function will also prune the map list of any different networkType messages
//
// Inputs:
// -string: My Identity Type ("Mate"/"Moderator")
// -byte: Network type (1 == Mainnet, 2 == Testnet 1)
// -func(int)error: Update progress function (will call the function to update between 0-100%)
//Outputs:
// -[]map[string]string: Updated myChatMessages map list
// -error
func GetUpdatedMyChatMessagesMapList ( myIdentityType string , networkType byte , updateProgressFunction func ( int ) error ) ( [ ] map [ string ] string , error ) {
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return nil , errors . New ( "GetUpdatedMyChatMessagesMapList called with invalid networkType: " + networkTypeString )
}
defer updateProgressFunction ( 100 )
myChatMessagesMapListDatastore , err := getMyChatMessagesMapListDatastore ( myIdentityType )
if ( err != nil ) { return nil , err }
messagesReadyStatus , err := GetMyChatMessagesReadyStatus ( myIdentityType )
if ( err != nil ) { return nil , err }
if ( messagesReadyStatus == false ) {
// We update our chat messages
updateMyChatMessages := func ( ) error {
parametersExist , err := getParameters . CheckIfSecretInboxEpochParametersExist ( networkType )
if ( err != nil ) { return err }
if ( parametersExist == false ) {
// We need these parameters to import user secret inbox information
// We will not update chat messages until we download these parameters
return nil
}
updatingChatMessagesMutex . Lock ( )
defer updatingChatMessagesMutex . Unlock ( )
myIdentityFound , myIdentityHash , err := myIdentity . GetMyIdentityHash ( myIdentityType )
if ( err != nil ) { return err }
if ( myIdentityFound == false ) {
// No chat messages can exist.
err = myChatMessagesMapListDatastore . DeleteMapList ( )
if ( err != nil ) { return err }
return nil
}
myInboxesList , err := myInbox . GetAllMyInboxes ( myIdentityHash , networkType )
if ( err != nil ) { return err }
err = updateProgressFunction ( 5 )
if ( err != nil ) { return err }
// This map will store raw encrypted message hashes for messages in our inboxes
// Map structure: Message Hash -> Message inbox
myRawInboxMessageHashesMap := make ( map [ [ 26 ] byte ] [ 10 ] byte )
for _ , inbox := range myInboxesList {
exists , inboxMessageHashesList , err := badgerDatabase . GetChatInboxMessageHashesList ( inbox )
if ( err != nil ) { return err }
if ( exists == false ) {
continue
}
for _ , messageHash := range inboxMessageHashesList {
myRawInboxMessageHashesMap [ messageHash ] = inbox
}
}
err = updateProgressFunction ( 10 )
if ( err != nil ) { return err }
if ( len ( myRawInboxMessageHashesMap ) == 0 ) {
// No messages to import, nothing left to do.
return nil
}
myIdentityExists , anyChatKeysExist , myChatDecryptionKeySetsList , err := myChatKeys . GetMyChatDecryptionKeySetsList ( myIdentityHash , networkType )
if ( err != nil ) { return err }
if ( myIdentityExists == false ) {
return errors . New ( "My identity not found after already being found." )
}
if ( anyChatKeysExist == false ) {
// The user has never broadcast their chat keys, or has deleted them.
// All messages are undecryptable, unless user imports decryption keys from their last device/profile
return nil
}
err = updateProgressFunction ( 15 )
if ( err != nil ) { return err }
myExistingMessagesMapList , err := myChatMessagesMapListDatastore . GetMapList ( )
if ( err != nil ) { return err }
// This will store the message hashes for messages we already have decrypted and imported
existingMessageHashesMap := make ( map [ [ 26 ] byte ] struct { } )
for _ , messageMap := range myExistingMessagesMapList {
messageIAmSender , exists := messageMap [ "IAmSender" ]
if ( exists == false ) {
return errors . New ( "Malformed message map: Missing IAmSender" )
}
if ( messageIAmSender == "Yes" ) {
continue
}
// Message was not send by user, so message status must be Sent
// Thus, messageHash will exist for all of these messages
currentMessageHashString , exists := messageMap [ "MessageHash" ]
if ( exists == false ) {
return errors . New ( "Malformed message map: Missing MessageHash." )
}
currentMessageHash , err := readMessages . ReadMessageHashHex ( currentMessageHashString )
if ( err != nil ) {
return errors . New ( "Malformed message map: contains invalid MessageHash: " + currentMessageHashString )
}
existingMessageHashesMap [ currentMessageHash ] = struct { } { }
}
err = updateProgressFunction ( 20 )
if ( err != nil ) { return err }
newMyChatMessagesMapList := make ( [ ] map [ string ] string , 0 )
index := 0
maximumIndex := len ( myRawInboxMessageHashesMap )
for messageHash , messageInbox := range myRawInboxMessageHashesMap {
2024-08-07 09:45:31 +02:00
newPercentageProgress , err := helpers . ScaleIntProportionally ( true , index , 0 , maximumIndex , 20 , 100 )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
err = updateProgressFunction ( newPercentageProgress )
if ( err != nil ) { return err }
index += 1
_ , messageIsAlreadyAdded := existingMessageHashesMap [ messageHash ]
if ( messageIsAlreadyAdded == true ) {
continue
}
messageHashHex := encoding . EncodeBytesToHexString ( messageHash [ : ] )
hasBeenDeleted , _ , err := myDeletedMessagesMapDatastore . GetMapEntry ( messageHashHex )
if ( err != nil ) { return err }
if ( hasBeenDeleted == true ) {
continue
}
isUndecryptable , _ , err := myUndecryptableMessagesMapDatastore . GetMapEntry ( messageHashHex )
if ( err != nil ) { return err }
if ( isUndecryptable == true ) {
// These are messages which we have already tried to import but were not able to
// This could be due to missing chat keys or a malicious sender
// If user ever imports new chat keys from a different device,
// the undecryptable messages map is deleted, so we can try again for all messages that haven't been deleted
continue
}
inboxFound , inboxMyIdentityHash , inboxDoubleSealedKeysSealerKey , inboxIsSecret , inboxNetworkType , conversationRecipient , err := myInbox . GetMyInboxInfo ( messageInbox )
if ( err != nil ) { return err }
if ( inboxFound == false ) {
// We know that the inbox is not a public inbox.
// We do not have the sealer key for this inbox. We cannot decrypt the message.
currentTime := time . Now ( ) . Unix ( )
currentTimeString := helpers . ConvertInt64ToString ( currentTime )
err := myUndecryptableMessagesMapDatastore . SetMapEntry ( messageHashHex , currentTimeString )
if ( err != nil ) { return err }
continue
}
if ( inboxMyIdentityHash != myIdentityHash ) {
return errors . New ( "GetAllMyInboxes is returning inbox for a different identity hash." )
}
if ( inboxIsSecret == true && inboxNetworkType != networkType ) {
return errors . New ( "GetAllMyInboxes is returning a secret inbox which belongs to a different network type." )
}
// Now we attempt to decrypt the message
messageExists , messageBytes , err := badgerDatabase . GetChatMessage ( messageHash )
if ( err != nil ) { return err }
if ( messageExists == false ) {
// Database inbox messages list is outdated. It will be updated automatically.
continue
}
ableToRead , _ , messageNetworkType , messageInbox_Received , _ , _ , _ , _ , _ , _ , err := readMessages . ReadChatMessagePublicData ( false , messageBytes )
if ( err != nil ) { return err }
if ( ableToRead == false ) {
return errors . New ( "Database corrupt: Contains message with unreadable public data." )
}
if ( messageInbox != messageInbox_Received ) {
return errors . New ( "myRawInboxMessageHashesMap contains mismatched message inbox value." )
}
if ( messageNetworkType != networkType ) {
// This message was sent to us on a different network type
// If the inbox is public, this is fine and expected
// If the inbox is secret, then the sender must be malicious (or our client is acting improperly)
if ( inboxIsSecret == true ) {
currentTime := time . Now ( ) . Unix ( )
currentTimeString := helpers . ConvertInt64ToString ( currentTime )
err := myDeletedMessagesMapDatastore . SetMapEntry ( messageHashHex , currentTimeString )
if ( err != nil ) { return err }
//TODO: Log this
}
continue
}
2024-06-11 06:59:06 +02:00
ableToRead , _ , _ , _ , messageCipherKey , senderIdentityHash , recipientIdentityHash , messageCreationTimeUnix , messageCommunication , senderCurrentSecretInboxSeed , senderNextSecretInboxSeed , senderDeviceIdentifier , senderLatestChatKeysUpdateTime , err := readMessages . ReadChatMessage ( messageBytes , inboxDoubleSealedKeysSealerKey , myChatDecryptionKeySetsList )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
if ( ableToRead == false ) {
// Either keys are lost/deleted or sender is forming malicious messages
// Skip this message.
//TODO: ReadChatMessage should return 3 bools: outer is malformed, Undecryptable, inner is malformed
// If the inner message is malformed, sender is malicious and we should add the message to the deletedMessagesMapDatastore
currentTime := time . Now ( ) . Unix ( )
currentTimeString := helpers . ConvertInt64ToString ( currentTime )
err := myUndecryptableMessagesMapDatastore . SetMapEntry ( messageHashHex , currentTimeString )
if ( err != nil ) { return err }
continue
}
if ( recipientIdentityHash != myIdentityHash ) {
// Message is sent to my inbox, but not my identity
// Sender is either malicious, or sender is being tricked by another user who is using my chat keys and sending secret inboxes that are my inboxes.
// Either way, ignore message and delete it, never try to import it again.
currentTime := time . Now ( ) . Unix ( )
currentTimeString := helpers . ConvertInt64ToString ( currentTime )
err := myDeletedMessagesMapDatastore . SetMapEntry ( messageHashHex , currentTimeString )
if ( err != nil ) { return err }
continue
}
senderIsBlocked , _ , _ , _ , err := myBlockedUsers . CheckIfUserIsBlocked ( senderIdentityHash )
if ( err != nil ) { return err }
if ( senderIsBlocked == true ) {
// We will not add messages sent from users we have blocked
continue
}
if ( inboxIsSecret == true ) {
// Secret inboxes are reserved for a specific recipient
if ( senderIdentityHash != conversationRecipient ) {
// Sender is either malicious, or sender is being tricked by
// another user who is using my chat keys and sending secret inboxes that are my inboxes.
// Either way, ignore message and delete it, never import it again
currentTime := time . Now ( ) . Unix ( )
currentTimeString := helpers . ConvertInt64ToString ( currentTime )
err := myDeletedMessagesMapDatastore . SetMapEntry ( messageHashHex , currentTimeString )
if ( err != nil ) { return err }
continue
}
}
2024-06-11 06:59:06 +02:00
err = peerChatKeys . SavePeerMessageLatestChatKeysUpdateTime ( senderIdentityHash , networkType , senderLatestChatKeysUpdateTime , messageCreationTimeUnix )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
2024-06-11 06:59:06 +02:00
parametersExist , err = peerSecretInboxes . AddPeerConversationSecretInboxSeeds ( myIdentityHash , senderIdentityHash , messageNetworkType , messageCreationTimeUnix , senderCurrentSecretInboxSeed , senderNextSecretInboxSeed )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
if ( parametersExist == false ) {
// This means we cannot add the sender's secret inboxes to our storage
// This means we will send all messages to their public inbox, reducing privacy
// This should not happen, because we already checked for parameters before starting this
// We will skip all the rest of the messages and try again when we have the parameters
break
}
err = myCipherKeys . SaveMessageCipherKey ( messageHash , messageCipherKey )
if ( err != nil ) { return err }
2024-06-11 06:59:06 +02:00
err = peerDevices . AddPeerDeviceIdentifierFromMessage ( senderIdentityHash , messageNetworkType , senderDeviceIdentifier , messageCreationTimeUnix )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
//TODO: Verify communication. For example, verify that image is valid, greet/reject is valid, etc.
err = myReadStatus . SetConversationReadUnreadStatus ( myIdentityHash , senderIdentityHash , networkType , "Unread" )
if ( err != nil ) { return err }
// We add message to the MyChatMessages map list
2024-06-11 06:59:06 +02:00
messageContentMap , err := getNewMessageMap ( "Sent" , messageHash , [ 20 ] byte { } , myIdentityHash , senderIdentityHash , messageNetworkType , false , messageCreationTimeUnix , messageCommunication )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
newMyChatMessagesMapList = append ( newMyChatMessagesMapList , messageContentMap )
}
if ( len ( newMyChatMessagesMapList ) != 0 ) {
// Some messages were successfully decrypted.
// We add them to our chat messages map list.
allMyChatMessagesMapList := slices . Concat ( myExistingMessagesMapList , newMyChatMessagesMapList )
err = myChatMessagesMapListDatastore . OverwriteMapList ( allMyChatMessagesMapList )
if ( err != nil ) { return err }
}
return nil
}
err := updateMyChatMessages ( )
if ( err != nil ) { return nil , err }
err = mySettings . SetSetting ( myIdentityType + "ChatMessagesReadyStatus" , "Yes" )
if ( err != nil ) { return nil , err }
}
networkTypeString := helpers . ConvertByteToString ( networkType )
lookupMap := map [ string ] string {
"NetworkType" : networkTypeString ,
}
anyExist , updatedMapList , err := myChatMessagesMapListDatastore . GetMapListItems ( lookupMap )
if ( err != nil ) { return nil , err }
if ( anyExist == false ) {
emptyMapList := make ( [ ] map [ string ] string , 0 )
return emptyMapList , nil
}
return updatedMapList , nil
}
func CheckIfUserHasMessagedMe ( theirIdentityHash [ 16 ] byte , networkType byte ) ( bool , error ) {
theirIdentityHashString , userIdentityType , err := identity . EncodeIdentityHashBytesToString ( theirIdentityHash )
if ( err != nil ) {
theirIdentityHashHex := encoding . EncodeBytesToHexString ( theirIdentityHash [ : ] )
return false , errors . New ( "CheckIfUserHasMessagedMe called with invalid theirIdentityHash: " + theirIdentityHashHex )
}
if ( userIdentityType == "Host" ) {
return false , errors . New ( "CheckIfUserHasMessagedMe called with host identity." )
}
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return false , errors . New ( "CheckIfUserHasMessagedMe called with invalid networkType: " + networkTypeString )
}
updateProgressFunction := func ( _ int ) error {
return nil
}
myChatMessagesMapList , err := GetUpdatedMyChatMessagesMapList ( userIdentityType , networkType , updateProgressFunction )
if ( err != nil ) { return false , err }
for _ , messageMap := range myChatMessagesMapList {
messageTheirIdentityHash , exists := messageMap [ "TheirIdentityHash" ]
if ( exists == false ) {
return false , errors . New ( "Malformed chat messages map list: missing theirIdentityHash" )
}
if ( messageTheirIdentityHash != theirIdentityHashString ) {
continue
}
iAmSender , exists := messageMap [ "IAmSender" ]
if ( exists == false ) {
return false , errors . New ( "Malformed myChatMessagesMapList: Missing IAmSender" )
}
if ( iAmSender == "No" ) {
return true , nil
}
}
return false , nil
}
func CheckIfIHaveMessagedUser ( theirIdentityHash [ 16 ] byte , networkType byte ) ( bool , error ) {
theirIdentityHashString , userIdentityType , err := identity . EncodeIdentityHashBytesToString ( theirIdentityHash )
if ( err != nil ) {
theirIdentityHashHex := encoding . EncodeBytesToHexString ( theirIdentityHash [ : ] )
return false , errors . New ( "CheckIfIHaveMessagedUser called with invalid theirIdentityHash: " + theirIdentityHashHex )
}
if ( userIdentityType == "Host" ) {
return false , errors . New ( "CheckIfIHaveMessagedUser called with host identity" )
}
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return false , errors . New ( "CheckIfIHaveMessagedUser called with invalid networkType: " + networkTypeString )
}
updateProgressFunction := func ( _ int ) error {
return nil
}
myChatMessagesMapList , err := GetUpdatedMyChatMessagesMapList ( userIdentityType , networkType , updateProgressFunction )
if ( err != nil ) { return false , err }
for _ , messageMap := range myChatMessagesMapList {
messageTheirIdentityHash , exists := messageMap [ "TheirIdentityHash" ]
if ( exists == false ) {
return false , errors . New ( "Malformed chat messages map list: missing TheirIdentityHash" )
}
if ( messageTheirIdentityHash != theirIdentityHashString ) {
continue
}
iAmSender , exists := messageMap [ "IAmSender" ]
if ( exists == false ) {
return false , errors . New ( "Malformed myChatMessagesMapList: Missing IAmSender" )
}
if ( iAmSender == "Yes" ) {
return true , nil
}
}
return false , nil
}
// This function retrieves the number of messages that exist between the user and another user
// We use this to warn the user before blocking a user about how many messages will be deleted
func GetNumberOfMyConversationMessages ( theirIdentityHash [ 16 ] byte , networkType byte ) ( int , error ) {
theirIdentityHashString , theirIdentityType , err := identity . EncodeIdentityHashBytesToString ( theirIdentityHash )
if ( err != nil ) {
theirIdentityHashHex := encoding . EncodeBytesToHexString ( theirIdentityHash [ : ] )
return 0 , errors . New ( "GetNumberOfMyConversationMessages called with invalid theirIdentityHash: " + theirIdentityHashHex )
}
if ( theirIdentityType == "Host" ) {
return 0 , errors . New ( "GetNumberOfMyConversationMessages called with host identity." )
}
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return 0 , errors . New ( "GetNumberOfMyConversationMessages called with invalid networkType: " + networkTypeString )
}
updateProgressFunction := func ( _ int ) error {
return nil
}
chatMessagesMapList , err := GetUpdatedMyChatMessagesMapList ( theirIdentityType , networkType , updateProgressFunction )
if ( err != nil ) { return 0 , err }
numberOfMessages := 0
for _ , messageMap := range chatMessagesMapList {
currentTheirIdentityHash , exists := messageMap [ "TheirIdentityHash" ]
if ( exists == false ) {
return 0 , errors . New ( "Malformed chat messages map list: missing TheirIdentityHash" )
}
if ( currentTheirIdentityHash == theirIdentityHashString ) {
numberOfMessages += 1
}
}
return numberOfMessages , nil
}
func GetNumberOfMessagesInMyInbox ( myIdentityType string , networkType byte ) ( int , error ) {
identityExists , myIdentityHash , err := myIdentity . GetMyIdentityHash ( myIdentityType )
if ( err != nil ) { return 0 , err }
if ( identityExists == false ) {
return 0 , nil
}
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return 0 , errors . New ( "GetNumberOfMessagesInMyInbox called with invalid networkType: " + networkTypeString )
}
updateProgressFunction := func ( _ int ) error {
return nil
}
myChatMessagesMapList , err := GetUpdatedMyChatMessagesMapList ( myIdentityType , networkType , updateProgressFunction )
if ( err != nil ) { return 0 , err }
numberOfInboxMessages := 0
for _ , messageMap := range myChatMessagesMapList {
messageMyIdentityHashString , exists := messageMap [ "MyIdentityHash" ]
if ( exists == false ) {
return 0 , errors . New ( "Malformed my chat messages map list: Item missing MyIdentityHash" )
}
messageMyIdentityHash , _ , err := identity . ReadIdentityHashString ( messageMyIdentityHashString )
if ( err != nil ) {
return 0 , errors . New ( "Malformed myChatMessages map list: Item contains invalid MyIdentityHash: " + messageMyIdentityHashString )
}
if ( messageMyIdentityHash != myIdentityHash ) {
return 0 , errors . New ( "Malformed myChatMessages map list: Contains message that does not belong to my identity." )
}
iAmSender , exists := messageMap [ "IAmSender" ]
if ( exists == false ) {
return 0 , errors . New ( "Malformed myChatMessagesMapList: Item missing IAmSender" )
}
if ( iAmSender == "No" ) {
numberOfInboxMessages += 1
}
}
return numberOfInboxMessages , nil
}
func CheckIfMessageIsDeleted ( messageHash [ 26 ] byte ) ( bool , error ) {
messageHashHex := encoding . EncodeBytesToHexString ( messageHash [ : ] )
hasBeenDeleted , _ , err := myDeletedMessagesMapDatastore . GetMapEntry ( messageHashHex )
if ( err != nil ) { return false , err }
return hasBeenDeleted , nil
}
func CheckIfMessageIsUndecryptable ( messageHash [ 26 ] byte ) ( bool , error ) {
messageHashHex := encoding . EncodeBytesToHexString ( messageHash [ : ] )
isUndecryptable , _ , err := myUndecryptableMessagesMapDatastore . GetMapEntry ( messageHashHex )
if ( err != nil ) { return false , err }
return isUndecryptable , nil
}
func CheckIfMessageIsImported ( messageHash [ 26 ] byte , identityType string , messageNetworkType byte ) ( bool , error ) {
if ( identityType != "Mate" && identityType != "Moderator" ) {
return false , errors . New ( "CheckIfMessageIsImported called with invalid identityType: " + identityType )
}
isValid := helpers . VerifyNetworkType ( messageNetworkType )
if ( isValid == false ) {
messageNetworkTypeString := helpers . ConvertByteToString ( messageNetworkType )
return false , errors . New ( "CheckIfMessageIsImported called with invalid messageNetworkType: " + messageNetworkTypeString )
}
messageHashHex := encoding . EncodeBytesToHexString ( messageHash [ : ] )
updateProgressFunction := func ( _ int ) error {
return nil
}
myChatMessagesMapList , err := GetUpdatedMyChatMessagesMapList ( identityType , messageNetworkType , updateProgressFunction )
if ( err != nil ) { return false , err }
for _ , messageMap := range myChatMessagesMapList {
currentMessageHash , exists := messageMap [ "MessageHash" ]
if ( exists == true ) {
if ( messageHashHex == currentMessageHash ) {
return true , nil
}
}
}
return false , nil
}
// We use this type to represent messages returned from the GetMyConversationInfoAndSortedMessagesList function
type ConversationMessage struct {
// "Queued"/"Sent"/"Failed"
MessageStatus string
// Sent == Message has been constructed and is in broadcast queue/has been broadcast
// If message is sent, then MessageHash exists.
MessageIsSent bool
// If MessageHash exists, then MessageIdentifier is empty
// If MessageIdentifier exists, then MessageHash is empty
MessageHash [ 26 ] byte
MessageIdentifier [ 20 ] byte
2024-06-11 06:59:06 +02:00
// Unix time of message creation time
CreationTime int64
2024-04-11 15:51:56 +02:00
// True if I am message sender, false if not.
IAmSender bool
// The contents of the message communication
Communication string
}
//This function returns all messages for a conversation and metadata about the conversation
//Inputs:
// -[16]byte: My Identity Hash
// -[16]byte: Their Identity Hash
// -byte: Network type of conversation
//Outputs:
// -bool: My identity found
// -bool: Conversation found (any messages found = true, no messages found = false)
2024-06-11 06:59:06 +02:00
// -[]ConversationMessage: Messages list (sorted by creation time)
// -int64: Conversation latest message creation time
2024-04-11 15:51:56 +02:00
// -bool: I have contacted them
// -bool: I have rejected them (if myIdentityType == Mate)
// -int64: Time of my most recent greet/rejection to them
// -bool: They have contacted me
// -bool: They have rejected me (if myIdentityType == Mate)
// -int64: Time of their most recent greet/rejection to me
// -error
func GetMyConversationInfoAndSortedMessagesList ( myIdentityHash [ 16 ] byte , theirIdentityHash [ 16 ] byte , networkType byte ) ( bool , bool , [ ] ConversationMessage , int64 , bool , bool , int64 , bool , bool , int64 , error ) {
myIdentityFound , myIdentityType , err := myIdentity . CheckIfIdentityHashIsMine ( myIdentityHash )
if ( err != nil ) { return false , false , nil , 0 , false , false , 0 , false , false , 0 , err }
if ( myIdentityFound == false ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , nil
}
theirIdentityType , err := identity . GetIdentityTypeFromIdentityHash ( theirIdentityHash )
if ( err != nil ) {
theirIdentityHashHex := encoding . EncodeBytesToHexString ( theirIdentityHash [ : ] )
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "GetMyConversationInfoAndSortedMessagesList called with invalid theirIdentityHash: " + theirIdentityHashHex )
}
if ( theirIdentityType != myIdentityType ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "GetMyConversationInfoAndSortedMessagesList called with mismatched identityType identities." )
}
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "GetMyConversationInfoAndSortedMessagesList called with invalid networkType: " + networkTypeString )
}
updateProgressFunction := func ( _ int ) error {
return nil
}
myChatMessagesList , err := GetUpdatedMyChatMessagesMapList ( myIdentityType , networkType , updateProgressFunction )
if ( err != nil ) { return false , false , nil , 0 , false , false , 0 , false , false , 0 , err }
if ( len ( myChatMessagesList ) == 0 ) {
return true , false , nil , 0 , false , false , 0 , false , false , 0 , nil
}
conversationMessagesList := make ( [ ] ConversationMessage , 0 )
2024-06-11 06:59:06 +02:00
lastMessageCreationTimeUnix := int64 ( 0 )
2024-04-11 15:51:56 +02:00
for _ , messageMap := range myChatMessagesList {
messageMyIdentityHashString , exists := messageMap [ "MyIdentityHash" ]
if ( exists == false ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Missing MyIdentityHash" )
}
messageTheirIdentityHashString , exists := messageMap [ "TheirIdentityHash" ]
if ( exists == false ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Missing TheirIdentityHash" )
}
messageMyIdentityHash , _ , err := identity . ReadIdentityHashString ( messageMyIdentityHashString )
if ( err != nil ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Contains invalid MyIdentityHash: " + messageMyIdentityHashString )
}
messageTheirIdentityHash , _ , err := identity . ReadIdentityHashString ( messageTheirIdentityHashString )
if ( err != nil ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Contains invalid TheirIdentityHash: " + messageTheirIdentityHashString )
}
if ( messageMyIdentityHash != myIdentityHash ) {
// This must be a message from an earlier identity which we deleted
// This should not happen, because all of our identity's messages should be deleted when we delete our identity
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Contains message for identity which is not our current identity." )
}
if ( messageTheirIdentityHash != theirIdentityHash ) {
continue
}
messageStatus , exists := messageMap [ "MessageStatus" ]
if ( exists == false ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Missing MessageStatus." )
}
iAmSenderString , exists := messageMap [ "IAmSender" ]
if ( exists == false ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Missing IAmSender." )
}
2024-06-11 06:59:06 +02:00
creationTimeUnixString , exists := messageMap [ "CreationTime" ]
2024-04-11 15:51:56 +02:00
if ( exists == false ) {
2024-06-11 06:59:06 +02:00
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Missing CreationTime." )
2024-04-11 15:51:56 +02:00
}
communicationString , exists := messageMap [ "Communication" ]
if ( exists == false ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Missing Communication" )
}
iAmSender , err := helpers . ConvertYesOrNoStringToBool ( iAmSenderString )
if ( err != nil ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Contains invalid IAmSender: " + iAmSenderString )
}
2024-06-11 06:59:06 +02:00
creationTimeUnix , err := helpers . ConvertStringToInt64 ( creationTimeUnixString )
2024-04-11 15:51:56 +02:00
if ( err != nil ) {
2024-06-11 06:59:06 +02:00
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Contains invalid CreationTime: " + creationTimeUnixString )
2024-04-11 15:51:56 +02:00
}
2024-06-11 06:59:06 +02:00
if ( lastMessageCreationTimeUnix < creationTimeUnix ) {
lastMessageCreationTimeUnix = creationTimeUnix
2024-04-11 15:51:56 +02:00
}
newMessageObject := ConversationMessage { }
newMessageObject . MessageStatus = messageStatus
if ( messageStatus == "Sent" ) {
newMessageObject . MessageIsSent = true
messageHashString , exists := messageMap [ "MessageHash" ]
if ( exists == false ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Missing MessageHash." )
}
messageHashArray , err := readMessages . ReadMessageHashHex ( messageHashString )
if ( err != nil ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Contains invalid MessageHash: " + messageHashString )
}
newMessageObject . MessageHash = messageHashArray
} else {
newMessageObject . MessageIsSent = false
messageIdentifierString , exists := messageMap [ "MessageIdentifier" ]
if ( exists == false ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Missing MessageIdentifier." )
}
messageIdentifierBytes , err := encoding . DecodeHexStringToBytes ( messageIdentifierString )
if ( err != nil ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Contains invalid MessageIdentifier: Not Hex: " + messageIdentifierString )
}
if ( len ( messageIdentifierBytes ) != 20 ) {
return false , false , nil , 0 , false , false , 0 , false , false , 0 , errors . New ( "Malformed message map: Contains invalid MessageIdentifier: Invalid length: " + messageIdentifierString )
}
messageIdentifierArray := [ 20 ] byte ( messageIdentifierBytes )
newMessageObject . MessageIdentifier = messageIdentifierArray
}
2024-06-11 06:59:06 +02:00
newMessageObject . CreationTime = creationTimeUnix
2024-04-11 15:51:56 +02:00
newMessageObject . IAmSender = iAmSender
newMessageObject . Communication = communicationString
conversationMessagesList = append ( conversationMessagesList , newMessageObject )
}
if ( len ( conversationMessagesList ) == 0 ) {
return true , false , nil , 0 , false , false , 0 , false , false , 0 , nil
}
// Now we sort messages oldest to newest
2024-06-11 06:59:06 +02:00
compareMessagesFunction := func ( message1 ConversationMessage , message2 ConversationMessage ) int {
2024-04-11 15:51:56 +02:00
2024-06-11 06:59:06 +02:00
message1CreationTime := message1 . CreationTime
message2CreationTime := message2 . CreationTime
2024-04-11 15:51:56 +02:00
2024-06-11 06:59:06 +02:00
if ( message1CreationTime == message2CreationTime ) {
2024-04-11 15:51:56 +02:00
// We sort messages so they always appear in the same order
2024-06-11 06:59:06 +02:00
message1IAmSender := message1 . IAmSender
message2IAmSender := message2 . IAmSender
2024-04-11 15:51:56 +02:00
2024-06-11 06:59:06 +02:00
if ( message1IAmSender == message2IAmSender ) {
2024-04-11 15:51:56 +02:00
2024-06-11 06:59:06 +02:00
message1Communication := message1 . Communication
message2Communication := message2 . Communication
2024-04-11 15:51:56 +02:00
2024-06-11 06:59:06 +02:00
if ( message1Communication == message2Communication ) {
2024-04-11 15:51:56 +02:00
return 0
}
2024-06-11 06:59:06 +02:00
if ( message1Communication < message2Communication ) {
2024-04-11 15:51:56 +02:00
return - 1
}
return 1
}
2024-06-11 06:59:06 +02:00
if ( message1IAmSender == false ) {
2024-04-11 15:51:56 +02:00
return - 1
}
return 1
}
2024-06-11 06:59:06 +02:00
if ( message1CreationTime < message2CreationTime ) {
2024-04-11 15:51:56 +02:00
return - 1
}
return 1
}
slices . SortFunc ( conversationMessagesList , compareMessagesFunction )
// Now we get conversation greet or reject status
// A user has greeted another user if they have sent them any message. It does not have to be a Greet message
// A rejection can only be undone by a greet
// Any messages sent after a rejection do not cancel the rejection, unless they are Greet messages
// Moderators cannot send greet/reject messages. TODO: Deal with this reality.
iHaveContactedThem := false
iHaveRejectedThem := false
timeOfMyMostRecentGreetOrReject := int64 ( 0 )
theyHaveContactedMe := false
theyHaveRejectedMe := false
timeOfTheirMostRecentGreetOrReject := int64 ( 0 )
// We iterate through list from oldest to newest message
for _ , currentMessageObject := range conversationMessagesList {
iAmSender := currentMessageObject . IAmSender
if ( iAmSender == true ) {
iHaveContactedThem = true
} else {
theyHaveContactedMe = true
}
2024-06-11 06:59:06 +02:00
messageCreationTime := currentMessageObject . CreationTime
2024-04-11 15:51:56 +02:00
messageCommunication := currentMessageObject . Communication
if ( messageCommunication == ">!>Greet" ) {
if ( iAmSender == true ) {
iHaveRejectedThem = false
2024-06-11 06:59:06 +02:00
timeOfMyMostRecentGreetOrReject = messageCreationTime
2024-04-11 15:51:56 +02:00
} else {
theyHaveRejectedMe = false
2024-06-11 06:59:06 +02:00
timeOfTheirMostRecentGreetOrReject = messageCreationTime
2024-04-11 15:51:56 +02:00
}
} else if ( messageCommunication == ">!>Reject" ) {
if ( iAmSender == true ) {
iHaveRejectedThem = true
2024-06-11 06:59:06 +02:00
timeOfMyMostRecentGreetOrReject = messageCreationTime
2024-04-11 15:51:56 +02:00
} else {
theyHaveRejectedMe = true
2024-06-11 06:59:06 +02:00
timeOfTheirMostRecentGreetOrReject = messageCreationTime
2024-04-11 15:51:56 +02:00
}
} else {
if ( iAmSender == true ) {
if ( iHaveRejectedThem == false ) {
2024-06-11 06:59:06 +02:00
timeOfMyMostRecentGreetOrReject = messageCreationTime
2024-04-11 15:51:56 +02:00
}
} else {
if ( theyHaveRejectedMe == false ) {
2024-06-11 06:59:06 +02:00
timeOfTheirMostRecentGreetOrReject = messageCreationTime
2024-04-11 15:51:56 +02:00
}
}
}
}
2024-06-11 06:59:06 +02:00
return true , true , conversationMessagesList , lastMessageCreationTimeUnix , iHaveContactedThem , iHaveRejectedThem , timeOfMyMostRecentGreetOrReject , theyHaveContactedMe , theyHaveRejectedMe , timeOfTheirMostRecentGreetOrReject , nil
2024-04-11 15:51:56 +02:00
}
func DeleteAllPeerConversationMessages ( theirIdentityHash [ 16 ] byte ) error {
theirIdentityHashString , theirIdentityType , err := identity . EncodeIdentityHashBytesToString ( theirIdentityHash )
if ( err != nil ) {
theirIdentityHashHex := encoding . EncodeBytesToHexString ( theirIdentityHash [ : ] )
return errors . New ( "DeleteAllPeerConversationMessages called with invalid theirIdentityHash: " + theirIdentityHashHex )
}
if ( theirIdentityType == "Host" ) {
return errors . New ( "DeleteAllPeerConversationMessages called with host identity." )
}
myIdentityExists , myIdentityHash , err := myIdentity . GetMyIdentityHash ( theirIdentityType )
if ( err != nil ) { return err }
if ( myIdentityExists == false ) {
// No messages should exist from this user.
return nil
}
updatingChatMessagesMutex . Lock ( )
defer updatingChatMessagesMutex . Unlock ( )
myChatMessagesMapListDatastore , err := getMyChatMessagesMapListDatastore ( theirIdentityType )
if ( err != nil ) { return err }
myChatMessagesMapList , err := myChatMessagesMapListDatastore . GetMapList ( )
if ( err != nil ) { return err }
newMyChatMessagesMapList := make ( [ ] map [ string ] string , 0 )
for _ , messageMap := range myChatMessagesMapList {
messageMyIdentityHashString , exists := messageMap [ "MyIdentityHash" ]
if ( exists == false ) {
return errors . New ( "Malformed chat messages map list: missing MyIdentityHash" )
}
messageMyIdentityHash , _ , err := identity . ReadIdentityHashString ( messageMyIdentityHashString )
if ( err != nil ) {
return errors . New ( "Malformed chat messages map list: Contains invalid MyIdentityHash: " + messageMyIdentityHashString )
}
if ( messageMyIdentityHash != myIdentityHash ) {
// A message exists that does not belong to our identity
// This should not happen, because when we delete our identity, we delete all of our chat messages
return errors . New ( "MyChatMessagesMapList contains message with identity that is not ours." )
}
messageTheirIdentityHash , exists := messageMap [ "TheirIdentityHash" ]
if ( exists == false ) {
return errors . New ( "Malformed myChatMessagesMapList: Item missing TheirIdentityHash" )
}
if ( messageTheirIdentityHash != theirIdentityHashString ) {
newMyChatMessagesMapList = append ( newMyChatMessagesMapList , messageMap )
}
}
err = myChatMessagesMapListDatastore . OverwriteMapList ( newMyChatMessagesMapList )
if ( err != nil ) { return err }
return nil
}
//Outputs:
// -bool: My identity exists
// -[][16]byte: List of all chat recipients that the user has not blocked
// -error
func GetAllMyNonBlockedChatRecipients ( myIdentityType string , networkType byte ) ( bool , [ ] [ 16 ] byte , error ) {
if ( myIdentityType != "Mate" && myIdentityType != "Moderator" ) {
return false , nil , errors . New ( "GetAllMyNonBlockedChatRecipients called with invalid identity type: " + myIdentityType )
}
identityExists , myIdentityHash , err := myIdentity . GetMyIdentityHash ( myIdentityType )
if ( err != nil ) { return false , nil , err }
if ( identityExists == false ) {
return false , nil , nil
}
updateProgressFunction := func ( _ int ) error {
return nil
}
myChatMessagesMapList , err := GetUpdatedMyChatMessagesMapList ( myIdentityType , networkType , updateProgressFunction )
if ( err != nil ) { return false , nil , err }
if ( len ( myChatMessagesMapList ) == 0 ) {
emptyList := make ( [ ] [ 16 ] byte , 0 )
return true , emptyList , nil
}
// We use a map to avoid duplicates
// Map Structure: Recipient Identity Hash -> Nothing
chatRecipientsMap := make ( map [ [ 16 ] byte ] struct { } )
for _ , messageMap := range myChatMessagesMapList {
messageMyIdentityHashString , exists := messageMap [ "MyIdentityHash" ]
if ( exists == false ) {
return false , nil , errors . New ( "Malformed myChatMessagesMapList: Item missing MyIdentityHash" )
}
messageMyIdentityHash , _ , err := identity . ReadIdentityHashString ( messageMyIdentityHashString )
if ( err != nil ) {
return false , nil , errors . New ( "Malformed myChatMessagesMapList: Item contains invalid MyIdentityHash: " + messageMyIdentityHashString )
}
if ( messageMyIdentityHash != myIdentityHash ) {
return false , nil , errors . New ( "myChatMessagesMapList contains message that does not belong to my identity." )
}
messageTheirIdentityHashString , exists := messageMap [ "TheirIdentityHash" ]
if ( exists == false ) {
return false , nil , errors . New ( "Malformed myChatMessagesMapList: Item missing TheirIdentityHash" )
}
messageTheirIdentityHash , _ , err := identity . ReadIdentityHashString ( messageTheirIdentityHashString )
if ( err != nil ) {
return false , nil , errors . New ( "Malformed myChatMessagesMapList: Item contains invalid TheirIdentityHash: " + messageTheirIdentityHashString )
}
chatRecipientsMap [ messageTheirIdentityHash ] = struct { } { }
}
nonBlockedChatRecipientsList := make ( [ ] [ 16 ] byte , 0 )
for userIdentityHash , _ := range chatRecipientsMap {
isBlocked , _ , _ , _ , err := myBlockedUsers . CheckIfUserIsBlocked ( userIdentityHash )
if ( err != nil ) { return false , nil , err }
if ( isBlocked == false ) {
nonBlockedChatRecipientsList = append ( nonBlockedChatRecipientsList , userIdentityHash )
}
}
return true , nonBlockedChatRecipientsList , nil
}
// This function adds a new user sent/failed/queued message to myChatMessages map list where user is sender
// We also remove the queued message map if message is sent/failed
func AddMyNewMessageToMyChatMessages (
messageStatus string ,
messageHash [ 26 ] byte ,
messageIdentifier [ 20 ] byte ,
myIdentityHash [ 16 ] byte ,
theirIdentityHash [ 16 ] byte ,
messageNetworkType byte ,
2024-06-11 06:59:06 +02:00
messageCreationTime int64 ,
2024-04-11 15:51:56 +02:00
communication string ) error {
identityIsMine , myIdentityType , err := myIdentity . CheckIfIdentityHashIsMine ( myIdentityHash )
if ( err != nil ) { return err }
if ( identityIsMine == false ) {
return errors . New ( "AddMyNewMessageToMyChatMessages called with identity that is not mine." )
}
if ( myIdentityType != "Mate" && myIdentityType != "Moderator" ) {
return errors . New ( "AddMyNewMessageToMyChatMessages called with host identity." )
}
2024-06-11 06:59:06 +02:00
newMessageMap , err := getNewMessageMap ( messageStatus , messageHash , messageIdentifier , myIdentityHash , theirIdentityHash , messageNetworkType , true , messageCreationTime , communication )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
updatingChatMessagesMutex . Lock ( )
defer updatingChatMessagesMutex . Unlock ( )
myChatMessagesMapListDatastore , err := getMyChatMessagesMapListDatastore ( myIdentityType )
if ( err != nil ) { return err }
existingMessagesMapList , err := myChatMessagesMapListDatastore . GetMapList ( )
if ( err != nil ) { return err }
getNewChatMessagesMapList := func ( ) ( [ ] map [ string ] string , error ) {
if ( messageStatus == "Queued" ) {
newChatMessagesMapList := append ( existingMessagesMapList , newMessageMap )
return newChatMessagesMapList , nil
}
// Chat message status is either Sent or Failed
// This means it will replace an existing queued message
// We remove that message from the existing map list
newChatMessagesMapList := make ( [ ] map [ string ] string , 0 )
for _ , messageMap := range existingMessagesMapList {
currentMessageIdentifierString , exists := messageMap [ "MessageIdentifier" ]
if ( exists == false ) {
return nil , errors . New ( "MyChatMessages map list item missing MessageIdentifier" )
}
currentMessageIdentifier , err := encoding . DecodeHexStringToBytes ( currentMessageIdentifierString )
if ( err != nil ) {
return nil , errors . New ( "MyChatMessages map list item contains invalid MessageIdentifier: " + currentMessageIdentifierString )
}
if ( len ( currentMessageIdentifier ) != 20 ) {
return nil , errors . New ( "MyChatMessages map list item contains invalid MessageIdentifier: " + currentMessageIdentifierString )
}
areEqual := bytes . Equal ( currentMessageIdentifier , messageIdentifier [ : ] )
if ( areEqual == false ) {
newChatMessagesMapList = append ( newChatMessagesMapList , messageMap )
continue
}
currentMessageStatus , exists := messageMap [ "MessageStatus" ]
if ( exists == false ) {
return nil , errors . New ( "MyChatMessages map list item missing MessageStatus" )
}
if ( currentMessageStatus != "Queued" ) {
return nil , errors . New ( "AddMyNewMessageToMyChatMessages called to replace existing message that is not queued: " + currentMessageStatus )
}
// This is reached if the message is the one which we want to remove
// We do not add the message
}
newChatMessagesMapList = append ( newChatMessagesMapList , newMessageMap )
return newChatMessagesMapList , nil
}
newChatMessagesMapList , err := getNewChatMessagesMapList ( )
if ( err != nil ) { return err }
err = myChatMessagesMapListDatastore . OverwriteMapList ( newChatMessagesMapList )
if ( err != nil ) { return err }
return nil
}
// MessageHash should be empty ("") if messageStatus == "Queued" or "Failed"
// messageIdentifier should be empty if messageStatus == "Sent"
2024-06-11 06:59:06 +02:00
func getNewMessageMap ( messageStatus string , messageHash [ 26 ] byte , messageIdentifier [ 20 ] byte , myIdentityHash [ 16 ] byte , theirIdentityHash [ 16 ] byte , messageNetworkType byte , iAmSender bool , messageCreationTime int64 , communication string ) ( map [ string ] string , error ) {
2024-04-11 15:51:56 +02:00
if ( messageStatus != "Sent" && messageStatus != "Queued" && messageStatus != "Failed" ) {
return nil , errors . New ( "getNewMessageMap called with invalid messageStatus: " + messageStatus )
}
myIdentityHashString , myIdentityType , err := identity . EncodeIdentityHashBytesToString ( myIdentityHash )
if ( err != nil ) {
myIdentityHashString := encoding . EncodeBytesToHexString ( myIdentityHash [ : ] )
return nil , errors . New ( "getNewMessageMap called with invalid my identity hash: " + myIdentityHashString )
}
theirIdentityHashString , theirIdentityType , err := identity . EncodeIdentityHashBytesToString ( theirIdentityHash )
if ( err != nil ) {
theirIdentityHashString := encoding . EncodeBytesToHexString ( theirIdentityHash [ : ] )
return nil , errors . New ( "getNewMessageMap called with invalid theirIdentityHash: " + theirIdentityHashString )
}
if ( myIdentityType != theirIdentityType ) {
return nil , errors . New ( "Trying to create message content map between different identityTypes" )
}
if ( myIdentityType == "Host" ) {
return nil , errors . New ( "Trying to create message content map from Host identityType" )
}
isValid := helpers . VerifyNetworkType ( messageNetworkType )
if ( isValid == false ) {
messageNetworkTypeString := helpers . ConvertByteToString ( messageNetworkType )
return nil , errors . New ( "getNewMessageMap called with invalid messageNetworkType: " + messageNetworkTypeString )
}
messageNetworkTypeString := helpers . ConvertByteToString ( messageNetworkType )
iAmSenderString := helpers . ConvertBoolToYesOrNoString ( iAmSender )
2024-06-11 06:59:06 +02:00
if ( messageCreationTime > time . Now ( ) . Unix ( ) + 10 ) {
return nil , errors . New ( "getNewMessageMap called with invalid creationTime: is in the future." )
2024-04-11 15:51:56 +02:00
}
isAllowed := allowedText . VerifyStringIsAllowed ( communication )
if ( isAllowed == false ) {
return nil , errors . New ( "getNewMessageMap called with communication containing unallowed text." )
}
2024-06-11 06:59:06 +02:00
messageCreationTimeString := helpers . ConvertInt64ToString ( messageCreationTime )
2024-04-11 15:51:56 +02:00
messageContentMap := map [ string ] string {
"MessageStatus" : messageStatus ,
"MyIdentityHash" : myIdentityHashString ,
"TheirIdentityHash" : theirIdentityHashString ,
"NetworkType" : messageNetworkTypeString ,
"IAmSender" : iAmSenderString ,
2024-06-11 06:59:06 +02:00
"CreationTime" : messageCreationTimeString ,
2024-04-11 15:51:56 +02:00
"Communication" : communication ,
}
if ( messageStatus == "Sent" ) {
messageHashHex := encoding . EncodeBytesToHexString ( messageHash [ : ] )
messageContentMap [ "MessageHash" ] = messageHashHex
} else {
// messageStatus == "Queued" || messageStatus == "Failed"
// Queued messages are messages that are queued to be sent, but are not sent yet
// They are displayed differently in the GUI
// They do not have a messageHash, because the raw encrypted message has not been created yet
// Failed messages are messages for which we were not able to broadcast, so they don't have a message hash either.
messageIdentifierHex := encoding . EncodeBytesToHexString ( messageIdentifier [ : ] )
messageContentMap [ "MessageIdentifier" ] = messageIdentifierHex
}
return messageContentMap , nil
}