seekia/internal/profiles/profileStorage/profileStorage.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
}