333 lines
13 KiB
Go
333 lines
13 KiB
Go
|
|
// profileStorage provides functions to store and retrieve downloaded profiles
|
|
|
|
package profileStorage
|
|
|
|
//TODO: Add a job to prune database entries for profiles/attributes which have been deleted
|
|
|
|
import "seekia/internal/badgerDatabase"
|
|
import "seekia/internal/contentMetadata"
|
|
import "seekia/internal/encoding"
|
|
import "seekia/internal/helpers"
|
|
import "seekia/internal/identity"
|
|
import "seekia/internal/mySettings"
|
|
import "seekia/internal/profiles/readProfiles"
|
|
|
|
import messagepack "github.com/vmihailenco/msgpack/v5"
|
|
|
|
import "errors"
|
|
import "slices"
|
|
|
|
func GetNumberOfStoredProfiles()(int64, error){
|
|
|
|
numberOfProfiles, err := badgerDatabase.GetNumberOfUserProfiles()
|
|
if (err != nil) { return 0, err }
|
|
|
|
return numberOfProfiles, nil
|
|
}
|
|
|
|
// Returns all identity hashes of all authors of profiles in the database
|
|
func GetAllStoredProfileIdentityHashes()([][16]byte, error){
|
|
|
|
mateProfileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Mate")
|
|
if (err != nil) { return nil, err }
|
|
|
|
hostProfileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Host")
|
|
if (err != nil) { return nil, err }
|
|
|
|
moderatorProfileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Moderator")
|
|
if (err != nil) { return nil, err }
|
|
|
|
allProfileIdentityHashesList := slices.Concat(mateProfileIdentityHashesList, hostProfileIdentityHashesList, moderatorProfileIdentityHashesList)
|
|
|
|
return allProfileIdentityHashesList, nil
|
|
}
|
|
|
|
func GetAllStoredProfileHashes()([][28]byte, error){
|
|
|
|
allMateProfileHashesList, err := badgerDatabase.GetAllProfileHashes("Mate")
|
|
if (err != nil) { return nil, err }
|
|
|
|
allHostProfileHashesList, err := badgerDatabase.GetAllProfileHashes("Host")
|
|
if (err != nil) { return nil, err }
|
|
|
|
allModeratorProfileHashesList, err := badgerDatabase.GetAllProfileHashes("Moderator")
|
|
if (err != nil) { return nil, err }
|
|
|
|
allProfileHashesList := slices.Concat(allMateProfileHashesList, allHostProfileHashesList, allModeratorProfileHashesList)
|
|
|
|
return allProfileHashesList, nil
|
|
}
|
|
|
|
|
|
//Outputs:
|
|
// -bool: Profile exists
|
|
// -[]byte: Profile Bytes
|
|
// -error
|
|
func GetStoredProfile(profileHash [28]byte)(bool, []byte, error){
|
|
|
|
profileType, _, err := readProfiles.ReadProfileHashMetadata(profileHash)
|
|
if (err != nil) {
|
|
profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
|
|
return false, nil, errors.New("GetStoredProfile called with invalid profileHash: " + profileHashHex)
|
|
}
|
|
|
|
exists, profileBytes, err := badgerDatabase.GetUserProfile(profileType, profileHash)
|
|
if (err != nil) { return false, nil, err }
|
|
if (exists == false){
|
|
return false, nil, nil
|
|
}
|
|
|
|
return true, profileBytes, nil
|
|
}
|
|
|
|
|
|
// This function verifies the profile file is valid and adds the profile to the database
|
|
//Outputs:
|
|
// -bool: Profile is well formed
|
|
// -[28]byte: Profile hash of added profile
|
|
// -error
|
|
func AddUserProfile(inputProfile []byte)(bool, [28]byte, error){
|
|
|
|
ableToRead, newProfileHash, newProfileVersion, newProfileNetworkType, userIdentityHash, _, _, rawProfileMap, err := readProfiles.ReadProfileAndHash(true, inputProfile)
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
if (ableToRead == false){
|
|
// Profile is invalid, host that we downloaded from must be malicious, or profile version is newer than our own.
|
|
return false, [28]byte{}, nil
|
|
}
|
|
|
|
userIdentityType, err := identity.GetIdentityTypeFromIdentityHash(userIdentityHash)
|
|
if (err != nil) {
|
|
identityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:])
|
|
return false, [28]byte{}, errors.New("IdentityHash invalid after profile has been verified: " + identityHashHex)
|
|
}
|
|
|
|
exists, _, err := badgerDatabase.GetUserProfile(userIdentityType, newProfileHash)
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
if (exists == true){
|
|
// Profile already imported, skip it.
|
|
return true, newProfileHash, nil
|
|
}
|
|
|
|
err = badgerDatabase.AddUserProfile(userIdentityType, newProfileHash, inputProfile)
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
|
|
err = badgerDatabase.AddIdentityProfileHash(userIdentityHash, newProfileHash)
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
|
|
profileAttributeHashesMap, _, err := readProfiles.GetProfileAttributeHashesMap(userIdentityHash, newProfileVersion, newProfileNetworkType, rawProfileMap)
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
|
|
for _, attributeHash := range profileAttributeHashesMap{
|
|
|
|
err := badgerDatabase.AddAttributeProfile(attributeHash, newProfileHash)
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
}
|
|
|
|
if (userIdentityType == "Mate"){
|
|
|
|
err = mySettings.SetSetting("MyMatchesNeedRefreshYesNo", "Yes")
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
|
|
} else if (userIdentityType == "Host"){
|
|
|
|
err = mySettings.SetSetting("ViewedHostsNeedsRefreshYesNo", "Yes")
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
|
|
} else if (userIdentityType == "Moderator"){
|
|
|
|
err = mySettings.SetSetting("ViewedModeratorsNeedsRefreshYesNo", "Yes")
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
}
|
|
|
|
err = mySettings.SetSetting("ViewedContentNeedsRefreshYesNo", "Yes")
|
|
if (err != nil) { return false, [28]byte{}, err }
|
|
|
|
return true, newProfileHash, nil
|
|
}
|
|
|
|
// This function will return a user's newest profile. Viewable status (approved/banned status) is ignored
|
|
//Outputs:
|
|
// -bool: Profile exists
|
|
// -int: Profile version
|
|
// -[28]byte: Profile hash
|
|
// -[]byte: Profile bytes
|
|
// -int64: Profile broadcast time
|
|
// -map[int]messagepack.RawMessage: Raw profile map
|
|
// -error
|
|
func GetNewestUserProfile(userIdentityHash [16]byte, networkType byte)(bool, int, [28]byte, []byte, int64, map[int]messagepack.RawMessage, error){
|
|
|
|
identityType, err := identity.GetIdentityTypeFromIdentityHash(userIdentityHash)
|
|
if (err != nil) {
|
|
userIdentityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:])
|
|
return false, 0, [28]byte{}, nil, 0, nil, errors.New("GetNewestUserProfile called with invalid userIdentityHash: " + userIdentityHashHex)
|
|
}
|
|
|
|
isValid := helpers.VerifyNetworkType(networkType)
|
|
if (isValid == false){
|
|
networkTypeString := helpers.ConvertByteToString(networkType)
|
|
return false, 0, [28]byte{}, nil, 0, nil, errors.New("GetNewestUserProfile called with invalid networkType: " + networkTypeString)
|
|
}
|
|
|
|
exists, profileHashesList, err := badgerDatabase.GetIdentityProfileHashesList(userIdentityHash)
|
|
if (err != nil) { return false, 0, [28]byte{}, nil, 0, nil, err }
|
|
if (exists == false){
|
|
return false, 0, [28]byte{}, nil, 0, nil, nil
|
|
}
|
|
|
|
anyProfileFound := false
|
|
|
|
//TODO: Deal with 2 profiles with identical broadcastTimes
|
|
// We must compare the profile hashes
|
|
|
|
var newestProfileHash [28]byte
|
|
newestProfileVersion := 0
|
|
newestProfileBroadcastTime := int64(0)
|
|
newestProfileBytes := make([]byte, 0)
|
|
newestProfileRawProfileMap := make(map[int]messagepack.RawMessage)
|
|
|
|
for _, profileHash := range profileHashesList{
|
|
|
|
exists, profileBytes, err := badgerDatabase.GetUserProfile(identityType, profileHash)
|
|
if (err != nil) { return false, 0, [28]byte{}, nil, 0, nil, err }
|
|
if (exists == false){
|
|
// Identity profile hashes list is outdated, it will be updated by databaseJobs
|
|
continue
|
|
}
|
|
|
|
ableToRead, profileVersion, profileNetworkType, profileIdentityHash, profileBroadcastTime, _, rawProfileMap, err := readProfiles.ReadProfile(false, profileBytes)
|
|
if (err != nil) { return false, 0, [28]byte{}, nil, 0, nil, err }
|
|
if (ableToRead == false){
|
|
return false, 0, [28]byte{}, nil, 0, nil, errors.New("Database corrupt: Contains invalid profile.")
|
|
}
|
|
if (profileIdentityHash != userIdentityHash){
|
|
return false, 0, [28]byte{}, nil, 0, nil, errors.New("Database corrupt: identity profiles list contains profile with different identityHash")
|
|
}
|
|
if (profileNetworkType != networkType){
|
|
// This profile belongs to a different network type
|
|
// Example of network types: Mainnet, Testnet1
|
|
continue
|
|
}
|
|
|
|
if (anyProfileFound == false || profileBroadcastTime > newestProfileBroadcastTime){
|
|
newestProfileVersion = profileVersion
|
|
newestProfileHash = profileHash
|
|
newestProfileBroadcastTime = profileBroadcastTime
|
|
newestProfileRawProfileMap = rawProfileMap
|
|
newestProfileBytes = profileBytes
|
|
}
|
|
|
|
anyProfileFound = true
|
|
}
|
|
|
|
if (anyProfileFound == false){
|
|
return false, 0, [28]byte{}, nil, 0, nil, nil
|
|
}
|
|
|
|
return true, newestProfileVersion, newestProfileHash, newestProfileBytes, newestProfileBroadcastTime, newestProfileRawProfileMap, nil
|
|
}
|
|
|
|
|
|
|
|
|
|
// This function is used to find metadata about a profile attribute
|
|
// We search through all stored profiles to find a profile that the attribute belongs to
|
|
// We may be able to verify the profile author while we are missing the actual profile content
|
|
// That would happen if we have saved the profile metadata and deleted the profile
|
|
//
|
|
//Outputs:
|
|
// -bool: Metadata exists
|
|
// -int: Attribute identifier
|
|
// -[16]byte: Attribute author identity hash
|
|
// -byte: Attribute network type
|
|
// -[][28]byte: List of profile hashes with this attribute (they may be deleted/expired)
|
|
// -error
|
|
func GetProfileAttributeMetadata(inputAttributeHash [27]byte)(bool, int, [16]byte, byte, [][28]byte, error){
|
|
|
|
isValid, err := readProfiles.VerifyAttributeHash(inputAttributeHash, false, "", false, false)
|
|
if (err != nil) { return false, 0, [16]byte{}, 0, nil, err }
|
|
if (isValid == false){
|
|
inputAttributeHashHex := encoding.EncodeBytesToHexString(inputAttributeHash[:])
|
|
return false, 0, [16]byte{}, 0, nil, errors.New("GetProfileAttributeMetadata called with invalid attributeHash: " + inputAttributeHashHex)
|
|
}
|
|
|
|
anyExist, attributeProfileHashesList, err := badgerDatabase.GetAttributeProfilesList(inputAttributeHash)
|
|
if (err != nil) { return false, 0, [16]byte{}, 0, nil, err }
|
|
if (anyExist == false){
|
|
return false, 0, [16]byte{}, 0, nil, nil
|
|
}
|
|
|
|
for _, profileHash := range attributeProfileHashesList{
|
|
|
|
profileMetadataExists, _, profileNetworkType, profileAuthor, _, profileIsDisabled, _, profileAttributeHashesMap, err := contentMetadata.GetProfileMetadata(profileHash)
|
|
if (err != nil) { return false, 0, [16]byte{}, 0, nil, err }
|
|
if (profileMetadataExists == false){
|
|
continue
|
|
}
|
|
if (profileIsDisabled == true){
|
|
// The profile is disabled, the attribute hash cannot belong to it.
|
|
return false, 0, [16]byte{}, 0, nil, errors.New("AttributeProfilesList contains disabled profile.")
|
|
}
|
|
|
|
for attributeIdentifier, profileAttributeHash := range profileAttributeHashesMap{
|
|
|
|
if (profileAttributeHash == inputAttributeHash){
|
|
return true, attributeIdentifier, profileAuthor, profileNetworkType, attributeProfileHashesList, nil
|
|
}
|
|
}
|
|
|
|
// This is only reached if the profile did not contain the attribute
|
|
return false, 0, [16]byte{}, 0, nil, errors.New("AttributeProfilesList contains profile which does not contain the attribute.")
|
|
}
|
|
|
|
// We could not find the attribute's metadata
|
|
|
|
return false, 0, [16]byte{}, 0, attributeProfileHashesList, nil
|
|
}
|
|
|
|
|
|
// This function is the same as GetProfileAttributeMetadata, except it attempts to find a full profile which contains the attribute
|
|
//Outputs:
|
|
// -bool: Metadata exists
|
|
// -int: Attribute identifier
|
|
// -[16]byte: Attribute author identity hash
|
|
// -byte: Attribute network type
|
|
// -[][28]byte: List of profile hashes with this attribute (they may be deleted/expired)
|
|
// -bool: Full Profile exists
|
|
// -[28]byte: Full profile hash
|
|
// -[]byte: Full Profile bytes
|
|
// -error
|
|
func GetProfileAttributeMetadataAndProfile(inputAttributeHash [27]byte)(bool, int, [16]byte, byte, [][28]byte, bool, [28]byte, []byte, error){
|
|
|
|
isValid, err := readProfiles.VerifyAttributeHash(inputAttributeHash, false, "", false, false)
|
|
if (err != nil) { return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, err }
|
|
if (isValid == false){
|
|
inputAttributeHashHex := encoding.EncodeBytesToHexString(inputAttributeHash[:])
|
|
return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, errors.New("GetProfileAttributeMetadataAndProfile called with invalid attributeHash: " + inputAttributeHashHex)
|
|
}
|
|
|
|
metadataExists, attributeIdentifier, attributeAuthor, attributeNetworkType, attributeProfileHashesList, err := GetProfileAttributeMetadata(inputAttributeHash)
|
|
if (err != nil) { return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, err }
|
|
if (metadataExists == false){
|
|
|
|
// Attribute metadata does not exist.
|
|
|
|
return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, nil
|
|
}
|
|
|
|
// Now we try to find a full profile which contains this attribute
|
|
|
|
for _, profileHash := range attributeProfileHashesList{
|
|
|
|
profileExists, profileBytes, err := GetStoredProfile(profileHash)
|
|
if (err != nil) { return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, err }
|
|
if (profileExists == true){
|
|
return true, attributeIdentifier, attributeAuthor, attributeNetworkType, attributeProfileHashesList, true, profileHash, profileBytes, nil
|
|
}
|
|
}
|
|
|
|
// We cannot find a full profile containing this attribute
|
|
|
|
return true, attributeIdentifier, attributeAuthor, attributeNetworkType, attributeProfileHashesList, false, [28]byte{}, nil, nil
|
|
}
|
|
|