285 lines
11 KiB
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 creation 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: Creation time of profile
|
|
// -error
|
|
getPeerDeviceIdentifierFromNewestProfile := func()(bool, string, [11]byte, int64, error){
|
|
|
|
profileExists, _, _, _, profileCreationTime, 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, profileCreationTime, nil
|
|
}
|
|
|
|
peerProfileFound, peerProfileDeviceIdentifierHex, peerProfileDeviceIdentifier, peerProfileCreationTime, 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
|
|
|
|
peerProfileCreationTimeString := helpers.ConvertInt64ToString(peerProfileCreationTime)
|
|
|
|
newMapValue := peerProfileDeviceIdentifierHex + "$" + peerProfileCreationTimeString
|
|
|
|
err := peerDevicesMapDatastore.SetMapEntry(peerDevicesMapDatastoreKey, newMapValue)
|
|
if (err != nil) { return false, [11]byte{}, 0, err }
|
|
|
|
return true, peerProfileDeviceIdentifier, peerProfileCreationTime, 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.ConvertCreationTimeStringToInt64(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 <= peerProfileCreationTime){
|
|
// 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 >= peerProfileCreationTime){
|
|
// Map device first seen time is newer than or equal to the profile
|
|
// Nothing to update
|
|
return true, mapDeviceIdentifierArray, mapDeviceFirstSeenTime, nil
|
|
}
|
|
}
|
|
|
|
peerProfileCreationTimeString := helpers.ConvertInt64ToString(peerProfileCreationTime)
|
|
|
|
newMapValue := peerProfileDeviceIdentifierHex + "$" + peerProfileCreationTimeString
|
|
|
|
err = peerDevicesMapDatastore.SetMapEntry(peerDevicesMapDatastoreKey, newMapValue)
|
|
if (err != nil) { return false, [11]byte{}, 0, err }
|
|
|
|
return true, peerProfileDeviceIdentifier, peerProfileCreationTime, nil
|
|
}
|
|
|
|
|
|
func AddPeerDeviceIdentifierFromMessage(peerIdentityHash [16]byte, networkType byte, deviceIdentifierBytes [11]byte, messageCreationTimeUnix 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 (messageCreationTimeUnix > 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 creation 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.ConvertCreationTimeStringToInt64(mapDeviceFirstSeenTimeString)
|
|
if (err != nil){
|
|
return errors.New("peerDevicesMapDatastore is malformed: Invalid device first seen time: " + mapDeviceFirstSeenTimeString)
|
|
}
|
|
|
|
if (mapDeviceIdentifier == deviceIdentifier){
|
|
|
|
if (mapDeviceFirstSeenTime <= messageCreationTimeUnix){
|
|
// 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 >= messageCreationTimeUnix){
|
|
// Our map device first seen time is newer than or equal to the input message.
|
|
// Nothing to do
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
messageCreationTimeUnixString := helpers.ConvertInt64ToString(messageCreationTimeUnix)
|
|
|
|
newMapValue := deviceIdentifier + "$" + messageCreationTimeUnixString
|
|
|
|
err = peerDevicesMapDatastore.SetMapEntry(peerDevicesMapDatastoreKey, newMapValue)
|
|
if (err != nil) { return err }
|
|
|
|
return nil
|
|
}
|
|
|
|
|
|
|
|
|