2024-04-11 15:51:56 +02:00
// myMessageQueue provides functions to add messages we want to send to the message queue
// All messages are added to the queue before being sent
// This is useful for 2 reasons.
// 1. If the account credit server is down/unreachable, we can automatically retry funding the message later
// 2. We can queue messages to send to users whose chat keys we do not yet have (and may not exist on the network at all)
package myMessageQueue
// For users whose keys are missing, we will wait for their chat keys to download in the background
// If we can't download their chat keys after trying for a certain time, we will discard the message
//TODO: If we disable our profile or delete our identity, clear our chat message queue
//TODO: A way to view the message queue through the GUI
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/messaging/myChatMessages"
import "seekia/internal/messaging/peerChatKeys"
import "seekia/internal/messaging/sendMessages"
import "seekia/internal/myDatastores/myMapList"
import "seekia/internal/myIdentity"
import "seekia/internal/network/myAccountCredit"
import "seekia/internal/parameters/getParameters"
import "seekia/internal/unixTime"
import "errors"
import "time"
import "sync"
// This will be locked whenever we are updating the message queue map list
var updatingMessageQueueMapListMutex sync . Mutex
var myMessageQueueMapListDatastore * myMapList . MyMapList
// This function must be called whenever an app user signs in
func InitializeMyMessageQueueDatastore ( ) error {
updatingMessageQueueMapListMutex . Lock ( )
defer updatingMessageQueueMapListMutex . Unlock ( )
newMyMessageQueueMapListDatastore , err := myMapList . CreateNewMapList ( "MyMessageQueue" )
if ( err != nil ) { return err }
myMessageQueueMapListDatastore = newMyMessageQueueMapListDatastore
return nil
}
//Outputs:
// -bool: Parameters exist
// -bool: Sufficient credits exist
// -error
func AddMessageToMyMessageQueue ( myIdentityHash [ 16 ] byte , recipientIdentityHash [ 16 ] byte , messageNetworkType byte , messageDuration int , messageCommunication string ) ( bool , bool , error ) {
// We keep track of our identity to make sure if we don't clear the queue properly between changing user/importing new identity, messages will not be sent by the wrong identity
myIdentityExists , myIdentityType , err := myIdentity . CheckIfIdentityHashIsMine ( myIdentityHash )
if ( err != nil ) { return false , false , err }
if ( myIdentityExists == false ) {
return false , false , errors . New ( "AddMessageToMyMessageQueue called with an unknown identity." )
}
myIdentityHashString , _ , err := identity . EncodeIdentityHashBytesToString ( myIdentityHash )
if ( err != nil ) {
myIdentityHashHex := encoding . EncodeBytesToHexString ( myIdentityHash [ : ] )
return false , false , errors . New ( "CheckIfIdentityHashIsMine returning invalid identity hash: " + myIdentityHashHex )
}
recipientIdentityHashString , recipientIdentityType , err := identity . EncodeIdentityHashBytesToString ( recipientIdentityHash )
if ( err != nil ) {
recipientIdentityHashHex := encoding . EncodeBytesToHexString ( recipientIdentityHash [ : ] )
return false , false , errors . New ( "AddMessageToMyMessageQueue called with invalid recipientIdentityHash: " + recipientIdentityHashHex )
}
if ( myIdentityType != recipientIdentityType ) {
return false , false , errors . New ( "AddMessageToMyMessageQueue called with mismatched my/recipient identityTypes." )
}
isValid := helpers . VerifyNetworkType ( messageNetworkType )
if ( isValid == false ) {
messageNetworkTypeString := helpers . ConvertByteToString ( messageNetworkType )
return false , false , errors . New ( "AddMessageToMyMessageQueue called with invalid messageNetworkType: " + messageNetworkTypeString )
}
messageNetworkTypeString := helpers . ConvertByteToString ( messageNetworkType )
messageDurationString := helpers . ConvertIntToString ( messageDuration )
messageIdentifierBytes , err := helpers . GetNewRandomBytes ( 20 )
if ( err != nil ) { return false , false , err }
messageIdentifier := [ 20 ] byte ( messageIdentifierBytes )
messageIdentifierString := encoding . EncodeBytesToHexString ( messageIdentifierBytes )
2024-06-11 06:59:06 +02:00
// TimeAdded will be used as the messageCreationTime when we send the message
2024-04-11 15:51:56 +02:00
currentTime := time . Now ( ) . Unix ( )
currentTimeString := helpers . ConvertInt64ToString ( currentTime )
newMessageMap := map [ string ] string {
"MessageIdentifier" : messageIdentifierString ,
"MyIdentityHash" : myIdentityHashString ,
"RecipientIdentityHash" : recipientIdentityHashString ,
"NetworkType" : messageNetworkTypeString ,
"MessageCommunication" : messageCommunication ,
"MessageDuration" : messageDurationString ,
"TimeAdded" : currentTimeString ,
}
estimatedMessageSize , err := sendMessages . GetEstimatedMessageSize ( recipientIdentityHash , messageCommunication )
if ( err != nil ) { return false , false , err }
updatingMessageQueueMapListMutex . Lock ( )
defer updatingMessageQueueMapListMutex . Unlock ( )
parametersExist , sufficientCreditsExist , err := myAccountCredit . FreezeCreditForMessage ( messageIdentifier , messageNetworkType , messageDuration , estimatedMessageSize )
if ( err != nil ) { return false , false , err }
if ( parametersExist == false ) {
return false , false , nil
}
if ( sufficientCreditsExist == false ) {
return true , false , nil
}
err = myMessageQueueMapListDatastore . AddMapListItem ( newMessageMap )
if ( err != nil ) { return false , false , err }
// Now we add queued message map to myChatMessages so the user will see a queued message in their GUI
err = myChatMessages . AddMyNewMessageToMyChatMessages ( "Queued" , [ 26 ] byte { } , messageIdentifier , myIdentityHash , recipientIdentityHash , messageNetworkType , currentTime , messageCommunication )
if ( err != nil ) { return false , false , err }
return true , true , nil
}
func PruneMessageQueue ( ) error {
//TODO: Delete messages that have already been sent
// Use sendMessages.CheckIfMessageIsSent()
return nil
}
// This function should be run automatically on a continual period
// It will attempt to send a random selection of messages from the queue
// It will contact 1 host per message
func AttemptToSendMessagesInQueue ( identityType string , networkType byte , maximumMessagesToSend int ) error {
if ( identityType != "Mate" && identityType != "Moderator" ) {
return errors . New ( "AttemptToSendMessagesInQueue called with invalid identityType: " + identityType )
}
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return errors . New ( "AttemptToSendMessagesInQueue called with invalid networkType: " + networkTypeString )
}
myIdentityExists , myIdentityHash , err := myIdentity . GetMyIdentityHash ( identityType )
if ( err != nil ) { return err }
if ( myIdentityExists == false ) {
return nil
}
parametersExist , err := getParameters . CheckIfChatParametersExist ( networkType )
if ( err != nil ) { return err }
if ( parametersExist == false ) {
// We need these parameters to send messages. We wait for them to download.
return nil
}
updatingMessageQueueMapListMutex . Lock ( )
defer updatingMessageQueueMapListMutex . Unlock ( )
currentMessageQueueMapList , err := myMessageQueueMapListDatastore . GetMapList ( )
if ( err != nil ) { return err }
// We randomize the map list order, so we are sending a random subset of the messages
helpers . RandomizeListOrder ( currentMessageQueueMapList )
// We use this to keep track of how many message sends we have succesfully started
sentMessagesCount := 0
messageMapsToDeleteList := make ( [ ] map [ string ] string , 0 )
for _ , messageQueueMessageMap := range currentMessageQueueMapList {
messageMyIdentityHashString , exists := messageQueueMessageMap [ "MyIdentityHash" ]
if ( exists == false ) {
return errors . New ( "MessageQueue mapList malformed: Item missing MyIdentityHash" )
}
messageMyIdentityHash , _ , err := identity . ReadIdentityHashString ( messageMyIdentityHashString )
if ( err != nil ) {
return errors . New ( "MessageQueue mapList malformed: Item contains invalid MyIdentityHash: " + messageMyIdentityHashString )
}
if ( messageMyIdentityHash != myIdentityHash ) {
// Must be identity for different identityType
continue
}
messageNetworkTypeString , exists := messageQueueMessageMap [ "NetworkType" ]
if ( exists == false ) {
return errors . New ( "MessageQueue mapList malformed: Item missing NetworkType." )
}
messageNetworkType , err := helpers . ConvertNetworkTypeStringToByte ( messageNetworkTypeString )
if ( err != nil ) {
return errors . New ( "MessageQueue mapList malformed: Item contains invalid NetworkType: " + messageNetworkTypeString )
}
if ( messageNetworkType != networkType ) {
// Message belongs to a different networkType.
continue
}
messageIdentifierString , exists := messageQueueMessageMap [ "MessageIdentifier" ]
if ( exists == false ) {
return errors . New ( "MessageQueue mapList malformed: Item missing MessageIdentifier" )
}
messageIdentifierBytes , err := encoding . DecodeHexStringToBytes ( messageIdentifierString )
if ( err != nil ) {
return errors . New ( "MessageQueue mapList malformed: Item contains invalid messageIdentifier: Not Hex: " + messageIdentifierString )
}
if ( len ( messageIdentifierBytes ) != 20 ) {
return errors . New ( "MessageQueue mapList malformed: Item contains invalid messageIdentifier: Invalid length: " + messageIdentifierString )
}
messageIdentifier := [ 20 ] byte ( messageIdentifierBytes )
messageRecipientString , exists := messageQueueMessageMap [ "RecipientIdentityHash" ]
if ( exists == false ) {
return errors . New ( "MessageQueue mapList malformed: item missing RecipientIdentityHash" )
}
messageRecipient , _ , err := identity . ReadIdentityHashString ( messageRecipientString )
if ( err != nil ) {
return errors . New ( "MessageQueue mapList malformed: Item contains invalid RecipientIdentityHash: " + messageRecipientString )
}
messageTimeAdded , exists := messageQueueMessageMap [ "TimeAdded" ]
if ( exists == false ) {
return errors . New ( "MessageQueue mapList malformed: Item missing TimeAdded" )
}
2024-06-11 06:59:06 +02:00
messageTimeAddedInt64 , err := helpers . ConvertCreationTimeStringToInt64 ( messageTimeAdded )
2024-04-11 15:51:56 +02:00
if ( err != nil ) {
return errors . New ( "MessageQueue mapList malformed: Item contains invalid TimeAdded: " + messageTimeAdded )
}
messageCommunication , exists := messageQueueMessageMap [ "MessageCommunication" ]
if ( exists == false ) {
return errors . New ( "MessageQueue mapList malformed: item missing MessageCommunication" )
}
// Now we check if recipient keys exist
peerIsDisabled , peerChatKeysExist , _ , _ , err := peerChatKeys . GetPeerNewestActiveChatKeys ( messageRecipient , networkType )
if ( err != nil ) { return err }
if ( peerIsDisabled == true || peerChatKeysExist == false ) {
// We see if message entry is more than 1 week old
// If it is, we will delete it
currentTime := time . Now ( ) . Unix ( )
elapsedTime := currentTime - messageTimeAddedInt64
maximumTime := unixTime . GetWeekUnix ( )
if ( elapsedTime > maximumTime ) {
creditsFound , err := myAccountCredit . ReleaseFrozenCreditForMessage ( messageIdentifier )
if ( err != nil ) { return err }
if ( creditsFound == false ) {
// This should not happen
// If the user manually released credits, the queued message that the credits were held for should have been deleted from queue
return errors . New ( "MessageQueue message frozen credits not found." )
}
// We update the existing message status inside myChatMessages to "Failed"
err = myChatMessages . AddMyNewMessageToMyChatMessages ( "Failed" , [ 26 ] byte { } , messageIdentifier , messageMyIdentityHash , messageRecipient , messageNetworkType , messageTimeAddedInt64 , messageCommunication )
if ( err != nil ) { return err }
messageMapsToDeleteList = append ( messageMapsToDeleteList , messageQueueMessageMap )
}
// Peer chat keys were not found.
// We will keep trying to download them. We skip this message for now.
continue
}
// We initiate message send
messageDurationString , exists := messageQueueMessageMap [ "MessageDuration" ]
if ( exists == false ) {
return errors . New ( "MessageQueue map list malformed: item missing MessageDuration" )
}
messageDurationToFund , err := helpers . ConvertStringToInt ( messageDurationString )
if ( err != nil ) {
return errors . New ( "MessageQueue mapList malformed: Item contains invalid MessageDuration: " + messageDurationString )
}
messageIsPendingOrSent , successfullyStartedSend , err := sendMessages . StartSendingMessage ( messageIdentifier , myIdentityHash , messageRecipient , messageNetworkType , messageTimeAddedInt64 , messageCommunication , messageDurationToFund )
if ( err != nil ) { return err }
if ( messageIsPendingOrSent == true ) {
// We are already trying to send the message, or the message has already been sent.
continue
}
if ( successfullyStartedSend == false ) {
// We don't have the required data
// We are probably missing parameters
// We continue looking for messages to send
continue
}
// We successfully started sending the message.
// It will be deleted from the message queue automatically if the send is successful
sentMessagesCount += 1
if ( sentMessagesCount >= maximumMessagesToSend ) {
// We have sent the number of messages we wanted to
break
}
}
for _ , messageMapToDelete := range messageMapsToDeleteList {
err := myMessageQueueMapListDatastore . DeleteMapListItems ( messageMapToDelete )
if ( err != nil ) { return err }
}
return nil
}