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