seekia/internal/messaging/sendMessages/sendMessages.go

420 lines
16 KiB
Go

// sendMessages provides functions to create and send messages
package sendMessages
//TODO: Add function to prune mySentMessagesMapDatastore
import "seekia/internal/convertCurrencies"
import "seekia/internal/cryptography/kyber"
import "seekia/internal/cryptography/nacl"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/logger"
import "seekia/internal/messaging/createMessages"
import "seekia/internal/messaging/inbox"
import "seekia/internal/messaging/myChatKeys"
import "seekia/internal/messaging/myChatMessages"
import "seekia/internal/messaging/mySecretInboxes"
import "seekia/internal/messaging/peerChatKeys"
import "seekia/internal/messaging/peerSecretInboxes"
import "seekia/internal/messaging/readMessages"
import "seekia/internal/myDatastores/myMap"
import "seekia/internal/myDevice"
import "seekia/internal/myIdentity"
import "seekia/internal/network/myBroadcasts"
import "seekia/internal/network/spendCredit"
import "seekia/internal/parameters/getParameters"
import "seekia/internal/profiles/myProfileStatus"
import "time"
import "errors"
import "sync"
// This mutex will be locked whenever we are adding a new message to send
var addingMessageMutex sync.Mutex
// This list host a map of messages which we have already sent
// Map Structure: Message Identifier -> Message Hash
var mySentMessagesMapDatastore *myMap.MyMap
// This function must be called whenever an app user signs in
func InitializeSentMessagesDatastore()error{
addingMessageMutex.Lock()
defer addingMessageMutex.Unlock()
newMySentMessagesMapDatastore, err := myMap.CreateNewMap("SentMessages")
if (err != nil) { return err }
mySentMessagesMapDatastore = newMySentMessagesMapDatastore
return nil
}
func WaitForPendingSendsToComplete(){
pendingMessagesWaitgroup.Wait()
}
// This waitgroup will be used to wait for all pending messages to be sent
var pendingMessagesWaitgroup sync.WaitGroup
var pendingMessagesMapMutex sync.RWMutex
// This list contains a map of message identifiers which we are currently attempting to send
var pendingMessagesMap map[[20]byte]struct{} = make(map[[20]byte]struct{})
func addMessageToPendingMessagesMap(messageIdentifier [20]byte){
pendingMessagesMapMutex.Lock()
pendingMessagesMap[messageIdentifier] = struct{}{}
pendingMessagesMapMutex.Unlock()
}
func deleteMessageFromPendingMessagesMap(messageIdentifier [20]byte){
pendingMessagesMapMutex.Lock()
delete(pendingMessagesMap, messageIdentifier)
pendingMessagesMapMutex.Unlock()
}
func checkIfMessageIsPending(messageIdentifier [20]byte)bool{
pendingMessagesMapMutex.RLock()
_, exists := pendingMessagesMap[messageIdentifier]
pendingMessagesMapMutex.RUnlock()
if (exists == false){
return false
}
return true
}
// This function will tell us if a message has been successfully sent
//Outputs:
// -bool: Message is sent
// -[26]byte: Message hash
// -error
func CheckIfMessageIsSent(messageIdentifier [20]byte)(bool, [26]byte, error){
messageIdentifierHex := encoding.EncodeBytesToHexString(messageIdentifier[:])
exists, messageHashString, err := mySentMessagesMapDatastore.GetMapEntry(messageIdentifierHex)
if (err != nil) { return false, [26]byte{}, err }
if (exists == false){
return false, [26]byte{}, nil
}
messageHash, err := readMessages.ReadMessageHashHex(messageHashString)
if (err != nil){
return false, [26]byte{}, errors.New("mySentMessagesMapDatastore contains invalid map value: Not hex: " + messageHashString)
}
return true, messageHash, nil
}
//Outputs:
// -bool: Message is pending or sent (we are already trying to send the message, or we already sent it)
// -bool: Successfully started send (required information exists)
// -error
func StartSendingMessage(messageIdentifier [20]byte, myIdentityHash [16]byte, recipientIdentityHash [16]byte, messageNetworkType byte, messageTimeSent int64, messageCommunication string, messageDuration int)(bool, bool, error){
isValid := helpers.VerifyNetworkType(messageNetworkType)
if (isValid == false){
messageNetworkTypeString := helpers.ConvertByteToString(messageNetworkType)
return false, false, errors.New("StartSendingMessage called with invalid messageNetworkType: " + messageNetworkTypeString)
}
addingMessageMutex.Lock()
defer addingMessageMutex.Unlock()
messageIsPending := checkIfMessageIsPending(messageIdentifier)
if (messageIsPending == true){
// We are already trying to send this message.
return true, false, nil
}
messageIsSent, _, err := CheckIfMessageIsSent(messageIdentifier)
if (err != nil) { return false, false, err }
if (messageIsSent == true){
// We already sent the message
return true, false, nil
}
identityExists, myIdentityPublicKey, myIdentityPrivateKey, err := myIdentity.GetMyPublicPrivateIdentityKeysFromIdentityHash(myIdentityHash)
if (err != nil) { return false, false, err }
if (identityExists == false) {
return false, false, errors.New("StartSendingMessage called when my identity does not exist.")
}
myIdentityExists, isActiveStatus, err := myProfileStatus.GetMyProfileIsActiveStatus(myIdentityHash, messageNetworkType)
if (err != nil) { return false, false, err }
if (myIdentityExists == false) {
return false, false, errors.New("My identity not found after being found already.")
}
if (isActiveStatus == false){
return false, false, nil
}
exists, myChatKeysLatestUpdateTime, err := myChatKeys.GetMyChatKeysLatestUpdateTime(myIdentityHash, messageNetworkType)
if (err != nil) { return false, false, err }
if (exists == false){
// We have not broadcast a profile yet.
return false, false, nil
}
//Outputs:
// -[10]byte: Recipient Inbox
// -[32]byte: Recipient inbox sealer key
// -error
getRecipientInbox := func()([10]byte, [32]byte, error){
secretInboxFound, theirSecretInbox, theirSecretInboxSealerKey, err := peerSecretInboxes.GetPeerConversationCurrentSecretInbox(myIdentityHash, recipientIdentityHash, messageNetworkType)
if (err != nil) { return [10]byte{}, [32]byte{}, err }
if (secretInboxFound == true){
return theirSecretInbox, theirSecretInboxSealerKey, nil
}
recipientPublicInbox, err := inbox.GetPublicInboxFromIdentityHash(recipientIdentityHash)
if (err != nil) { return [10]byte{}, [32]byte{}, err }
recipientPublicInboxSealerKey, err := inbox.GetPublicInboxSealerKeyFromIdentityHash(recipientIdentityHash)
if (err != nil) { return [10]byte{}, [32]byte{}, err }
return recipientPublicInbox, recipientPublicInboxSealerKey, nil
}
recipientInbox, recipientInboxSealerKey, err := getRecipientInbox()
if (err != nil) { return false, false, err }
if (len(recipientInboxSealerKey) != 32){
return false, false, errors.New("getRecipientInbox returning invalid recipientInboxSealerKey: Invalid length.")
}
recipientInboxSealerKeyArray := [32]byte(recipientInboxSealerKey)
peerIsDisabled, peerChatKeysExist, recipientNaclKey, recipientKyberKey, err := peerChatKeys.GetPeerNewestActiveChatKeys(recipientIdentityHash, messageNetworkType)
if (err != nil) { return false, false, err }
if (peerIsDisabled == true || peerChatKeysExist == false) {
return false, false, nil
}
// The secret inboxes returned from the function below may not exist yet in our storage.
// If they are missing, we will create new secret inbox(es)
// We will add our new secret inboxes to our mySecretInboxes storage only if the message send is successful
parametersExist, myCurrentSecretInboxExists, myExistingCurrentSecretInboxSeed, myNextSecretInboxExists, myExistingNextSecretInboxSeed, err := mySecretInboxes.GetMySecretInboxSeedsForMessage(myIdentityHash, recipientIdentityHash, messageNetworkType, messageTimeSent)
if (err != nil) { return false, false, err }
if (parametersExist == false){
// We cannot determine the current epoch without the epoch parameters
return false, false, nil
}
getMyCurrentSecretInboxSeed := func()([22]byte, error){
if (myCurrentSecretInboxExists == true){
return myExistingCurrentSecretInboxSeed, nil
}
newCurrentSecretInboxSeed, err := inbox.GetNewRandomSecretInboxSeed()
if (err != nil) { return [22]byte{}, err }
return newCurrentSecretInboxSeed, nil
}
getMyNextSecretInboxSeed := func()([22]byte, error){
if (myNextSecretInboxExists == true){
return myExistingNextSecretInboxSeed, nil
}
newNextSecretInboxSeed, err := inbox.GetNewRandomSecretInboxSeed()
if (err != nil) { return [22]byte{}, err }
return newNextSecretInboxSeed, nil
}
myCurrentSecretInboxSeed, err := getMyCurrentSecretInboxSeed()
if (err != nil) { return false, false, err }
myNextSecretInboxSeed, err := getMyNextSecretInboxSeed()
if (err != nil) { return false, false, err }
myIdentityFound, myDeviceIdentifier, err := myDevice.GetMyDeviceIdentifier(myIdentityHash, messageNetworkType)
if (err != nil) { return false, false, err }
if (myIdentityFound == false){
return false, false, errors.New("My identity not found after being found already.")
}
chatParametersExist, err := getParameters.CheckIfChatParametersExist(messageNetworkType)
if (err != nil) { return false, false, err }
if (chatParametersExist == false){
return false, false, nil
}
// We have everything we need to send the message
// It will only fail in 2 ways:
// 1. We cannot connect to the account credit server(s)
// 2. The cost of sending messages has increased since we froze the funds, and we don't have enough funds to send the message anymore
finalMessage, messageHash, err := createMessages.CreateChatMessage(messageNetworkType, myIdentityHash, myIdentityPublicKey, myIdentityPrivateKey, messageTimeSent, myCurrentSecretInboxSeed, myNextSecretInboxSeed, myDeviceIdentifier, myChatKeysLatestUpdateTime, messageCommunication, recipientIdentityHash, recipientInbox, recipientNaclKey, recipientKyberKey, recipientInboxSealerKeyArray)
if (err != nil) { return false, false, err }
sendMessageFunction := func(){
attemptToSendMessage := func()error{
messageSize := len(finalMessage)
sufficientFundsExist, successfullyFunded, err := spendCredit.FundMyMessageWithServer(messageIdentifier, messageHash, messageNetworkType, messageSize, messageDuration)
if (err != nil) { return err }
if (sufficientFundsExist == false){
// Message send is not successful because we ran out of funds.
// We will try to send message again in the future, when hopefully user has added more funds
// This should only happen if the cost of sending the message has gone up since user froze funds
return nil
}
if (successfullyFunded == false){
// We could not connect to the server
return nil
}
// Message was successfully funded
// We add it to our broadcasts to be automatically broadcast
// We also add the secret inbox seeds and the message map to our chat messages
parametersExist, err := mySecretInboxes.AddMySecretInboxSeeds(myIdentityHash, recipientIdentityHash, messageNetworkType, messageTimeSent, myCurrentSecretInboxSeed, myNextSecretInboxSeed)
if (err != nil) { return err }
if (parametersExist == false){
// This is very unlikely, because we just checked for parameters
// We cannot determine the secret inbox epoch for the message we have funded
// We won't be able to download message replies to our sent secret inboxes
// As long as the user can pass this at least once within the current secret inbox epoch with a message sent
// to this recipient, they should be able to add their secret inbox seeds to the mySecretInboxes datastore
// TODO: Use a fallback value so this doesn't happen
}
err = myBroadcasts.BroadcastMyMessage(finalMessage)
if (err != nil) { return err }
err = myChatMessages.AddMyNewMessageToMyChatMessages("Sent", messageHash, messageIdentifier, myIdentityHash, recipientIdentityHash, messageNetworkType, messageTimeSent, messageCommunication)
if (err != nil) { return err }
messageIdentifierHex := encoding.EncodeBytesToHexString(messageIdentifier[:])
messageHashHex := encoding.EncodeBytesToHexString(messageHash[:])
err = mySentMessagesMapDatastore.SetMapEntry(messageIdentifierHex, messageHashHex)
if (err != nil){ return err }
return nil
}
err := attemptToSendMessage()
if (err != nil){
logger.AddLogError("General", err)
}
deleteMessageFromPendingMessagesMap(messageIdentifier)
pendingMessagesWaitgroup.Done()
}
addMessageToPendingMessagesMap(messageIdentifier)
pendingMessagesWaitgroup.Add(1)
go sendMessageFunction()
return false, true, nil
}
// This function calculates current network cost for a provided message
// This is used to show the user approximately how much a message will cost
//Outputs:
// -bool: Parameters exist
// -float64: Cost in provided currency
// -error
func GetMessageNetworkCurrencyCostForProvidedDuration(messageNetworkType byte, messageSizeBytes int, messageDurationToFund int, currencyCode string)(bool, float64, error){
isValid := helpers.VerifyNetworkType(messageNetworkType)
if (isValid == false){
messageNetworkTypeString := helpers.ConvertByteToString(messageNetworkType)
return false, 0, errors.New("GetMessageNetworkCurrencyCostForProvidedDuration called with invalid messageNetworkType: " + messageNetworkTypeString)
}
messageKilobytes := messageSizeBytes/1000
currentTime := time.Now().Unix()
parametersExist, currentCostPerDay, err := getParameters.GetMessageKilobyteGoldCostPerDay(messageNetworkType, currentTime)
if (err != nil) { return false, 0, err }
if (parametersExist == false) {
return false, 0, nil
}
messageDays := messageDurationToFund/86400
// GetMessageKilobyteGoldCostPerDay returns grams. We convert to kilograms.
kilogramsCostPerDay := currentCostPerDay/1000
costInKilogramsOfGold := float64(messageDays) * float64(messageKilobytes) * kilogramsCostPerDay
exchangeRatesFound, currencyCost, err := convertCurrencies.ConvertKilogramsOfGoldToAnyCurrency(messageNetworkType, costInKilogramsOfGold, currencyCode)
if (err != nil) { return false, 0, err }
if (exchangeRatesFound == false){
return false, 0, nil
}
return true, currencyCost, nil
}
// We use this function to estimate the size of a message we have not created yet
// This enables us to show the user how much the message will cost, and for us to freeze the required funds
func GetEstimatedMessageSize(recipientIdentityHash [16]byte, messageCommunication string)(int, error){
recipientIdentityType, err := identity.GetIdentityTypeFromIdentityHash(recipientIdentityHash)
if (err != nil) {
recipientIdentityHashHex := encoding.EncodeBytesToHexString(recipientIdentityHash[:])
return 0, errors.New("GetEstimatedMessageSize called with invalid recipientIdentityHash: " + recipientIdentityHashHex)
}
senderIdentityPublicKey, senderIdentityPrivateKey, err := identity.GetNewRandomPublicPrivateIdentityKeys()
if (err != nil) { return 0, err }
senderIdentityHash, err := identity.ConvertIdentityKeyToIdentityHash(senderIdentityPublicKey, recipientIdentityType)
if (err != nil) { return 0, err }
senderCurrentSecretInboxSeed, err := inbox.GetNewRandomSecretInboxSeed()
if (err != nil) { return 0, err }
senderNextSecretInboxSeed, err := inbox.GetNewRandomSecretInboxSeed()
if (err != nil) { return 0, err }
recipientInbox, err := inbox.GetPublicInboxFromIdentityHash(recipientIdentityHash)
if (err != nil) { return 0, err }
doubleSealedKeysSealerKey, err := helpers.GetNewRandom32ByteArray()
if (err != nil) { return 0, err }
senderLatestChatKeysUpdateTime := time.Now().UnixNano()
recipientNaclPublicKey, err := nacl.GetNewRandomPublicNaclKey()
if (err != nil) { return 0, err }
recipientKyberPublicKey, err := kyber.GetNewRandomPublicKyberKey()
if (err != nil) { return 0, err }
messageSentTime := time.Now().Unix()
senderDeviceIdentifier, err := helpers.GetNewRandomDeviceIdentifier()
if (err != nil) { return 0, err }
finalMessage, _, err := createMessages.CreateChatMessage(1, senderIdentityHash, senderIdentityPublicKey, senderIdentityPrivateKey, messageSentTime, senderCurrentSecretInboxSeed, senderNextSecretInboxSeed, senderDeviceIdentifier, senderLatestChatKeysUpdateTime, messageCommunication, recipientIdentityHash, recipientInbox, recipientNaclPublicKey, recipientKyberPublicKey, doubleSealedKeysSealerKey)
if (err != nil) { return 0, err }
messageSize := len(finalMessage)
return messageSize, nil
}