seekia/internal/messaging/myMessageQueue/myMessageQueue.go

350 lines
13 KiB
Go
Raw Normal View History

// 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)
// TimeAdded will be used as the messageSentTime when we send the message
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")
}
messageTimeAddedInt64, err := helpers.ConvertBroadcastTimeStringToInt64(messageTimeAdded)
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
}