// 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 }