seekia/internal/messaging/peerDevices/peerDevices.go

285 lines
11 KiB
Go

// peerDevices provides functions to keep track of peer device identifiers
// When a user changes their device identifier, we discard their old secret inboxes and chat keys
// We use this package to keep track of when a user has moved to a new device
package peerDevices
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/myDatastores/myMap"
import "seekia/internal/profiles/profileStorage"
import "seekia/internal/profiles/readProfiles"
import "seekia/internal/unixTime"
import "time"
import "strings"
import "sync"
import "errors"
// We lock this whenever we are updating the map
var updatingPeerDevicesMapMutex sync.Mutex
// Map Structure: Peer Identity Hash + "@" + Network Type -> Device Identifier + "$" + Device First Seen Time
var peerDevicesMapDatastore *myMap.MyMap
// This function must be called whenever an app user signs in
func InitializePeerDevicesDatastore()error{
updatingPeerDevicesMapMutex.Lock()
defer updatingPeerDevicesMapMutex.Unlock()
newPeerDevicesMapDatastore, err := myMap.CreateNewMap("PeerDevices")
if (err != nil) { return err }
peerDevicesMapDatastore = newPeerDevicesMapDatastore
return nil
}
//Outputs:
// -bool: Device info found
// -[11]byte: Newest device identifier
// -int64: Time that this device was first seen
// -We discard all chat keys/secret inboxes that were sent before this point in time
// -error
func GetPeerNewestDeviceInfo(peerIdentityHash [16]byte, networkType byte)(bool, [11]byte, int64, error){
identityType, err := identity.GetIdentityTypeFromIdentityHash(peerIdentityHash)
if (err != nil){
peerIdentityHashHex := encoding.EncodeBytesToHexString(peerIdentityHash[:])
return false, [11]byte{}, 0, errors.New("GetPeerNewestDeviceInfo called with invalid identityHash: " + peerIdentityHashHex)
}
if (identityType == "Host"){
return false, [11]byte{}, 0, errors.New("GetPeerNewestDeviceInfo called with Host identity.")
}
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
networkTypeString := helpers.ConvertByteToString(networkType)
return false, [11]byte{}, 0, errors.New("GetPeerNewestDeviceInfo called with invalid networkType: " + networkTypeString)
}
// We use this lock because we are comparing the map time with the newest stored profile broadcast time
// If we added a newer time from a message during this process, without a lock, we could overwrite the newer time from the message
updatingPeerDevicesMapMutex.Lock()
defer updatingPeerDevicesMapMutex.Unlock()
//Outputs:
// -bool: Peer profile found
// -string: Peer profile device identifier (encoded Hex)
// -[11]byte: Peer profile device identifier
// -int64: Broadcast time of profile
// -error
getPeerDeviceIdentifierFromNewestProfile := func()(bool, string, [11]byte, int64, error){
profileExists, _, _, _, profileBroadcastTime, rawProfileMap, err := profileStorage.GetNewestUserProfile(peerIdentityHash, networkType)
if (err != nil) { return false, "", [11]byte{}, 0, err }
if (profileExists == false){
return false, "", [11]byte{}, 0, nil
}
attributeExists, deviceIdentifierHex, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(rawProfileMap, "DeviceIdentifier")
if (err != nil) { return false, "", [11]byte{}, 0, err }
if (attributeExists == false){
return false, "", [11]byte{}, 0, errors.New("Database corrupt: Contains Mate/Moderator profile missing DeviceIdentifier.")
}
deviceIdentifierBytes, err := encoding.DecodeHexStringToBytes(deviceIdentifierHex)
if (err != nil){
return false, "", [11]byte{}, 0, errors.New("Database corrupt: Contains profile with invalid DeviceIdentifier: " + deviceIdentifierHex)
}
if (len(deviceIdentifierBytes) != 11){
return false, "", [11]byte{}, 0, errors.New("Database corrupt: Contains profile with invalid DeviceIdentifier: " + deviceIdentifierHex)
}
deviceIdentifierArray := [11]byte(deviceIdentifierBytes)
return true, deviceIdentifierHex, deviceIdentifierArray, profileBroadcastTime, nil
}
peerProfileFound, peerProfileDeviceIdentifierHex, peerProfileDeviceIdentifier, peerProfileBroadcastTime, err := getPeerDeviceIdentifierFromNewestProfile()
if (err != nil) { return false, [11]byte{}, 0, err }
peerIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(peerIdentityHash)
if (err != nil) {
peerIdentityHashHex := encoding.EncodeBytesToHexString(peerIdentityHash[:])
return false, [11]byte{}, 0, errors.New("GetIdentityTypeFromIdentityHash not verifying identity hash: " + peerIdentityHashHex)
}
networkTypeString := helpers.ConvertByteToString(networkType)
peerDevicesMapDatastoreKey := peerIdentityHashString + "@" + networkTypeString
mapEntryExists, mapValue, err := peerDevicesMapDatastore.GetMapEntry(peerDevicesMapDatastoreKey)
if (err != nil) { return false, [11]byte{}, 0, err }
if (mapEntryExists == false){
if (peerProfileFound == false){
return false, [11]byte{}, 0, nil
}
// peerProfileFound == true
peerProfileBroadcastTimeString := helpers.ConvertInt64ToString(peerProfileBroadcastTime)
newMapValue := peerProfileDeviceIdentifierHex + "$" + peerProfileBroadcastTimeString
err := peerDevicesMapDatastore.SetMapEntry(peerDevicesMapDatastoreKey, newMapValue)
if (err != nil) { return false, [11]byte{}, 0, err }
return true, peerProfileDeviceIdentifier, peerProfileBroadcastTime, nil
}
// mapEntryExists == true
mapDeviceIdentifier, mapDeviceFirstSeenTimeString, delimiterFound := strings.Cut(mapValue, "$")
if (delimiterFound == false){
return false, [11]byte{}, 0, errors.New("peerDevicesMapDatastore is malformed: Invalid value: " + mapValue)
}
mapDeviceIdentifierBytes, err := encoding.DecodeHexStringToBytes(mapDeviceIdentifier)
if (err != nil){
return false, [11]byte{}, 0, errors.New("peerDevicesMapDatastore is malformed: Invalid DeviceIdentifier: " + mapDeviceIdentifier)
}
if (len(mapDeviceIdentifierBytes) != 11){
return false, [11]byte{}, 0, errors.New("peerDevicesMapDatastore is malformed: Invalid DeviceIdentifier: " + mapDeviceIdentifier)
}
mapDeviceIdentifierArray := [11]byte(mapDeviceIdentifierBytes)
mapDeviceFirstSeenTime, err := helpers.ConvertBroadcastTimeStringToInt64(mapDeviceFirstSeenTimeString)
if (err != nil){
return false, [11]byte{}, 0, errors.New("peerDevicesMapDatastore is malformed: Invalid device first seen time: " + mapDeviceFirstSeenTimeString)
}
if (peerProfileFound == false){
return true, mapDeviceIdentifierArray, mapDeviceFirstSeenTime, nil
}
// Both a map entry and a stored profile exist
// We see if the profile is newer/older than what we have stored in the map
// If it is, we update the map
if (peerProfileDeviceIdentifierHex == mapDeviceIdentifier){
if (mapDeviceFirstSeenTime <= peerProfileBroadcastTime){
// Map device first seen time is earlier than or equal to the profile
// Nothing to update
return true, mapDeviceIdentifierArray, mapDeviceFirstSeenTime, nil
}
// Profile has a device first seen time that is earlier
// We will update the map with the profile time
} else {
if (mapDeviceFirstSeenTime >= peerProfileBroadcastTime){
// Map device first seen time is newer than or equal to the profile
// Nothing to update
return true, mapDeviceIdentifierArray, mapDeviceFirstSeenTime, nil
}
}
peerProfileBroadcastTimeString := helpers.ConvertInt64ToString(peerProfileBroadcastTime)
newMapValue := peerProfileDeviceIdentifierHex + "$" + peerProfileBroadcastTimeString
err = peerDevicesMapDatastore.SetMapEntry(peerDevicesMapDatastoreKey, newMapValue)
if (err != nil) { return false, [11]byte{}, 0, err }
return true, peerProfileDeviceIdentifier, peerProfileBroadcastTime, nil
}
func AddPeerDeviceIdentifierFromMessage(peerIdentityHash [16]byte, networkType byte, deviceIdentifierBytes [11]byte, messageTimeSentUnix int64)error{
peerIdentityHashString, identityType, err := identity.EncodeIdentityHashBytesToString(peerIdentityHash)
if (err != nil) {
peerIdentityHashHex := encoding.EncodeBytesToHexString(peerIdentityHash[:])
return errors.New("AddPeerDeviceIdentifierFromMessage called with invalid identityHash: " + peerIdentityHashHex)
}
if (identityType == "Host"){
return errors.New("AddPeerDeviceIdentifierFromMessage called with Host identity.")
}
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
networkTypeString := helpers.ConvertByteToString(networkType)
return errors.New("AddPeerDeviceIdentifierFromMessage called with invalid networkType: " + networkTypeString)
}
deviceIdentifier := encoding.EncodeBytesToHexString(deviceIdentifierBytes[:])
currentTime := time.Now().Unix()
dayUnix := unixTime.GetDayUnix()
latestAllowedTime := currentTime + dayUnix
if (messageTimeSentUnix > latestAllowedTime){
// Either sender's or our own computer clock must be inaccurate
// We don't want to add the device identifier because it could prevent us from ever being able to contact the user
// For example, if the user's message sent time is accidentally 1 year in the future, we would not be able to contact them for 1 year
// We would mistakenly reject all of their new profile chat keys as being out of date
return nil
}
networkTypeString := helpers.ConvertByteToString(networkType)
peerDevicesMapDatastoreKey := peerIdentityHashString + "@" + networkTypeString
updatingPeerDevicesMapMutex.Lock()
defer updatingPeerDevicesMapMutex.Unlock()
mapEntryExists, mapValue, err := peerDevicesMapDatastore.GetMapEntry(peerDevicesMapDatastoreKey)
if (err != nil) { return err }
if (mapEntryExists == true){
// We see if the map entry is newer than the device identifier sent in the message
// If it is newer, then we will not add the message device identifier
mapDeviceIdentifier, mapDeviceFirstSeenTimeString, delimiterFound := strings.Cut(mapValue, "$")
if (delimiterFound == false){
return errors.New("peerDevicesMapDatastore is malformed: Invalid value: " + mapValue)
}
mapDeviceFirstSeenTime, err := helpers.ConvertBroadcastTimeStringToInt64(mapDeviceFirstSeenTimeString)
if (err != nil){
return errors.New("peerDevicesMapDatastore is malformed: Invalid device first seen time: " + mapDeviceFirstSeenTimeString)
}
if (mapDeviceIdentifier == deviceIdentifier){
if (mapDeviceFirstSeenTime <= messageTimeSentUnix){
// Our map device first seen time is earlier than or equal to the message
// Nothing to do
return nil
}
// We will update our map device first seen time to be earlier
} else {
if (mapDeviceFirstSeenTime >= messageTimeSentUnix){
// Our map device first seen time is newer than or equal to the input message.
// Nothing to do
return nil
}
}
}
messageTimeSentUnixString := helpers.ConvertInt64ToString(messageTimeSentUnix)
newMapValue := deviceIdentifier + "$" + messageTimeSentUnixString
err = peerDevicesMapDatastore.SetMapEntry(peerDevicesMapDatastoreKey, newMapValue)
if (err != nil) { return err }
return nil
}