1358 lines
49 KiB
Go
1358 lines
49 KiB
Go
|
|
// 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{
|
|
|
|
newPercentageProgress, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 20, 100)
|
|
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
|
|
}
|
|
|
|
ableToRead, _, _, _, messageCipherKey, senderIdentityHash, recipientIdentityHash, messageCreationTimeUnix, messageCommunication, senderCurrentSecretInboxSeed, senderNextSecretInboxSeed, senderDeviceIdentifier, senderLatestChatKeysUpdateTime, err := readMessages.ReadChatMessage(messageBytes, inboxDoubleSealedKeysSealerKey, myChatDecryptionKeySetsList)
|
|
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
|
|
}
|
|
}
|
|
|
|
err = peerChatKeys.SavePeerMessageLatestChatKeysUpdateTime(senderIdentityHash, networkType, senderLatestChatKeysUpdateTime, messageCreationTimeUnix)
|
|
if (err != nil) { return err }
|
|
|
|
parametersExist, err = peerSecretInboxes.AddPeerConversationSecretInboxSeeds(myIdentityHash, senderIdentityHash, messageNetworkType, messageCreationTimeUnix, senderCurrentSecretInboxSeed, senderNextSecretInboxSeed)
|
|
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 }
|
|
|
|
err = peerDevices.AddPeerDeviceIdentifierFromMessage(senderIdentityHash, messageNetworkType, senderDeviceIdentifier, messageCreationTimeUnix)
|
|
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
|
|
|
|
messageContentMap, err := getNewMessageMap("Sent", messageHash, [20]byte{}, myIdentityHash, senderIdentityHash, messageNetworkType, false, messageCreationTimeUnix, messageCommunication)
|
|
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
|
|
|
|
// Unix time of message creation time
|
|
CreationTime int64
|
|
|
|
// 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)
|
|
// -[]ConversationMessage: Messages list (sorted by creation time)
|
|
// -int64: Conversation latest message creation time
|
|
// -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)
|
|
|
|
lastMessageCreationTimeUnix := int64(0)
|
|
|
|
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.")
|
|
}
|
|
|
|
creationTimeUnixString, exists := messageMap["CreationTime"]
|
|
if (exists == false) {
|
|
return false, false, nil, 0, false, false, 0, false, false, 0, errors.New("Malformed message map: Missing CreationTime.")
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
creationTimeUnix, err := helpers.ConvertStringToInt64(creationTimeUnixString)
|
|
if (err != nil) {
|
|
return false, false, nil, 0, false, false, 0, false, false, 0, errors.New("Malformed message map: Contains invalid CreationTime: " + creationTimeUnixString)
|
|
}
|
|
|
|
if (lastMessageCreationTimeUnix < creationTimeUnix){
|
|
lastMessageCreationTimeUnix = creationTimeUnix
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
newMessageObject.CreationTime = creationTimeUnix
|
|
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
|
|
|
|
compareMessagesFunction := func(message1 ConversationMessage, message2 ConversationMessage)int {
|
|
|
|
message1CreationTime := message1.CreationTime
|
|
message2CreationTime := message2.CreationTime
|
|
|
|
if (message1CreationTime == message2CreationTime){
|
|
|
|
// We sort messages so they always appear in the same order
|
|
|
|
message1IAmSender := message1.IAmSender
|
|
message2IAmSender := message2.IAmSender
|
|
|
|
if (message1IAmSender == message2IAmSender){
|
|
|
|
message1Communication := message1.Communication
|
|
message2Communication := message2.Communication
|
|
|
|
if (message1Communication == message2Communication){
|
|
return 0
|
|
}
|
|
if (message1Communication < message2Communication){
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
if (message1IAmSender == false){
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
if (message1CreationTime < message2CreationTime){
|
|
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
|
|
}
|
|
|
|
messageCreationTime := currentMessageObject.CreationTime
|
|
messageCommunication := currentMessageObject.Communication
|
|
|
|
if (messageCommunication == ">!>Greet"){
|
|
if (iAmSender == true){
|
|
iHaveRejectedThem = false
|
|
timeOfMyMostRecentGreetOrReject = messageCreationTime
|
|
} else {
|
|
theyHaveRejectedMe = false
|
|
timeOfTheirMostRecentGreetOrReject = messageCreationTime
|
|
}
|
|
} else if (messageCommunication == ">!>Reject"){
|
|
if (iAmSender == true){
|
|
iHaveRejectedThem = true
|
|
timeOfMyMostRecentGreetOrReject = messageCreationTime
|
|
} else {
|
|
theyHaveRejectedMe = true
|
|
timeOfTheirMostRecentGreetOrReject = messageCreationTime
|
|
}
|
|
} else {
|
|
if (iAmSender == true){
|
|
if (iHaveRejectedThem == false){
|
|
timeOfMyMostRecentGreetOrReject = messageCreationTime
|
|
}
|
|
} else{
|
|
if (theyHaveRejectedMe == false){
|
|
timeOfTheirMostRecentGreetOrReject = messageCreationTime
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true, true, conversationMessagesList, lastMessageCreationTimeUnix, iHaveContactedThem, iHaveRejectedThem, timeOfMyMostRecentGreetOrReject, theyHaveContactedMe, theyHaveRejectedMe, timeOfTheirMostRecentGreetOrReject, nil
|
|
}
|
|
|
|
|
|
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,
|
|
messageCreationTime int64,
|
|
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.")
|
|
}
|
|
|
|
newMessageMap, err := getNewMessageMap(messageStatus, messageHash, messageIdentifier, myIdentityHash, theirIdentityHash, messageNetworkType, true, messageCreationTime, communication)
|
|
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"
|
|
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){
|
|
|
|
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)
|
|
|
|
if (messageCreationTime > time.Now().Unix() + 10){
|
|
return nil, errors.New("getNewMessageMap called with invalid creationTime: is in the future.")
|
|
}
|
|
|
|
isAllowed := allowedText.VerifyStringIsAllowed(communication)
|
|
if (isAllowed == false){
|
|
return nil, errors.New("getNewMessageMap called with communication containing unallowed text.")
|
|
}
|
|
|
|
messageCreationTimeString := helpers.ConvertInt64ToString(messageCreationTime)
|
|
|
|
messageContentMap := map[string]string{
|
|
"MessageStatus": messageStatus,
|
|
"MyIdentityHash": myIdentityHashString,
|
|
"TheirIdentityHash": theirIdentityHashString,
|
|
"NetworkType": messageNetworkTypeString,
|
|
"IAmSender": iAmSenderString,
|
|
"CreationTime": messageCreationTimeString,
|
|
"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
|
|
}
|
|
|
|
|